| @@ -0,0 +1,499 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIChatTabBar = extends(UIWidget, "UIChatTabBar") | |
| 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 | + | ||
| 98 | + | if not tabBar.currentTab and #tabBar.tabs > 0 then | |
| 99 | + | tabBar:selectTab(tabBar.tabs[1]) | |
| 100 | + | end | |
| 101 | + | end | |
| 102 | + | ||
| 103 | + | local function hideTabs(tabBar, fromBack, toArray, width) | |
| 104 | + | while #tabBar.tabs > 0 and getMaxMargin(tabBar) + width > tabBar:getWidth() do | |
| 105 | + | local index = fromBack and #tabBar.tabs or 1 | |
| 106 | + | local tab = tabBar.tabs[index] | |
| 107 | + | table.remove(tabBar.tabs, index) | |
| 108 | + | if fromBack then | |
| 109 | + | table.insert(toArray, 1, tab) | |
| 110 | + | else | |
| 111 | + | table.insert(toArray, tab) | |
| 112 | + | end | |
| 113 | + | if tabBar.currentTab == tab then | |
| 114 | + | if #tabBar.tabs > 0 then | |
| 115 | + | tabBar:selectTab(tabBar.tabs[#tabBar.tabs]) | |
| 116 | + | else | |
| 117 | + | tabBar.currentTab:setChecked(false) | |
| 118 | + | tabBar.currentTab = nil | |
| 119 | + | end | |
| 120 | + | end | |
| 121 | + | tab:setVisible(false) | |
| 122 | + | end | |
| 123 | + | end | |
| 124 | + | ||
| 125 | + | local function showPreTab(tabBar) | |
| 126 | + | if #tabBar.preTabs == 0 then | |
| 127 | + | return nil | |
| 128 | + | end | |
| 129 | + | ||
| 130 | + | local tmpTab = tabBar.preTabs[#tabBar.preTabs] | |
| 131 | + | hideTabs(tabBar, true, tabBar.postTabs, tmpTab:getWidth()) | |
| 132 | + | ||
| 133 | + | table.remove(tabBar.preTabs, #tabBar.preTabs) | |
| 134 | + | table.insert(tabBar.tabs, 1, tmpTab) | |
| 135 | + | tmpTab:setVisible(true) | |
| 136 | + | return tmpTab | |
| 137 | + | end | |
| 138 | + | ||
| 139 | + | local function showPostTab(tabBar) | |
| 140 | + | if #tabBar.postTabs == 0 then | |
| 141 | + | return nil | |
| 142 | + | end | |
| 143 | + | ||
| 144 | + | local tmpTab = tabBar.postTabs[1] | |
| 145 | + | hideTabs(tabBar, false, tabBar.preTabs, tmpTab:getWidth()) | |
| 146 | + | ||
| 147 | + | table.remove(tabBar.postTabs, 1) | |
| 148 | + | table.insert(tabBar.tabs, tmpTab) | |
| 149 | + | tmpTab:setVisible(true) | |
| 150 | + | return tmpTab | |
| 151 | + | end | |
| 152 | + | ||
| 153 | + | local function onTabMousePress(tab, mousePos, mouseButton) | |
| 154 | + | if mouseButton == MouseRightButton then | |
| 155 | + | if tab.menuCallback then tab.menuCallback(tab, mousePos, mouseButton) end | |
| 156 | + | return true | |
| 157 | + | end | |
| 158 | + | end | |
| 159 | + | ||
| 160 | + | local function onTabDragEnter(tab, mousePos) | |
| 161 | + | tab:raise() | |
| 162 | + | tab.hotSpot = mousePos.x - tab:getMarginLeft() | |
| 163 | + | tab.tabBar.selected = tab | |
| 164 | + | return true | |
| 165 | + | end | |
| 166 | + | ||
| 167 | + | local function onTabDragLeave(tab) | |
| 168 | + | updateMargins(tab.tabBar) | |
| 169 | + | tab.tabBar.selected = nil | |
| 170 | + | return true | |
| 171 | + | end | |
| 172 | + | ||
| 173 | + | local function onTabDragMove(tab, mousePos, mouseMoved) | |
| 174 | + | if tab == tab.tabBar.selected then | |
| 175 | + | local xoff = mousePos.x - tab.hotSpot | |
| 176 | + | ||
| 177 | + | -- update indexes | |
| 178 | + | updateIndexes(tab.tabBar, tab, xoff) | |
| 179 | + | updateIndexes(tab.tabBar, tab, xoff) | |
| 180 | + | ||
| 181 | + | -- update margins | |
| 182 | + | updateMargins(tab.tabBar) | |
| 183 | + | xoff = math.max(xoff, 0) | |
| 184 | + | xoff = math.min(xoff, getMaxMargin(tab.tabBar, tab)) | |
| 185 | + | tab:setMarginLeft(xoff) | |
| 186 | + | end | |
| 187 | + | end | |
| 188 | + | ||
| 189 | + | local function tabBlink(tab, steps, maxSteps, interval) | |
| 190 | + | local steps = steps or 0 | |
| 191 | + | local maxSteps = maxSteps or 4 | |
| 192 | + | local interval = interval or 500 | |
| 193 | + | tab:setOn(not tab:isOn()) | |
| 194 | + | ||
| 195 | + | removeEvent(tab.blinkEvent) | |
| 196 | + | if steps < maxSteps then | |
| 197 | + | tab.blinkEvent = scheduleEvent(function() tabBlink(tab, steps + 1, maxSteps, interval) end, interval) | |
| 198 | + | else | |
| 199 | + | tab:setOn(true) | |
| 200 | + | tab.blinkEvent = nil | |
| 201 | + | end | |
| 202 | + | end | |
| 203 | + | ||
| 204 | + | -- public functions | |
| 205 | + | function UIChatTabBar.create() | |
| 206 | + | local tabbar = UIChatTabBar.internalCreate() | |
| 207 | + | tabbar:setFocusable(false) | |
| 208 | + | tabbar.tabs = {} | |
| 209 | + | tabbar.selected = nil -- dragged tab | |
| 210 | + | tabbar.tabSpacing = 0 | |
| 211 | + | tabbar.tabsMoveable = false | |
| 212 | + | tabbar.preTabs = {} | |
| 213 | + | tabbar.postTabs = {} | |
| 214 | + | tabbar.prevNavigation = nil | |
| 215 | + | tabbar.nextNavigation = nil | |
| 216 | + | tabbar.onGeometryChange = function() | |
| 217 | + | hideTabs(tabbar, true, tabbar.postTabs, 0) | |
| 218 | + | updateTabs(tabbar) | |
| 219 | + | end | |
| 220 | + | return tabbar | |
| 221 | + | end | |
| 222 | + | ||
| 223 | + | function UIChatTabBar:onDestroy() | |
| 224 | + | if self.prevNavigation then | |
| 225 | + | self.prevNavigation:disable() | |
| 226 | + | end | |
| 227 | + | ||
| 228 | + | if self.nextNavigation then | |
| 229 | + | self.nextNavigation:disable() | |
| 230 | + | end | |
| 231 | + | ||
| 232 | + | self.nextNavigation = nil | |
| 233 | + | self.prevNavigation = nil | |
| 234 | + | end | |
| 235 | + | ||
| 236 | + | function UIChatTabBar:setContentWidget(widget) | |
| 237 | + | self.contentWidget = widget | |
| 238 | + | if #self.tabs > 0 then | |
| 239 | + | self.contentWidget:addChild(self.tabs[1].tabPanel) | |
| 240 | + | end | |
| 241 | + | end | |
| 242 | + | ||
| 243 | + | function UIChatTabBar:setTabSpacing(tabSpacing) | |
| 244 | + | self.tabSpacing = tabSpacing | |
| 245 | + | updateMargins(self) | |
| 246 | + | end | |
| 247 | + | ||
| 248 | + | function UIChatTabBar:addTab(text, panel, menuCallback) | |
| 249 | + | if panel == nil then | |
| 250 | + | panel = g_ui.createWidget(self:getStyleName() .. 'Panel') | |
| 251 | + | panel:setId('tabPanel') | |
| 252 | + | end | |
| 253 | + | ||
| 254 | + | local tab = g_ui.createWidget(self:getStyleName() .. 'Button', self) | |
| 255 | + | panel.isTab = true | |
| 256 | + | tab.tabPanel = panel | |
| 257 | + | tab.tabBar = self | |
| 258 | + | tab:setId('tab') | |
| 259 | + | tab:setDraggable(self.tabsMoveable) | |
| 260 | + | tab:setText(text) | |
| 261 | + | tab:setWidth(tab:getTextSize().width + tab:getPaddingLeft() + tab:getPaddingRight()) | |
| 262 | + | tab.menuCallback = menuCallback or nil | |
| 263 | + | tab.onClick = onTabClick | |
| 264 | + | tab.onMousePress = onTabMousePress | |
| 265 | + | tab.onDragEnter = onTabDragEnter | |
| 266 | + | tab.onDragLeave = onTabDragLeave | |
| 267 | + | tab.onDragMove = onTabDragMove | |
| 268 | + | tab.onDestroy = function() tab.tabPanel:destroy() end | |
| 269 | + | ||
| 270 | + | if #self.tabs == 0 then | |
| 271 | + | self:selectTab(tab) | |
| 272 | + | tab:setMarginLeft(0) | |
| 273 | + | table.insert(self.tabs, tab) | |
| 274 | + | else | |
| 275 | + | local newMargin = self.tabSpacing * #self.tabs | |
| 276 | + | for i = 1, #self.tabs do | |
| 277 | + | newMargin = newMargin + self.tabs[i]:getWidth() | |
| 278 | + | end | |
| 279 | + | tab:setMarginLeft(newMargin) | |
| 280 | + | ||
| 281 | + | hideTabs(self, true, self.postTabs, tab:getWidth()) | |
| 282 | + | table.insert(self.tabs, tab) | |
| 283 | + | if #self.tabs == 1 then | |
| 284 | + | self:selectTab(tab) | |
| 285 | + | end | |
| 286 | + | updateMargins(self) | |
| 287 | + | end | |
| 288 | + | ||
| 289 | + | updateNavigation(self) | |
| 290 | + | return tab | |
| 291 | + | end | |
| 292 | + | ||
| 293 | + | -- Additional function to move the tab by lua | |
| 294 | + | function UIChatTabBar:moveTab(tab, units) | |
| 295 | + | local index = table.find(self.tabs, tab) | |
| 296 | + | if index == nil then return end | |
| 297 | + | ||
| 298 | + | local focus = false | |
| 299 | + | if self.currentTab == tab then | |
| 300 | + | self:selectPrevTab() | |
| 301 | + | focus = true | |
| 302 | + | end | |
| 303 | + | ||
| 304 | + | table.remove(self.tabs, index) | |
| 305 | + | ||
| 306 | + | local newIndex = math.min(#self.tabs+1, math.max(index + units, 1)) | |
| 307 | + | table.insert(self.tabs, newIndex, tab) | |
| 308 | + | if focus then self:selectTab(tab) end | |
| 309 | + | updateMargins(self) | |
| 310 | + | return newIndex | |
| 311 | + | end | |
| 312 | + | ||
| 313 | + | function UIChatTabBar:onStyleApply(styleName, styleNode) | |
| 314 | + | if styleNode['movable'] then | |
| 315 | + | self.tabsMoveable = styleNode['movable'] | |
| 316 | + | end | |
| 317 | + | if styleNode['tab-spacing'] then | |
| 318 | + | self:setTabSpacing(styleNode['tab-spacing']) | |
| 319 | + | end | |
| 320 | + | end | |
| 321 | + | ||
| 322 | + | function UIChatTabBar:removeTab(tab) | |
| 323 | + | local tabTables = {self.tabs, self.preTabs, self.postTabs} | |
| 324 | + | local index = nil | |
| 325 | + | local tabTable = nil | |
| 326 | + | for i = 1, #tabTables do | |
| 327 | + | index = table.find(tabTables[i], tab) | |
| 328 | + | if index ~= nil then | |
| 329 | + | tabTable = tabTables[i] | |
| 330 | + | break | |
| 331 | + | end | |
| 332 | + | end | |
| 333 | + | ||
| 334 | + | if tabTable == nil then | |
| 335 | + | return | |
| 336 | + | end | |
| 337 | + | ||
| 338 | + | if self.currentTab == tab then | |
| 339 | + | self:selectPrevTab() | |
| 340 | + | if #self.tabs == 1 then | |
| 341 | + | self.currentTab = nil | |
| 342 | + | end | |
| 343 | + | end | |
| 344 | + | table.remove(tabTable, index) | |
| 345 | + | if tab.blinkEvent then | |
| 346 | + | removeEvent(tab.blinkEvent) | |
| 347 | + | end | |
| 348 | + | tab:destroy() | |
| 349 | + | updateTabs(self) | |
| 350 | + | end | |
| 351 | + | ||
| 352 | + | function UIChatTabBar:getTab(text) | |
| 353 | + | for k,tab in pairs(self.tabs) do | |
| 354 | + | if tab:getText():lower() == text:lower() then | |
| 355 | + | return tab | |
| 356 | + | end | |
| 357 | + | end | |
| 358 | + | for k,tab in pairs(self.preTabs) do | |
| 359 | + | if tab:getText():lower() == text:lower() then | |
| 360 | + | return tab | |
| 361 | + | end | |
| 362 | + | end | |
| 363 | + | for k,tab in pairs(self.postTabs) do | |
| 364 | + | if tab:getText():lower() == text:lower() then | |
| 365 | + | return tab | |
| 366 | + | end | |
| 367 | + | end | |
| 368 | + | end | |
| 369 | + | ||
| 370 | + | function UIChatTabBar:selectTab(tab) | |
| 371 | + | if self.currentTab == tab then return end | |
| 372 | + | if self.contentWidget then | |
| 373 | + | local selectedWidget = self.contentWidget:getLastChild() | |
| 374 | + | if selectedWidget and selectedWidget.isTab then | |
| 375 | + | self.contentWidget:removeChild(selectedWidget) | |
| 376 | + | end | |
| 377 | + | self.contentWidget:addChild(tab.tabPanel) | |
| 378 | + | tab.tabPanel:fill('parent') | |
| 379 | + | end | |
| 380 | + | ||
| 381 | + | if self.currentTab then | |
| 382 | + | self.currentTab:setChecked(false) | |
| 383 | + | end | |
| 384 | + | signalcall(self.onTabChange, self, tab) | |
| 385 | + | self.currentTab = tab | |
| 386 | + | tab:setChecked(true) | |
| 387 | + | tab:setOn(false) | |
| 388 | + | tab.blinking = false | |
| 389 | + | ||
| 390 | + | local parent = tab:getParent() | |
| 391 | + | parent:focusChild(tab, MouseFocusReason) | |
| 392 | + | updateNavigation(self) | |
| 393 | + | end | |
| 394 | + | ||
| 395 | + | function UIChatTabBar:selectNextTab() | |
| 396 | + | if self.currentTab == nil then | |
| 397 | + | return | |
| 398 | + | end | |
| 399 | + | ||
| 400 | + | local index = table.find(self.tabs, self.currentTab) | |
| 401 | + | if index == nil then | |
| 402 | + | return | |
| 403 | + | end | |
| 404 | + | ||
| 405 | + | local newIndex = index + 1 | |
| 406 | + | if newIndex > #self.tabs then | |
| 407 | + | if #self.postTabs > 0 then | |
| 408 | + | local widget = showPostTab(self) | |
| 409 | + | self:selectTab(widget) | |
| 410 | + | else | |
| 411 | + | if #self.preTabs > 0 then | |
| 412 | + | for i = 1, #self.preTabs do | |
| 413 | + | showPreTab(self) | |
| 414 | + | end | |
| 415 | + | end | |
| 416 | + | ||
| 417 | + | self:selectTab(self.tabs[1]) | |
| 418 | + | end | |
| 419 | + | updateTabs(self) | |
| 420 | + | return | |
| 421 | + | end | |
| 422 | + | ||
| 423 | + | local nextTab = self.tabs[newIndex] | |
| 424 | + | if not nextTab then | |
| 425 | + | return | |
| 426 | + | end | |
| 427 | + | ||
| 428 | + | self:selectTab(nextTab) | |
| 429 | + | end | |
| 430 | + | ||
| 431 | + | function UIChatTabBar:selectPrevTab() | |
| 432 | + | if self.currentTab == nil then | |
| 433 | + | return | |
| 434 | + | end | |
| 435 | + | ||
| 436 | + | local index = table.find(self.tabs, self.currentTab) | |
| 437 | + | if index == nil then | |
| 438 | + | return | |
| 439 | + | end | |
| 440 | + | ||
| 441 | + | local newIndex = index - 1 | |
| 442 | + | if newIndex <= 0 then | |
| 443 | + | if #self.preTabs > 0 then | |
| 444 | + | local widget = showPreTab(self) | |
| 445 | + | self:selectTab(widget) | |
| 446 | + | else | |
| 447 | + | if #self.postTabs > 0 then | |
| 448 | + | for i = 1, #self.postTabs do | |
| 449 | + | showPostTab(self) | |
| 450 | + | end | |
| 451 | + | end | |
| 452 | + | ||
| 453 | + | self:selectTab(self.tabs[#self.tabs]) | |
| 454 | + | end | |
| 455 | + | updateTabs(self) | |
| 456 | + | return | |
| 457 | + | end | |
| 458 | + | ||
| 459 | + | local prevTab = self.tabs[newIndex] | |
| 460 | + | if not prevTab then | |
| 461 | + | return | |
| 462 | + | end | |
| 463 | + | ||
| 464 | + | self:selectTab(prevTab) | |
| 465 | + | end | |
| 466 | + | ||
| 467 | + | function UIChatTabBar:blinkTab(tab) | |
| 468 | + | if tab:isChecked() then return end | |
| 469 | + | if tab.blinking then return end | |
| 470 | + | tab.blinking = true | |
| 471 | + | tabBlink(tab, 0, 2, 800) | |
| 472 | + | end | |
| 473 | + | ||
| 474 | + | function UIChatTabBar:getTabPanel(tab) | |
| 475 | + | return tab.tabPanel | |
| 476 | + | end | |
| 477 | + | ||
| 478 | + | function UIChatTabBar:getCurrentTabPanel() | |
| 479 | + | if self.currentTab then | |
| 480 | + | return self.currentTab.tabPanel | |
| 481 | + | end | |
| 482 | + | end | |
| 483 | + | ||
| 484 | + | function UIChatTabBar:getCurrentTab() | |
| 485 | + | return self.currentTab | |
| 486 | + | end | |
| 487 | + | ||
| 488 | + | function UIChatTabBar:setNavigation(prevButton, nextButton) | |
| 489 | + | self.prevNavigation = prevButton | |
| 490 | + | self.nextNavigation = nextButton | |
| 491 | + | ||
| 492 | + | if self.prevNavigation then | |
| 493 | + | self.prevNavigation.onClick = function() self:selectPrevTab() end | |
| 494 | + | end | |
| 495 | + | if self.nextNavigation then | |
| 496 | + | self.nextNavigation.onClick = function() self:selectNextTab() end | |
| 497 | + | end | |
| 498 | + | updateNavigation(self) | |
| 499 | + | end |
| @@ -0,0 +1,62 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIColorWheel = extends(UIButton, "UIColorWheel") | |
| 3 | + | ||
| 4 | + | local epsilon = 1E-12 | |
| 5 | + | ||
| 6 | + | function UIColorWheel.create() | |
| 7 | + | local btn = UIColorWheel.internalCreate() | |
| 8 | + | return btn | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | function UIColorWheel:onStyleApply(styleName, styleNode) | |
| 12 | + | for name,value in pairs(styleNode) do | |
| 13 | + | if name == 'num-slices' then | |
| 14 | + | self.numSlices = tonumber(value) | |
| 15 | + | elseif name == 'num-lines' then | |
| 16 | + | self.numRows = tonumber(value) | |
| 17 | + | end | |
| 18 | + | end | |
| 19 | + | end | |
| 20 | + | ||
| 21 | + | function UIColorWheel.setNumSlices(self, value) | |
| 22 | + | self.numSlices = tonumber(value) | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | function UIColorWheel.setNumRows(self, value) | |
| 26 | + | self.numRows = tonumber(value) | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | function UIColorWheel.getColorByMousePos(self) | |
| 30 | + | if not self.numSlices or not self.numRows or self.numSlices <= 0 or self.numRows <= 0 then | |
| 31 | + | return | |
| 32 | + | end | |
| 33 | + | ||
| 34 | + | -- define variables | |
| 35 | + | local anglePerSlice = 360 / self.numSlices | |
| 36 | + | ||
| 37 | + | local mousePos = g_window.getMousePosition() | |
| 38 | + | local center = { x = self:getWidth() / 2, y = self:getHeight() / 2} | |
| 39 | + | local right = { x = 1, y = 0 } | |
| 40 | + | ||
| 41 | + | local localMousePos = {x = mousePos.x - self:getPosition().x - center.x, y = mousePos.y - self:getPosition().y - center.y} | |
| 42 | + | localMousePos.x = localMousePos.x / center.x | |
| 43 | + | localMousePos.y = localMousePos.y / center.y | |
| 44 | + | ||
| 45 | + | if getMagnitude(localMousePos) > 1 then | |
| 46 | + | return self.colorIndex | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | -- find angle to find slice | |
| 50 | + | local angle = getAngle(localMousePos, right) | |
| 51 | + | local sliceIndex = math.floor(angle / anglePerSlice) | |
| 52 | + | ||
| 53 | + | local rowIndex = math.floor(getMagnitude(localMousePos) * self.numRows) | |
| 54 | + | ||
| 55 | + | self.colorIndex = rowIndex * self.numSlices + sliceIndex | |
| 56 | + | ||
| 57 | + | return self.colorIndex | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | function UIColorWheel.getColor(self) | |
| 61 | + | return self.colorIndex | |
| 62 | + | end |
| @@ -0,0 +1,314 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UICreatureButton = extends(UIWidget, "UICreatureButton") | |
| 3 | + | ||
| 4 | + | local CreatureButtonColors = { | |
| 5 | + | onIdle = {notHovered = '#888888', hovered = '#FFFFFF' }, | |
| 6 | + | onTargeted = {notHovered = '#FF0000', hovered = '#FF8888' }, | |
| 7 | + | onFollowed = {notHovered = '#4CBBFF', hovered = '#93D6FF' } | |
| 8 | + | } | |
| 9 | + | ||
| 10 | + | local FightStanceColors = { | |
| 11 | + | [FightOffensive] = {notHovered = '#FF0000', hovered = '#FF8888' }, | |
| 12 | + | [FightBalanced] = {notHovered = '#FFFF00', hovered = '#FFFF8B' }, | |
| 13 | + | [FightDefensive] = {notHovered = '#00FF00', hovered = '#88FF88' }, | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | local LifeBarColors = {} -- Must be sorted by percentAbove | |
| 17 | + | table.insert(LifeBarColors, {percentAbove = 92, color = '#00BC00' } ) | |
| 18 | + | table.insert(LifeBarColors, {percentAbove = 60, color = '#50A150' } ) | |
| 19 | + | table.insert(LifeBarColors, {percentAbove = 30, color = '#A1A100' } ) | |
| 20 | + | table.insert(LifeBarColors, {percentAbove = 8, color = '#BF0A0A' } ) | |
| 21 | + | table.insert(LifeBarColors, {percentAbove = 3, color = '#910F0F' } ) | |
| 22 | + | table.insert(LifeBarColors, {percentAbove = -1, color = '#850C0C' } ) | |
| 23 | + | ||
| 24 | + | local ThreatMeterColors = { | |
| 25 | + | { percentAbove = 67, color = '#ed1c24' }, | |
| 26 | + | { percentAbove = 33, color = '#ff7f27' }, | |
| 27 | + | { percentAbove = -1, color = '#22b14c' }, | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | function UICreatureButton.create() | |
| 31 | + | local button = UICreatureButton.internalCreate() | |
| 32 | + | button:setFocusable(false) | |
| 33 | + | button.creature = nil | |
| 34 | + | button.creatureName = '' | |
| 35 | + | button.isHovered = false | |
| 36 | + | button.healthPercent = 0 | |
| 37 | + | button.manaPercent = 0 | |
| 38 | + | button.threatPercent = 0 | |
| 39 | + | ||
| 40 | + | button.manaBar = false | |
| 41 | + | button.threatBar = false | |
| 42 | + | return button | |
| 43 | + | end | |
| 44 | + | ||
| 45 | + | function UICreatureButton:setCreature(creature) | |
| 46 | + | self.creature = creature | |
| 47 | + | end | |
| 48 | + | ||
| 49 | + | function UICreatureButton:getCreature() | |
| 50 | + | return self.creature | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | function UICreatureButton:getCreatureId() | |
| 54 | + | return self.creature:getId() | |
| 55 | + | end | |
| 56 | + | ||
| 57 | + | function UICreatureButton:setup(creature, id) | |
| 58 | + | self.lifeBarWidget = self:getChildById('lifeBar') | |
| 59 | + | self.manaBarWidget = self:getChildById('manaBar') | |
| 60 | + | self.threatMeterWidget = self:getChildById('threatMeter') | |
| 61 | + | self.creatureWidget = self:getChildById('creature') | |
| 62 | + | self.labelWidget = self:getChildById('label') | |
| 63 | + | self.skullWidget = self:getChildById('skull') | |
| 64 | + | self.emblemWidget = self:getChildById('emblem') | |
| 65 | + | self.summonWidget = self:getChildById('summon') | |
| 66 | + | self.shieldWidget = self:getChildById('shield') | |
| 67 | + | end | |
| 68 | + | ||
| 69 | + | function UICreatureButton:update() | |
| 70 | + | local color = CreatureButtonColors.onIdle | |
| 71 | + | if self.creature == g_game.getAttackingCreature() then | |
| 72 | + | if self.creature:isNpc() then | |
| 73 | + | color = CreatureButtonColors.onFollowed | |
| 74 | + | else | |
| 75 | + | color = CreatureButtonColors.onTargeted | |
| 76 | + | end | |
| 77 | + | elseif self.creature == g_game.getFollowingCreature() then | |
| 78 | + | color = CreatureButtonColors.onFollowed | |
| 79 | + | end | |
| 80 | + | ||
| 81 | + | color = self.isHovered and color.hovered or color.notHovered | |
| 82 | + | ||
| 83 | + | if self.color == color then | |
| 84 | + | return | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | self.color = color | |
| 88 | + | ||
| 89 | + | if color ~= CreatureButtonColors.onIdle.notHovered then | |
| 90 | + | self.creatureWidget:setBorderWidth(1) | |
| 91 | + | self.creatureWidget:setBorderColor(color) | |
| 92 | + | self.labelWidget:setColor(color) | |
| 93 | + | else | |
| 94 | + | self.creatureWidget:setBorderWidth(0) | |
| 95 | + | self.labelWidget:setColor(color) | |
| 96 | + | end | |
| 97 | + | ||
| 98 | + | -- getHealingCreature not implemented in C++ bindings, skip yellow tint | |
| 99 | + | self.creatureWidget:setImageColor('white') | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | function UICreatureButton:creatureSetup(creature) | |
| 103 | + | if self.creature ~= creature then | |
| 104 | + | self.creature = creature | |
| 105 | + | self.creatureWidget:setCreature(creature) | |
| 106 | + | ||
| 107 | + | if self.creatureName ~= creature:getName() then | |
| 108 | + | self.creatureName = creature:getName() | |
| 109 | + | self.labelWidget:setText(creature:getName()) | |
| 110 | + | end | |
| 111 | + | ||
| 112 | + | if self.manaBar then | |
| 113 | + | self.manaBar = false | |
| 114 | + | self.manaPercent = 0 | |
| 115 | + | self.manaBarWidget:setPercent(0) | |
| 116 | + | self.manaBarWidget:setVisible(false) | |
| 117 | + | end | |
| 118 | + | ||
| 119 | + | if self.threatBar then | |
| 120 | + | self.threatBar = false | |
| 121 | + | self.threatPercent = 0 | |
| 122 | + | self.threatMeterWidget:setPercent(0) | |
| 123 | + | self.threatMeterWidget:setVisible(false) | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | if self.creature:isPlayer() and self.manaBarWidget then | |
| 127 | + | self:setHeight(25) | |
| 128 | + | self.manaBar = true | |
| 129 | + | self.manaPercent = 0 | |
| 130 | + | self.manaBarWidget:setPercent(0) | |
| 131 | + | self.manaBarWidget:setVisible(true) | |
| 132 | + | elseif false then -- hasThreatMeter() not implemented in C++ bindings | |
| 133 | + | if self.threatMeterWidget then | |
| 134 | + | self:setHeight(25) | |
| 135 | + | self.threatBar = true | |
| 136 | + | self.threatPercent = 0 | |
| 137 | + | self.threatMeterWidget:setPercent(0) | |
| 138 | + | self.threatMeterWidget:setVisible(true) | |
| 139 | + | end | |
| 140 | + | else | |
| 141 | + | self:setHeight(20) | |
| 142 | + | end | |
| 143 | + | end | |
| 144 | + | ||
| 145 | + | self:updateLifeBarPercent() | |
| 146 | + | self:updateManaBarPercent() | |
| 147 | + | self:updateThreatMeterPercent() | |
| 148 | + | self:updateSkull() | |
| 149 | + | self:updateEmblem() | |
| 150 | + | self:updateSummon() | |
| 151 | + | self:updateShield() | |
| 152 | + | self:update() | |
| 153 | + | end | |
| 154 | + | ||
| 155 | + | function UICreatureButton:updateSkull() | |
| 156 | + | if not self.creature then | |
| 157 | + | return | |
| 158 | + | end | |
| 159 | + | ||
| 160 | + | local skullId = self.creature:getSkull() | |
| 161 | + | if skullId == self.skullId then | |
| 162 | + | return | |
| 163 | + | end | |
| 164 | + | ||
| 165 | + | self.skullId = skullId | |
| 166 | + | ||
| 167 | + | if skullId ~= SkullNone and self.skullWidget then | |
| 168 | + | self.skullWidget:setWidth(self.skullWidget:getHeight()) | |
| 169 | + | local imagePath = getSkullImagePath(skullId) | |
| 170 | + | self.skullWidget:setImageSource(imagePath) | |
| 171 | + | self.skullWidget:setMarginLeft(5) | |
| 172 | + | elseif self.skullWidget then | |
| 173 | + | self.skullWidget:setWidth(0) | |
| 174 | + | end | |
| 175 | + | end | |
| 176 | + | ||
| 177 | + | function UICreatureButton:updateEmblem() | |
| 178 | + | if not self.creature then | |
| 179 | + | return | |
| 180 | + | end | |
| 181 | + | ||
| 182 | + | local emblemId = self.creature:getEmblem() | |
| 183 | + | if emblemId == self.emblemId then | |
| 184 | + | return | |
| 185 | + | end | |
| 186 | + | ||
| 187 | + | self.emblemId = emblemId | |
| 188 | + | ||
| 189 | + | if emblemId ~= EmblemNone and self.emblemWidget then | |
| 190 | + | self.emblemWidget:setWidth(self.emblemWidget:getHeight()) | |
| 191 | + | local imagePath = getEmblemImagePath(emblemId) | |
| 192 | + | self.emblemWidget:setImageSource(imagePath) | |
| 193 | + | self.emblemWidget:setMarginLeft(5) | |
| 194 | + | elseif self.emblemWidget then | |
| 195 | + | self.emblemWidget:setWidth(0) | |
| 196 | + | self.emblemWidget:setMarginLeft(0) | |
| 197 | + | end | |
| 198 | + | end | |
| 199 | + | ||
| 200 | + | function UICreatureButton:updateSummon(iconId) | |
| 201 | + | if not self.creature then | |
| 202 | + | return | |
| 203 | + | end | |
| 204 | + | ||
| 205 | + | local iconId = self.creature:getType() | |
| 206 | + | if iconId == self.iconId then | |
| 207 | + | return | |
| 208 | + | end | |
| 209 | + | ||
| 210 | + | self.iconId = iconId | |
| 211 | + | ||
| 212 | + | if iconId >= CreatureTypeSummonOwn and self.summonWidget then | |
| 213 | + | self.summonWidget:setWidth(self.summonWidget:getHeight()) | |
| 214 | + | local imagePath = getTypeImagePath(iconId) | |
| 215 | + | self.summonWidget:setImageSource(imagePath) | |
| 216 | + | self.summonWidget:setMarginLeft(7) | |
| 217 | + | elseif self.summonWidget then | |
| 218 | + | self.summonWidget:setWidth(0) | |
| 219 | + | self.summonWidget:setMarginLeft(0) | |
| 220 | + | end | |
| 221 | + | end | |
| 222 | + | ||
| 223 | + | function UICreatureButton:updateShield(shieldId) | |
| 224 | + | if not self.creature then | |
| 225 | + | return | |
| 226 | + | end | |
| 227 | + | ||
| 228 | + | local shieldId = self.creature:getShield() | |
| 229 | + | if shieldId == self.shieldId then | |
| 230 | + | return | |
| 231 | + | end | |
| 232 | + | ||
| 233 | + | self.shieldId = shieldId | |
| 234 | + | ||
| 235 | + | if shieldId ~= ShieldNone and self.shieldWidget then | |
| 236 | + | self.shieldWidget:setWidth(self.shieldWidget:getHeight()) | |
| 237 | + | local imagePath = getShieldImagePathAndBlink(shieldId) | |
| 238 | + | self.shieldWidget:setImageSource(imagePath) | |
| 239 | + | self.shieldWidget:setMarginLeft(5) | |
| 240 | + | elseif self.shieldWidget then | |
| 241 | + | self.shieldWidget:setWidth(0) | |
| 242 | + | self.shieldWidget:setMarginLeft(0) | |
| 243 | + | end | |
| 244 | + | end | |
| 245 | + | ||
| 246 | + | function UICreatureButton:updateLifeBarPercent() | |
| 247 | + | if not self.creature then | |
| 248 | + | return | |
| 249 | + | end | |
| 250 | + | ||
| 251 | + | local percent = self.creature:getHealthPercent() | |
| 252 | + | if self.healthPercent == percent then | |
| 253 | + | return | |
| 254 | + | end | |
| 255 | + | ||
| 256 | + | self.healthPercent = percent | |
| 257 | + | if self.lifeBarWidget then | |
| 258 | + | self.lifeBarWidget:setPercent(percent) | |
| 259 | + | ||
| 260 | + | local color | |
| 261 | + | for i, v in pairs(LifeBarColors) do | |
| 262 | + | if percent > v.percentAbove then | |
| 263 | + | color = v.color | |
| 264 | + | break | |
| 265 | + | end | |
| 266 | + | end | |
| 267 | + | ||
| 268 | + | self.lifeBarWidget:setBackgroundColor(color) | |
| 269 | + | end | |
| 270 | + | end | |
| 271 | + | ||
| 272 | + | function UICreatureButton:updateManaBarPercent() | |
| 273 | + | if not self.creature or not self.manaBar then | |
| 274 | + | return | |
| 275 | + | end | |
| 276 | + | ||
| 277 | + | local percent = self.creature:getManaPercent() | |
| 278 | + | if self.manaPercent == percent then | |
| 279 | + | return | |
| 280 | + | end | |
| 281 | + | ||
| 282 | + | self.manaPercent = percent | |
| 283 | + | if self.manaBarWidget then | |
| 284 | + | self.manaBarWidget:setPercent(percent) | |
| 285 | + | self.manaBarWidget:setBackgroundColor('#ba55d3') | |
| 286 | + | end | |
| 287 | + | end | |
| 288 | + | ||
| 289 | + | function UICreatureButton:updateThreatMeterPercent() | |
| 290 | + | if not self.creature or not self.threatBar then | |
| 291 | + | return | |
| 292 | + | end | |
| 293 | + | ||
| 294 | + | -- getThreatMeterPercent() not implemented in C++ bindings | |
| 295 | + | local percent = 0 | |
| 296 | + | if self.threatPercent == percent then | |
| 297 | + | return | |
| 298 | + | end | |
| 299 | + | ||
| 300 | + | self.threatPercent = percent | |
| 301 | + | if self.threatMeterWidget then | |
| 302 | + | self.threatMeterWidget:setPercent(percent) | |
| 303 | + | ||
| 304 | + | local color | |
| 305 | + | for i, v in pairs(ThreatMeterColors) do | |
| 306 | + | if percent > v.percentAbove then | |
| 307 | + | color = v.color | |
| 308 | + | break | |
| 309 | + | end | |
| 310 | + | end | |
| 311 | + | ||
| 312 | + | self.threatMeterWidget:setBackgroundColor(color) | |
| 313 | + | end | |
| 314 | + | end |
| @@ -0,0 +1,60 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIHealthBar = extends(UIWidget, "UIHealthBar") | |
| 3 | + | ||
| 4 | + | function UIHealthBar.create() | |
| 5 | + | local bar = UIHealthBar.internalCreate() | |
| 6 | + | bar:setPhantom(false) | |
| 7 | + | bar:setAutoFocusPolicy(AutoFocusFirst) | |
| 8 | + | bar.hasBorder = true | |
| 9 | + | return bar | |
| 10 | + | end | |
| 11 | + | ||
| 12 | + | function UIHealthBar.setHealthBorder(self, value) | |
| 13 | + | self.hasBorder = value | |
| 14 | + | end | |
| 15 | + | ||
| 16 | + | -- used for healthbars, manabars and resourcebars (check game_healthinfo module for examples) | |
| 17 | + | function UIHealthBar.setPercentage(self, value, maxValue) | |
| 18 | + | -- the extra resize code is implemented to work in junction with uiwidget.lua:resizeElements | |
| 19 | + | local percentage = math.min(100, math.floor((value/maxValue)*100)) | |
| 20 | + | local widget = self:recursiveGetChildById('bar') | |
| 21 | + | local icon = self:recursiveGetChildById('icon') | |
| 22 | + | ||
| 23 | + | widget:setImageWidth(math.ceil((widget:getWidth() - icon:getWidth() + 13) * percentage / 100)) | |
| 24 | + | ||
| 25 | + | widget:setVisible(widget:getImageWidth() > 0) | |
| 26 | + | ||
| 27 | + | local text = self:recursiveGetChildById('text') | |
| 28 | + | if text then | |
| 29 | + | --text:setText(getFormattedShortNumber(value) .. '/' .. getFormattedShortNumber(maxValue)) | |
| 30 | + | text:setText(value .. '/' .. maxValue) | |
| 31 | + | end | |
| 32 | + | ||
| 33 | + | local resourceText = self:recursiveGetChildById('resourceText') | |
| 34 | + | if resourceText then | |
| 35 | + | resourceText:setText(value) | |
| 36 | + | end | |
| 37 | + | ||
| 38 | + | --[[ | |
| 39 | + | local indicator = self:recursiveGetChildById('indicator') | |
| 40 | + | if indicator then | |
| 41 | + | indicator:setMarginRight(-2*resizeFactor + (size.width - size.newWidth)) | |
| 42 | + | end | |
| 43 | + | ||
| 44 | + | ||
| 45 | + | local anim = self:recursiveGetChildById('orb') | |
| 46 | + | local backGlow = self:recursiveGetChildById('background-glow') | |
| 47 | + | if anim and backGlow then | |
| 48 | + | if percentage == 100 then | |
| 49 | + | -- anim:play() | |
| 50 | + | backGlow:setOpacity(0) | |
| 51 | + | backGlow:show() | |
| 52 | + | g_effects.startFadeInOut(backGlow, 0, 50, 0.05) | |
| 53 | + | else | |
| 54 | + | -- anim:stop() | |
| 55 | + | g_effects.stopFadeInOut(backGlow) | |
| 56 | + | backGlow:hide() | |
| 57 | + | end | |
| 58 | + | end | |
| 59 | + | ]] | |
| 60 | + | end |
| @@ -0,0 +1,137 @@ | |||
| 1 | + | function UIItem:onDragEnter(mousePos) | |
| 2 | + | if self:isVirtual() then return false end | |
| 3 | + | ||
| 4 | + | local item = self:getItem() | |
| 5 | + | if not item then return false end | |
| 6 | + | ||
| 7 | + | self:setBorderWidth(1) | |
| 8 | + | self.currentDragThing = item | |
| 9 | + | g_mouse.pushCursor('target') | |
| 10 | + | return true | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | function UIItem:onDragLeave(droppedWidget, mousePos) | |
| 14 | + | if self:isVirtual() then return false end | |
| 15 | + | self.currentDragThing = nil | |
| 16 | + | g_mouse.popCursor('target') | |
| 17 | + | self:setBorderWidth(0) | |
| 18 | + | self.hoveredWho = nil | |
| 19 | + | return true | |
| 20 | + | end | |
| 21 | + | ||
| 22 | + | function UIItem:onDrop(widget, mousePos, forced) | |
| 23 | + | if not self:canAcceptDrop(widget, mousePos) and not forced then return false end | |
| 24 | + | ||
| 25 | + | local item = widget.currentDragThing | |
| 26 | + | if not item or not item:isItem() then return false end | |
| 27 | + | ||
| 28 | + | if self.selectable then | |
| 29 | + | if item:isPickupable() then | |
| 30 | + | self:setItem(Item.create(item:getId(), item:getCountOrSubType())) | |
| 31 | + | return true | |
| 32 | + | end | |
| 33 | + | return false | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | local toPos = self.position | |
| 37 | + | ||
| 38 | + | local itemPos = item:getPosition() | |
| 39 | + | if itemPos.x == toPos.x and itemPos.y == toPos.y and itemPos.z == toPos.z then return false end | |
| 40 | + | ||
| 41 | + | if item:getCount() > 1 then | |
| 42 | + | modules.game_interface.moveStackableItem(item, toPos) | |
| 43 | + | else | |
| 44 | + | g_game.move(item, toPos, 1) | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | self:setBorderWidth(0) | |
| 48 | + | return true | |
| 49 | + | end | |
| 50 | + | ||
| 51 | + | function UIItem:onDestroy() | |
| 52 | + | if self == g_ui.getDraggingWidget() and self.hoveredWho then | |
| 53 | + | self.hoveredWho:setBorderWidth(0) | |
| 54 | + | end | |
| 55 | + | ||
| 56 | + | if self.hoveredWho then | |
| 57 | + | self.hoveredWho = nil | |
| 58 | + | end | |
| 59 | + | end | |
| 60 | + | ||
| 61 | + | function UIItem:onHoverChange(hovered) | |
| 62 | + | UIWidget.onHoverChange(self, hovered) | |
| 63 | + | ||
| 64 | + | if self:isVirtual() or not self:isDraggable() then return end | |
| 65 | + | ||
| 66 | + | local draggingWidget = g_ui.getDraggingWidget() | |
| 67 | + | if draggingWidget and self ~= draggingWidget then | |
| 68 | + | local gotMap = draggingWidget:getClassName() == 'UIGameMap' | |
| 69 | + | local gotItem = draggingWidget:getClassName() == 'UIItem' and not draggingWidget:isVirtual() | |
| 70 | + | if hovered and (gotItem or gotMap) then | |
| 71 | + | self:setBorderWidth(1) | |
| 72 | + | draggingWidget.hoveredWho = self | |
| 73 | + | else | |
| 74 | + | self:setBorderWidth(0) | |
| 75 | + | draggingWidget.hoveredWho = nil | |
| 76 | + | end | |
| 77 | + | end | |
| 78 | + | end | |
| 79 | + | ||
| 80 | + | function UIItem:onMouseRelease(mousePosition, mouseButton) | |
| 81 | + | if self.cancelNextRelease then | |
| 82 | + | self.cancelNextRelease = false | |
| 83 | + | return true | |
| 84 | + | end | |
| 85 | + | ||
| 86 | + | if self:isVirtual() then return false end | |
| 87 | + | ||
| 88 | + | local item = self:getItem() | |
| 89 | + | if not item or not self:containsPoint(mousePosition) then return false end | |
| 90 | + | ||
| 91 | + | if modules.client_options.getOption('classicControl') and not g_app.isMobile() and | |
| 92 | + | ((g_mouse.isPressed(MouseLeftButton) and mouseButton == MouseRightButton) or | |
| 93 | + | (g_mouse.isPressed(MouseRightButton) and mouseButton == MouseLeftButton)) then | |
| 94 | + | g_game.look(item) | |
| 95 | + | self.cancelNextRelease = true | |
| 96 | + | return true | |
| 97 | + | elseif modules.game_interface.processMouseAction(mousePosition, mouseButton, nil, item, item, nil, nil) then | |
| 98 | + | return true | |
| 99 | + | end | |
| 100 | + | return false | |
| 101 | + | end | |
| 102 | + | ||
| 103 | + | function UIItem:canAcceptDrop(widget, mousePos) | |
| 104 | + | if not self.selectable and (self:isVirtual() or not self:isDraggable()) then return false end | |
| 105 | + | if not widget or not widget.currentDragThing then return false end | |
| 106 | + | ||
| 107 | + | local children = rootWidget:recursiveGetChildrenByPos(mousePos) | |
| 108 | + | for i=1,#children do | |
| 109 | + | local child = children[i] | |
| 110 | + | if child == self then | |
| 111 | + | return true | |
| 112 | + | elseif not child:isPhantom() then | |
| 113 | + | return false | |
| 114 | + | end | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | error('Widget ' .. self:getId() .. ' not in drop list.') | |
| 118 | + | return false | |
| 119 | + | end | |
| 120 | + | ||
| 121 | + | function UIItem:onClick(mousePos) | |
| 122 | + | if not self.selectable or not self.editable then | |
| 123 | + | return | |
| 124 | + | end | |
| 125 | + | ||
| 126 | + | if modules.game_itemselector then | |
| 127 | + | modules.game_itemselector.show(self) | |
| 128 | + | end | |
| 129 | + | end | |
| 130 | + | ||
| 131 | + | function UIItem:onItemChange() | |
| 132 | + | local tooltip = nil | |
| 133 | + | if self:getItem() and self:getItem():getTooltip():len() > 0 then | |
| 134 | + | tooltip = self:getItem():getTooltip() | |
| 135 | + | end | |
| 136 | + | self:setTooltip(tooltip) | |
| 137 | + | end |
| @@ -0,0 +1,35 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIMenuPanel = extends(UIWidget, "UIMenuPanel") | |
| 3 | + | ||
| 4 | + | function UIMenuPanel.create() | |
| 5 | + | local panel = UIMenuPanel.internalCreate() | |
| 6 | + | ||
| 7 | + | --[[ | |
| 8 | + | connect(rootWidget, { | |
| 9 | + | onUIResize = function() panel:onUIResize(styleName, styleNode) end | |
| 10 | + | }) | |
| 11 | + | ]] | |
| 12 | + | return panel | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function UIMenuPanel:onUIResize(styleName, styleNode) | |
| 16 | + | if self.autoResize == false then | |
| 17 | + | return | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | if not self.originalWidth then | |
| 21 | + | self.originalWidth = self:getWidth() | |
| 22 | + | self.widthRatio = self.originalWidth/(1920-152) | |
| 23 | + | end | |
| 24 | + | ||
| 25 | + | local width = g_window:getWidth() - 152 | |
| 26 | + | self:setWidth(self.widthRatio * width) | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | function UIMenuPanel:onStyleApply(styleName, styleNode) | |
| 30 | + | for name,value in pairs(styleNode) do | |
| 31 | + | if name == 'auto-resize' then | |
| 32 | + | self.autoResize = value | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | end |
| @@ -0,0 +1,356 @@ | |||
| 1 | + | -- Left-click teleport for gamemasters. The server tells us on login whether | |
| 2 | + | -- this character may use it (server: data/scripts/customs/admin_minimap_teleport.lua). | |
| 3 | + | local ADMIN_MINIMAP_TELEPORT_OPCODE = 215 | |
| 4 | + | local adminMinimapTeleportEnabled = false | |
| 5 | + | local adminMinimapTeleportRegistered = false | |
| 6 | + | ||
| 7 | + | local function sendAdminMinimapTeleport(pos) | |
| 8 | + | local protocolGame = g_game.getProtocolGame() | |
| 9 | + | if not protocolGame or not pos then | |
| 10 | + | return false | |
| 11 | + | end | |
| 12 | + | ||
| 13 | + | protocolGame:sendExtendedOpcode(ADMIN_MINIMAP_TELEPORT_OPCODE, json.encode({ | |
| 14 | + | action = 'teleport', | |
| 15 | + | position = { x = pos.x, y = pos.y, z = pos.z } | |
| 16 | + | })) | |
| 17 | + | return true | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | local function onAdminMinimapTeleportOpcode(protocol, opcode, buffer) | |
| 21 | + | local ok, status = pcall(json.decode, buffer) | |
| 22 | + | if not ok or type(status) ~= 'table' or status.action ~= 'access' then | |
| 23 | + | return | |
| 24 | + | end | |
| 25 | + | ||
| 26 | + | adminMinimapTeleportEnabled = status.enabled == true | |
| 27 | + | end | |
| 28 | + | ||
| 29 | + | function UIMinimap:onCreate() | |
| 30 | + | self.autowalk = true | |
| 31 | + | end | |
| 32 | + | ||
| 33 | + | function UIMinimap:onSetup() | |
| 34 | + | if not adminMinimapTeleportRegistered then | |
| 35 | + | -- pcall: after a gamelib reload the opcode is still registered | |
| 36 | + | adminMinimapTeleportRegistered = pcall(ProtocolGame.registerExtendedOpcode, | |
| 37 | + | ADMIN_MINIMAP_TELEPORT_OPCODE, onAdminMinimapTeleportOpcode) | |
| 38 | + | end | |
| 39 | + | ||
| 40 | + | self.flagWindow = nil | |
| 41 | + | self.flags = {} | |
| 42 | + | self.alternatives = {} | |
| 43 | + | self.onAddAutomapFlag = function(pos, icon, description) self:addFlag(pos, icon, description) end | |
| 44 | + | self.onRemoveAutomapFlag = function(pos, icon, description) self:removeFlag(pos, icon, description) end | |
| 45 | + | connect(g_game, { | |
| 46 | + | onAddAutomapFlag = self.onAddAutomapFlag, | |
| 47 | + | onRemoveAutomapFlag = self.onRemoveAutomapFlag, | |
| 48 | + | }) | |
| 49 | + | end | |
| 50 | + | ||
| 51 | + | function UIMinimap:onDestroy() | |
| 52 | + | for _,widget in pairs(self.alternatives) do | |
| 53 | + | widget:destroy() | |
| 54 | + | end | |
| 55 | + | self.alternatives = {} | |
| 56 | + | disconnect(g_game, { | |
| 57 | + | onAddAutomapFlag = self.onAddAutomapFlag, | |
| 58 | + | onRemoveAutomapFlag = self.onRemoveAutomapFlag, | |
| 59 | + | }) | |
| 60 | + | self:destroyFlagWindow() | |
| 61 | + | self.flags = {} | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function UIMinimap:onVisibilityChange() | |
| 65 | + | if not self:isVisible() then | |
| 66 | + | self:destroyFlagWindow() | |
| 67 | + | end | |
| 68 | + | end | |
| 69 | + | ||
| 70 | + | function UIMinimap:onCameraPositionChange(cameraPos) | |
| 71 | + | if self.cross then | |
| 72 | + | self:setCrossPosition(self.cross.pos) | |
| 73 | + | end | |
| 74 | + | end | |
| 75 | + | ||
| 76 | + | function UIMinimap:hideFloor() | |
| 77 | + | self.floorUpWidget:hide() | |
| 78 | + | self.floorDownWidget:hide() | |
| 79 | + | end | |
| 80 | + | ||
| 81 | + | function UIMinimap:hideZoom() | |
| 82 | + | self.zoomInWidget:hide() | |
| 83 | + | self.zoomOutWidget:hide() | |
| 84 | + | end | |
| 85 | + | ||
| 86 | + | function UIMinimap:disableAutoWalk() | |
| 87 | + | self.autowalk = false | |
| 88 | + | end | |
| 89 | + | ||
| 90 | + | function UIMinimap:load() | |
| 91 | + | local settings = g_settings.getNode('Minimap') | |
| 92 | + | if settings then | |
| 93 | + | if settings.flags then | |
| 94 | + | for _,flag in pairs(settings.flags) do | |
| 95 | + | self:addFlag(flag.position, flag.icon, flag.description) | |
| 96 | + | end | |
| 97 | + | end | |
| 98 | + | self:setZoom(settings.zoom) | |
| 99 | + | end | |
| 100 | + | end | |
| 101 | + | ||
| 102 | + | function UIMinimap:save() | |
| 103 | + | local settings = { flags={} } | |
| 104 | + | for _,flag in pairs(self.flags) do | |
| 105 | + | if not flag.temporary then | |
| 106 | + | table.insert(settings.flags, { | |
| 107 | + | position = flag.pos, | |
| 108 | + | icon = flag.icon, | |
| 109 | + | description = flag.description, | |
| 110 | + | }) | |
| 111 | + | end | |
| 112 | + | end | |
| 113 | + | settings.zoom = self:getZoom() | |
| 114 | + | g_settings.setNode('Minimap', settings) | |
| 115 | + | end | |
| 116 | + | ||
| 117 | + | local function onFlagMouseRelease(widget, pos, button) | |
| 118 | + | if button == MouseRightButton then | |
| 119 | + | local menu = g_ui.createWidget('PopupMenu') | |
| 120 | + | menu:setGameMenu(true) | |
| 121 | + | menu:addOption(tr('Delete mark'), function() widget:destroy() end) | |
| 122 | + | menu:display(pos) | |
| 123 | + | return true | |
| 124 | + | end | |
| 125 | + | return false | |
| 126 | + | end | |
| 127 | + | ||
| 128 | + | function UIMinimap:setCrossPosition(pos) | |
| 129 | + | local cross = self.cross | |
| 130 | + | if not self.cross then | |
| 131 | + | cross = g_ui.createWidget('MinimapCross', self) | |
| 132 | + | cross:setIcon('/images/ui/windows/minimap/cross') | |
| 133 | + | self.cross = cross | |
| 134 | + | end | |
| 135 | + | ||
| 136 | + | pos.z = self:getCameraPosition().z | |
| 137 | + | cross.pos = pos | |
| 138 | + | if pos then | |
| 139 | + | self:centerInPosition(cross, pos) | |
| 140 | + | else | |
| 141 | + | cross:breakAnchors() | |
| 142 | + | end | |
| 143 | + | end | |
| 144 | + | ||
| 145 | + | function UIMinimap:addFlag(pos, icon, description, temporary) | |
| 146 | + | if not pos or not icon then return end | |
| 147 | + | local flag = self:getFlag(pos, icon, description) | |
| 148 | + | if flag or not icon then | |
| 149 | + | return | |
| 150 | + | end | |
| 151 | + | temporary = temporary or false | |
| 152 | + | ||
| 153 | + | flag = g_ui.createWidget('MinimapFlag') | |
| 154 | + | self:insertChild(1, flag) | |
| 155 | + | flag.pos = pos | |
| 156 | + | flag.description = description | |
| 157 | + | flag.icon = icon | |
| 158 | + | flag.temporary = temporary | |
| 159 | + | if type(tonumber(icon)) == 'number' then | |
| 160 | + | flag:setIcon('/images/ui/windows/minimap/flag' .. icon) | |
| 161 | + | else | |
| 162 | + | flag:setIcon(resolvepath(icon, 1)) | |
| 163 | + | end | |
| 164 | + | flag:setTooltip(description) | |
| 165 | + | flag.onMouseRelease = onFlagMouseRelease | |
| 166 | + | flag.onDestroy = function() table.removevalue(self.flags, flag) end | |
| 167 | + | table.insert(self.flags, flag) | |
| 168 | + | self:centerInPosition(flag, pos) | |
| 169 | + | end | |
| 170 | + | ||
| 171 | + | function UIMinimap:addAlternativeWidget(widget, pos, maxZoom) | |
| 172 | + | widget.pos = pos | |
| 173 | + | widget.maxZoom = maxZoom or 0 | |
| 174 | + | widget.minZoom = minZoom | |
| 175 | + | table.insert(self.alternatives, widget) | |
| 176 | + | end | |
| 177 | + | ||
| 178 | + | function UIMinimap:setAlternativeWidgetsVisible(show) | |
| 179 | + | local layout = self:getLayout() | |
| 180 | + | layout:disableUpdates() | |
| 181 | + | for _,widget in pairs(self.alternatives) do | |
| 182 | + | if show then | |
| 183 | + | self:insertChild(1, widget) | |
| 184 | + | self:centerInPosition(widget, widget.pos) | |
| 185 | + | else | |
| 186 | + | self:removeChild(widget) | |
| 187 | + | widget:destroy() | |
| 188 | + | end | |
| 189 | + | end | |
| 190 | + | layout:enableUpdates() | |
| 191 | + | layout:update() | |
| 192 | + | end | |
| 193 | + | ||
| 194 | + | function UIMinimap:onZoomChange(zoom) | |
| 195 | + | for _,widget in pairs(self.alternatives) do | |
| 196 | + | if (not widget.minZoom or widget.minZoom >= zoom) and widget.maxZoom <= zoom then | |
| 197 | + | widget:show() | |
| 198 | + | else | |
| 199 | + | widget:hide() | |
| 200 | + | end | |
| 201 | + | end | |
| 202 | + | end | |
| 203 | + | ||
| 204 | + | function UIMinimap:getFlag(pos) | |
| 205 | + | for _,flag in pairs(self.flags) do | |
| 206 | + | if flag.pos.x == pos.x and flag.pos.y == pos.y and flag.pos.z == pos.z then | |
| 207 | + | return flag | |
| 208 | + | end | |
| 209 | + | end | |
| 210 | + | return nil | |
| 211 | + | end | |
| 212 | + | ||
| 213 | + | function UIMinimap:removeFlag(pos, icon, description) | |
| 214 | + | local flag = self:getFlag(pos) | |
| 215 | + | if flag then | |
| 216 | + | flag:destroy() | |
| 217 | + | end | |
| 218 | + | end | |
| 219 | + | ||
| 220 | + | function UIMinimap:reset() | |
| 221 | + | self:setZoom(0) | |
| 222 | + | if self.cross then | |
| 223 | + | self:setCameraPosition(self.cross.pos) | |
| 224 | + | end | |
| 225 | + | end | |
| 226 | + | ||
| 227 | + | function UIMinimap:move(x, y) | |
| 228 | + | local cameraPos = self:getCameraPosition() | |
| 229 | + | local scale = self:getScale() | |
| 230 | + | if scale > 1 then scale = 1 end | |
| 231 | + | local dx = x/scale | |
| 232 | + | local dy = y/scale | |
| 233 | + | local pos = {x = cameraPos.x - dx, y = cameraPos.y - dy, z = cameraPos.z} | |
| 234 | + | self:setCameraPosition(pos) | |
| 235 | + | end | |
| 236 | + | ||
| 237 | + | function UIMinimap:onMouseWheel(mousePos, direction) | |
| 238 | + | local keyboardModifiers = g_keyboard.getModifiers() | |
| 239 | + | if direction == MouseWheelUp and keyboardModifiers == KeyboardNoModifier then | |
| 240 | + | self:zoomIn() | |
| 241 | + | elseif direction == MouseWheelDown and keyboardModifiers == KeyboardNoModifier then | |
| 242 | + | self:zoomOut() | |
| 243 | + | elseif direction == MouseWheelDown and keyboardModifiers == KeyboardCtrlModifier then | |
| 244 | + | self:floorUp(1) | |
| 245 | + | elseif direction == MouseWheelUp and keyboardModifiers == KeyboardCtrlModifier then | |
| 246 | + | self:floorDown(1) | |
| 247 | + | end | |
| 248 | + | end | |
| 249 | + | ||
| 250 | + | function UIMinimap:onMousePress(pos, button) | |
| 251 | + | if not self:isDragging() then | |
| 252 | + | self.allowNextRelease = true | |
| 253 | + | end | |
| 254 | + | end | |
| 255 | + | ||
| 256 | + | function UIMinimap:onMouseRelease(pos, button) | |
| 257 | + | if not self.allowNextRelease then return true end | |
| 258 | + | self.allowNextRelease = false | |
| 259 | + | ||
| 260 | + | local mapPos = self:getTilePosition(pos) | |
| 261 | + | if not mapPos then return end | |
| 262 | + | ||
| 263 | + | if button == MouseLeftButton then | |
| 264 | + | local player = g_game.getLocalPlayer() | |
| 265 | + | if adminMinimapTeleportEnabled and sendAdminMinimapTeleport(mapPos) then | |
| 266 | + | return true | |
| 267 | + | end | |
| 268 | + | if self.autowalk then | |
| 269 | + | player:autoWalk(mapPos) | |
| 270 | + | end | |
| 271 | + | return true | |
| 272 | + | elseif button == MouseRightButton then | |
| 273 | + | local menu = g_ui.createWidget('PopupMenu') | |
| 274 | + | menu:setGameMenu(true) | |
| 275 | + | menu:addOption(tr('Create mark'), function() self:createFlagWindow(mapPos) end) | |
| 276 | + | menu:display(pos) | |
| 277 | + | return true | |
| 278 | + | end | |
| 279 | + | return false | |
| 280 | + | end | |
| 281 | + | ||
| 282 | + | function UIMinimap:onDragEnter(pos) | |
| 283 | + | self.dragReference = pos | |
| 284 | + | self.dragCameraReference = self:getCameraPosition() | |
| 285 | + | return true | |
| 286 | + | end | |
| 287 | + | ||
| 288 | + | function UIMinimap:onDragMove(pos, moved) | |
| 289 | + | local scale = self:getScale() | |
| 290 | + | local dx = (self.dragReference.x - pos.x)/scale | |
| 291 | + | local dy = (self.dragReference.y - pos.y)/scale | |
| 292 | + | local pos = {x = self.dragCameraReference.x + dx, y = self.dragCameraReference.y + dy, z = self.dragCameraReference.z} | |
| 293 | + | self:setCameraPosition(pos) | |
| 294 | + | return true | |
| 295 | + | end | |
| 296 | + | ||
| 297 | + | function UIMinimap:onDragLeave(widget, pos) | |
| 298 | + | return true | |
| 299 | + | end | |
| 300 | + | ||
| 301 | + | function UIMinimap:onStyleApply(styleName, styleNode) | |
| 302 | + | for name,value in pairs(styleNode) do | |
| 303 | + | if name == 'autowalk' then | |
| 304 | + | self.autowalk = value | |
| 305 | + | end | |
| 306 | + | end | |
| 307 | + | end | |
| 308 | + | ||
| 309 | + | function UIMinimap:createFlagWindow(pos) | |
| 310 | + | if self.flagWindow then return end | |
| 311 | + | if not pos then return end | |
| 312 | + | ||
| 313 | + | self.flagWindow = g_ui.createWidget('MinimapFlagWindow', rootWidget) | |
| 314 | + | self.flagWindow:setWidth(210) -- For some reason changing this in otui got ignored | |
| 315 | + | self.flagWindow:setHeight(215) | |
| 316 | + | ||
| 317 | + | local positionLabel = self.flagWindow:getChildById('position') | |
| 318 | + | local description = self.flagWindow:getChildById('description') | |
| 319 | + | local okButton = self.flagWindow:getChildById('okButton') | |
| 320 | + | local cancelButton = self.flagWindow:getChildById('cancelButton') | |
| 321 | + | ||
| 322 | + | positionLabel:setText(string.format('%i, %i, %i', pos.x, pos.y, pos.z)) | |
| 323 | + | ||
| 324 | + | local flagRadioGroup = UIRadioGroup.create() | |
| 325 | + | for i=0,19 do | |
| 326 | + | local checkbox = self.flagWindow:getChildById('flag' .. i) | |
| 327 | + | checkbox.icon = i | |
| 328 | + | flagRadioGroup:addWidget(checkbox) | |
| 329 | + | end | |
| 330 | + | ||
| 331 | + | flagRadioGroup:selectWidget(flagRadioGroup:getFirstWidget()) | |
| 332 | + | ||
| 333 | + | local successFunc = function() | |
| 334 | + | self:addFlag(pos, flagRadioGroup:getSelectedWidget().icon, description:getText()) | |
| 335 | + | self:destroyFlagWindow() | |
| 336 | + | end | |
| 337 | + | ||
| 338 | + | local cancelFunc = function() | |
| 339 | + | self:destroyFlagWindow() | |
| 340 | + | end | |
| 341 | + | ||
| 342 | + | okButton.onClick = successFunc | |
| 343 | + | cancelButton.onClick = cancelFunc | |
| 344 | + | ||
| 345 | + | self.flagWindow.onEnter = successFunc | |
| 346 | + | self.flagWindow.onEscape = cancelFunc | |
| 347 | + | ||
| 348 | + | self.flagWindow.onDestroy = function() flagRadioGroup:destroy() end | |
| 349 | + | end | |
| 350 | + | ||
| 351 | + | function UIMinimap:destroyFlagWindow() | |
| 352 | + | if self.flagWindow then | |
| 353 | + | self.flagWindow:destroy() | |
| 354 | + | self.flagWindow = nil | |
| 355 | + | end | |
| 356 | + | end |
| @@ -0,0 +1,83 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIRadialProgress = extends(UIWidget, "UIRadialProgress") | |
| 3 | + | ||
| 4 | + | function UIRadialProgress.create() | |
| 5 | + | local progressbar = UIRadialProgress.internalCreate() | |
| 6 | + | progressbar.progress = 100 | |
| 7 | + | progressbar.radial = false | |
| 8 | + | return progressbar | |
| 9 | + | end | |
| 10 | + | ||
| 11 | + | function UIRadialProgress:onStyleApply(name, node) | |
| 12 | + | for name,value in pairs(node) do | |
| 13 | + | if name == 'radial' then | |
| 14 | + | self:setRadial(value) | |
| 15 | + | end | |
| 16 | + | end | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function UIRadialProgress:setProgress(value) | |
| 20 | + | self.progress = tonumber(value) or 100 | |
| 21 | + | self:updateProgressbar() | |
| 22 | + | end | |
| 23 | + | ||
| 24 | + | function UIRadialProgress:getProgress() | |
| 25 | + | return self.progress | |
| 26 | + | end | |
| 27 | + | ||
| 28 | + | function UIRadialProgress:setRadial(value) | |
| 29 | + | self.radial = not not value | |
| 30 | + | end | |
| 31 | + | ||
| 32 | + | function UIRadialProgress:isRadial() | |
| 33 | + | return self.radial | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | function UIRadialProgress:updateProgressbar() | |
| 37 | + | local progress1 = self:getChildById('progress1') | |
| 38 | + | local progress2 = self:getChildById('progress2') | |
| 39 | + | ||
| 40 | + | if not (progress1 and progress2) then | |
| 41 | + | return | |
| 42 | + | end | |
| 43 | + | ||
| 44 | + | local radius = progress1:getImageTextureWidth() | |
| 45 | + | local height = radius * 2 | |
| 46 | + | local progressRad = self.progress / 100 * math.pi * 2 | |
| 47 | + | ||
| 48 | + | -- calculate progressbar heights | |
| 49 | + | local height1 = radius * 2 | |
| 50 | + | local height2 = 0 | |
| 51 | + | ||
| 52 | + | if self.radial then | |
| 53 | + | if progressRad < math.pi then | |
| 54 | + | height1 = radius - math.cos(progressRad) * radius | |
| 55 | + | else | |
| 56 | + | progressRad = progressRad - math.pi | |
| 57 | + | height2 = radius - math.cos(progressRad) * radius | |
| 58 | + | end | |
| 59 | + | else | |
| 60 | + | if self.progress < 50 then | |
| 61 | + | height1 = self.progress / 50 * height | |
| 62 | + | else | |
| 63 | + | height2 = (self.progress-50) / 50 * height | |
| 64 | + | end | |
| 65 | + | end | |
| 66 | + | ||
| 67 | + | -- set image rects | |
| 68 | + | if height1 == 0 then | |
| 69 | + | progress1:hide() | |
| 70 | + | else | |
| 71 | + | progress1:show() | |
| 72 | + | progress1:setImageRect({x=0, y=0, width=radius, height=height1}) | |
| 73 | + | progress1:setImageClip({x=0, y=0, width=radius, height=height1}) | |
| 74 | + | end | |
| 75 | + | ||
| 76 | + | if height2 == 0 then | |
| 77 | + | progress2:hide() | |
| 78 | + | else | |
| 79 | + | progress2:show() | |
| 80 | + | progress2:setImageRect({x=0, y=height-height2, width=radius, height=height2}) | |
| 81 | + | progress2:setImageClip({x=0, y=height-height2, width=radius, height=height2}) | |
| 82 | + | end | |
| 83 | + | end |
| @@ -0,0 +1,67 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UIRating = extends(UIWidget, "UIRating") | |
| 3 | + | ||
| 4 | + | function UIRating.create() | |
| 5 | + | local panel = UIRating.internalCreate() | |
| 6 | + | panel.rating = 5 | |
| 7 | + | return panel | |
| 8 | + | end | |
| 9 | + | ||
| 10 | + | function UIRating:onStyleApply(styleName, styleNode) | |
| 11 | + | for name,value in pairs(styleNode) do | |
| 12 | + | if name == 'rating' then | |
| 13 | + | self.rating = value -- delayed execution | |
| 14 | + | elseif name == 'custom-size' then | |
| 15 | + | self.customSize = value | |
| 16 | + | end | |
| 17 | + | end | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | function UIRating:setRating(value) | |
| 21 | + | self._rating = self.rating | |
| 22 | + | ||
| 23 | + | for index, child in pairs(self:getChildren()) do | |
| 24 | + | if index <= value then | |
| 25 | + | child:show() | |
| 26 | + | else | |
| 27 | + | child:hide() | |
| 28 | + | end | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | self:updateSize() | |
| 32 | + | end | |
| 33 | + | ||
| 34 | + | function UIRating:updateSize() | |
| 35 | + | local children = 0 | |
| 36 | + | for index, child in pairs(self:getChildren()) do | |
| 37 | + | if child:isVisible() then | |
| 38 | + | children = children + 1 | |
| 39 | + | end | |
| 40 | + | end | |
| 41 | + | self:setWidth(self:getHeight() * children + self:getLayout():getSpacing() * (children-1)) | |
| 42 | + | end | |
| 43 | + | ||
| 44 | + | function UIRating:onGeometryChange() | |
| 45 | + | if self.customSize then | |
| 46 | + | self:setCustomSize(self.customSize) | |
| 47 | + | self.customSize = nil | |
| 48 | + | end | |
| 49 | + | if self.rating and self.rating ~= self._rating then | |
| 50 | + | self:setRating(self.rating) | |
| 51 | + | end | |
| 52 | + | end | |
| 53 | + | ||
| 54 | + | function UIRating:getRating() | |
| 55 | + | return self.rating | |
| 56 | + | end | |
| 57 | + | ||
| 58 | + | function UIRating:setCustomSize(size) | |
| 59 | + | self:setHeight(size) | |
| 60 | + | ||
| 61 | + | for index, child in pairs(self:getChildren()) do | |
| 62 | + | child:setWidth(size) | |
| 63 | + | child:setHeight(size) | |
| 64 | + | end | |
| 65 | + | ||
| 66 | + | self:updateSize() | |
| 67 | + | end |
| @@ -0,0 +1,89 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UISpellProgressRect = extends(UIProgressRect, "UISpellProgressRect") | |
| 3 | + | ||
| 4 | + | function UISpellProgressRect.create() | |
| 5 | + | local progressbar = UISpellProgressRect.internalCreate() | |
| 6 | + | progressbar.cooldown = 1000 | |
| 7 | + | progressbar.event = nil | |
| 8 | + | progressbar.currentCooldown = 0 | |
| 9 | + | progressbar.showText = false | |
| 10 | + | progressbar.globalCooldown = false | |
| 11 | + | progressbar.cooldownRemainder = 0 | |
| 12 | + | return progressbar | |
| 13 | + | end | |
| 14 | + | ||
| 15 | + | function UISpellProgressRect:setCooldown(num) | |
| 16 | + | self.cooldown = num | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function UISpellProgressRect:getCooldown() | |
| 20 | + | return self.cooldown | |
| 21 | + | end | |
| 22 | + | ||
| 23 | + | function UISpellProgressRect:reset() | |
| 24 | + | removeEvent(self.event) | |
| 25 | + | self.currentCooldown = 0 | |
| 26 | + | self.cooldownRemainder = 0 | |
| 27 | + | self:removeSpellCooldown() | |
| 28 | + | end | |
| 29 | + | ||
| 30 | + | function UISpellProgressRect:onStyleApply(name, node) | |
| 31 | + | for name,value in pairs(node) do | |
| 32 | + | if name == 'cooldown' then | |
| 33 | + | self.cooldown = tonumber(value) | |
| 34 | + | elseif name == 'show-text' then | |
| 35 | + | self.showText = true | |
| 36 | + | end | |
| 37 | + | end | |
| 38 | + | end | |
| 39 | + | ||
| 40 | + | function UISpellProgressRect:onSpellCooldown(cooldown, isGCD) | |
| 41 | + | if self.event then | |
| 42 | + | removeEvent(self.event) | |
| 43 | + | if not self.globalCooldown and self.currentCooldown > 0 then | |
| 44 | + | self.cooldownRemainder = self.currentCooldown | |
| 45 | + | end | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | self.cooldown = cooldown | |
| 49 | + | self.currentCooldown = self.cooldown | |
| 50 | + | self.globalCooldown = isGCD | |
| 51 | + | self:setPercent(100) | |
| 52 | + | ||
| 53 | + | local text = self:recursiveGetChildById('text') | |
| 54 | + | if text then | |
| 55 | + | text:setText(math.max(0, math.ceil(cooldown/1000))) | |
| 56 | + | text:setVisible(self.showText) | |
| 57 | + | end | |
| 58 | + | ||
| 59 | + | local widget = self | |
| 60 | + | self.event = scheduleEvent(function() widget:updateSpellCooldown() end, 10) | |
| 61 | + | self:show() | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function UISpellProgressRect:updateSpellCooldown() | |
| 65 | + | if self.currentCooldown <= 0 then | |
| 66 | + | if not self.globalCooldown then self.cooldownRemainder = 0 end | |
| 67 | + | self:removeSpellCooldown() | |
| 68 | + | return | |
| 69 | + | end | |
| 70 | + | ||
| 71 | + | if self.globalCooldown and self.cooldownRemainder > 0 then self.cooldownRemainder = self.cooldownRemainder - 10 end | |
| 72 | + | self.currentCooldown = self.currentCooldown - 10 | |
| 73 | + | self:setPercent(100 - (self.currentCooldown/self.cooldown * 100)) | |
| 74 | + | self.text:setText(math.max(0, math.ceil(self.currentCooldown/1000))) | |
| 75 | + | ||
| 76 | + | local widget = self | |
| 77 | + | self.event = scheduleEvent(function() widget:updateSpellCooldown(false) end, 10) | |
| 78 | + | self:show() | |
| 79 | + | end | |
| 80 | + | ||
| 81 | + | function UISpellProgressRect:removeSpellCooldown() | |
| 82 | + | self.globalCooldown = false | |
| 83 | + | self:hide() | |
| 84 | + | removeEvent(self.event) | |
| 85 | + | self.event = nil | |
| 86 | + | if self.cooldownRemainder > 0 then | |
| 87 | + | self:onSpellCooldown(self.cooldownRemainder, false) | |
| 88 | + | end | |
| 89 | + | end |
| @@ -0,0 +1,134 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UISprite = extends(UIImageView, "UISprite") | |
| 3 | + | ||
| 4 | + | function UISprite.create() | |
| 5 | + | local widget = UISprite.internalCreate() | |
| 6 | + | widget.anim = { | |
| 7 | + | path = '', | |
| 8 | + | count = 0, | |
| 9 | + | speed = 0, | |
| 10 | + | currentIndex = 0, | |
| 11 | + | startIndex = 1, | |
| 12 | + | defaultIndex = 1, | |
| 13 | + | prefix = '', | |
| 14 | + | } | |
| 15 | + | widget.event = nil | |
| 16 | + | widget.playing = false | |
| 17 | + | return widget | |
| 18 | + | end | |
| 19 | + | ||
| 20 | + | function UISprite:onStyleApply(styleName, styleNode) | |
| 21 | + | local updateAnim = false | |
| 22 | + | ||
| 23 | + | for name,value in pairs(styleNode) do | |
| 24 | + | if name == 'path' then | |
| 25 | + | if not value:ends('/') then | |
| 26 | + | value = value .. '/' | |
| 27 | + | end | |
| 28 | + | self.anim.path = value | |
| 29 | + | updateAnim = true | |
| 30 | + | elseif name == 'prefix' then | |
| 31 | + | self.anim.prefix = value | |
| 32 | + | updateAnim = true | |
| 33 | + | elseif name == 'count' then | |
| 34 | + | self.anim.count = tonumber(value) | |
| 35 | + | updateAnim = true | |
| 36 | + | elseif name == 'speed' then | |
| 37 | + | self.anim.speed = tonumber(value) | |
| 38 | + | updateAnim = true | |
| 39 | + | elseif name == 'start-index' then | |
| 40 | + | self.anim.startIndex = tonumber(value) | |
| 41 | + | updateAnim = true | |
| 42 | + | elseif name == 'default-index' then | |
| 43 | + | self.anim.defaultIndex = tonumber(value) | |
| 44 | + | updateAnim = true | |
| 45 | + | end | |
| 46 | + | end | |
| 47 | + | ||
| 48 | + | if updateAnim then | |
| 49 | + | self:updateAnim() | |
| 50 | + | end | |
| 51 | + | end | |
| 52 | + | ||
| 53 | + | function UISprite:onReset() | |
| 54 | + | local anim = self.anim | |
| 55 | + | anim.currentIndex = anim.defaultIndex | |
| 56 | + | ||
| 57 | + | local filename = anim.path .. anim.prefix .. anim.currentIndex | |
| 58 | + | self:setImageSource(filename) | |
| 59 | + | ||
| 60 | + | -- sprite.event = scheduleEvent(self.onAnim, sprite.speed) | |
| 61 | + | end | |
| 62 | + | ||
| 63 | + | function UISprite:onStep() | |
| 64 | + | local anim = self.anim | |
| 65 | + | anim.currentIndex = anim.currentIndex + 1 | |
| 66 | + | if anim.currentIndex > anim.count then | |
| 67 | + | -- todo implement loop count | |
| 68 | + | -- anim.currentIndex = anim.startIndex | |
| 69 | + | self:stopAnim() | |
| 70 | + | return | |
| 71 | + | end | |
| 72 | + | ||
| 73 | + | local filename = anim.path .. anim.prefix .. anim.currentIndex | |
| 74 | + | self:setImageSource(filename) | |
| 75 | + | ||
| 76 | + | -- sprite.event = scheduleEvent(self.onAnim, sprite.speed) | |
| 77 | + | end | |
| 78 | + | ||
| 79 | + | function UISprite:updateAnim() | |
| 80 | + | -- if not self.sprite then return end | |
| 81 | + | ||
| 82 | + | if self.event then | |
| 83 | + | removeEvent(self.event) | |
| 84 | + | self.event = nil | |
| 85 | + | end | |
| 86 | + | ||
| 87 | + | if self.playing == false then | |
| 88 | + | return | |
| 89 | + | end | |
| 90 | + | ||
| 91 | + | if self.anim then | |
| 92 | + | local speed = self.anim.speed | |
| 93 | + | if speed > 0 then | |
| 94 | + | self.event = cycleEvent(function() self:onStep() end, speed) | |
| 95 | + | end | |
| 96 | + | end | |
| 97 | + | end | |
| 98 | + | ||
| 99 | + | function UISprite:stopAnim() | |
| 100 | + | removeEvent(self.event) | |
| 101 | + | self.event = nil | |
| 102 | + | ||
| 103 | + | removeEvent(self.delayEvent) | |
| 104 | + | self.delayEvent = nil | |
| 105 | + | ||
| 106 | + | self.playing = false | |
| 107 | + | ||
| 108 | + | self:onReset() | |
| 109 | + | end | |
| 110 | + | ||
| 111 | + | function UISprite:startAnim(delay) | |
| 112 | + | if delay and delay < 0 then delay = 0 end | |
| 113 | + | local spr = self | |
| 114 | + | ||
| 115 | + | if delay and delay > 0 then | |
| 116 | + | self.delayEvent = scheduleEvent(function() | |
| 117 | + | spr.playing = true | |
| 118 | + | spr:updateAnim() | |
| 119 | + | end, delay) | |
| 120 | + | else | |
| 121 | + | spr.playing = true | |
| 122 | + | spr:updateAnim() | |
| 123 | + | end | |
| 124 | + | ||
| 125 | + | end | |
| 126 | + | ||
| 127 | + | function UISprite:onDestroy() | |
| 128 | + | removeEvent(self.event) | |
| 129 | + | self.event = nil | |
| 130 | + | end | |
| 131 | + | ||
| 132 | + | function UISprite:isPlaying() | |
| 133 | + | return self.event ~= nil | |
| 134 | + | end |
| @@ -0,0 +1,50 @@ | |||
| 1 | + | -- @docclass | |
| 2 | + | UITradepackListItem = extends(UIWidget, "UITradepackListItem") | |
| 3 | + | ||
| 4 | + | function UITradepackListItem.create() | |
| 5 | + | local btn = UITradepackListItem.internalCreate() | |
| 6 | + | return btn | |
| 7 | + | end | |
| 8 | + | ||
| 9 | + | function UITradepackListItem:onStyleApply(styleName, styleNode) | |
| 10 | + | for name,value in pairs(styleNode) do | |
| 11 | + | if name == 'even-background-color' then | |
| 12 | + | self.evenBackgroundColor = value | |
| 13 | + | elseif name == 'odd-background-color' then | |
| 14 | + | self.oddBackgroundColor = value | |
| 15 | + | end | |
| 16 | + | end | |
| 17 | + | end | |
| 18 | + | ||
| 19 | + | function UITradepackListItem:updateBackgroundColor() | |
| 20 | + | self.backgroundColor = nil | |
| 21 | + | ||
| 22 | + | local children = self:getParent():getChildren() | |
| 23 | + | local isEven = self.isEven | |
| 24 | + | ||
| 25 | + | if isEven and self.evenBackgroundColor then | |
| 26 | + | self.backgroundColor = self.evenBackgroundColor | |
| 27 | + | elseif not isEven and self.oddBackgroundColor then | |
| 28 | + | self.backgroundColor = self.oddBackgroundColor | |
| 29 | + | end | |
| 30 | + | ||
| 31 | + | if self.backgroundColor then | |
| 32 | + | self:mergeStyle({ ['background-color'] = self.backgroundColor }) | |
| 33 | + | end | |
| 34 | + | end | |
| 35 | + | ||
| 36 | + | function UITradepackListItem:setRowBackgroundColor(order) | |
| 37 | + | self.backgroundColor = nil | |
| 38 | + | ||
| 39 | + | local children = self:getParent():getChildren() | |
| 40 | + | ||
| 41 | + | if order == 'even' and self.evenBackgroundColor then | |
| 42 | + | self.backgroundColor = self.evenBackgroundColor | |
| 43 | + | elseif order == 'odd' and self.oddBackgroundColor then | |
| 44 | + | self.backgroundColor = self.oddBackgroundColor | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | if self.backgroundColor then | |
| 48 | + | self:mergeStyle({ ['background-color'] = self.backgroundColor }) | |
| 49 | + | end | |
| 50 | + | end |
| @@ -0,0 +1,11 @@ | |||
| 1 | + | function postostring(pos) | |
| 2 | + | return pos.x .. " " .. pos.y .. " " .. pos.z | |
| 3 | + | end | |
| 4 | + | ||
| 5 | + | function dirtostring(dir) | |
| 6 | + | for k, v in pairs(Directions) do | |
| 7 | + | if v == dir then | |
| 8 | + | return k | |
| 9 | + | end | |
| 10 | + | end | |
| 11 | + | end |
| @@ -0,0 +1,455 @@ | |||
| 1 | + | actionPanel1 = nil | |
| 2 | + | actionPanel2 = nil | |
| 3 | + | ||
| 4 | + | local actionConfig | |
| 5 | + | local hotkeyAssignWindow | |
| 6 | + | local actionButtonsInPanel = 50 | |
| 7 | + | ||
| 8 | + | ActionTypes = { | |
| 9 | + | USE = 0, | |
| 10 | + | USE_SELF = 1, | |
| 11 | + | USE_TARGET = 2, | |
| 12 | + | USE_WITH = 3, | |
| 13 | + | EQUIP = 4 | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | ActionColors = { | |
| 17 | + | empty = '#00000033', | |
| 18 | + | text = '#00000033', | |
| 19 | + | itemUse = '#8888FF88', | |
| 20 | + | itemUseSelf = '#00FF0088', | |
| 21 | + | itemUseTarget = '#FF000088', | |
| 22 | + | itemUseWith = '#F5B32588', | |
| 23 | + | itemEquip = '#FFFFFF88' | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | function init() | |
| 27 | + | local bottomPanel = modules.game_interface.getActionPanel() | |
| 28 | + | actionPanel1 = g_ui.loadUI('actionbar', bottomPanel) | |
| 29 | + | bottomPanel:moveChildToIndex(actionPanel1, 1) | |
| 30 | + | actionPanel2 = g_ui.loadUI('actionbar', bottomPanel) | |
| 31 | + | bottomPanel:moveChildToIndex(actionPanel2, 1) | |
| 32 | + | ||
| 33 | + | actionConfig = g_configs.create("/actionbar.otml") | |
| 34 | + | ||
| 35 | + | connect(g_game, { | |
| 36 | + | onGameStart = online, | |
| 37 | + | onGameEnd = offline, | |
| 38 | + | onSpellGroupCooldown = onSpellGroupCooldown, | |
| 39 | + | onSpellCooldown = onSpellCooldown | |
| 40 | + | }) | |
| 41 | + | ||
| 42 | + | if g_game.isOnline() then | |
| 43 | + | online() | |
| 44 | + | end | |
| 45 | + | end | |
| 46 | + | ||
| 47 | + | function terminate() | |
| 48 | + | disconnect(g_game, { | |
| 49 | + | onGameStart = online, | |
| 50 | + | onGameEnd = offline, | |
| 51 | + | onSpellGroupCooldown = onSpellGroupCooldown, | |
| 52 | + | onSpellCooldown = onSpellCooldown | |
| 53 | + | }) | |
| 54 | + | ||
| 55 | + | -- remove hotkeys, also saves config | |
| 56 | + | if actionPanel1.tabBar:getChildCount() > 0 and actionPanel2.tabBar:getChildCount() > 0 then | |
| 57 | + | offline() | |
| 58 | + | end | |
| 59 | + | ||
| 60 | + | actionPanel1:destroy() | |
| 61 | + | actionPanel2:destroy() | |
| 62 | + | end | |
| 63 | + | ||
| 64 | + | function show() | |
| 65 | + | if not g_game.isOnline() then return end | |
| 66 | + | actionPanel1:setOn(g_settings.getBoolean("actionBar1", false)) | |
| 67 | + | actionPanel2:setOn(g_settings.getBoolean("actionBar2", false)) | |
| 68 | + | end | |
| 69 | + | ||
| 70 | + | function hide() | |
| 71 | + | actionPanel1:setOn(false) | |
| 72 | + | actionPanel2:setOn(false) | |
| 73 | + | end | |
| 74 | + | ||
| 75 | + | function switchMode(newMode) | |
| 76 | + | if newMode then | |
| 77 | + | actionPanel1:setImageColor('#ffffff88') | |
| 78 | + | actionPanel2:setImageColor('#ffffff88') | |
| 79 | + | else | |
| 80 | + | actionPanel1:setImageColor('white') | |
| 81 | + | actionPanel2:setImageColor('white') | |
| 82 | + | end | |
| 83 | + | end | |
| 84 | + | ||
| 85 | + | function online() | |
| 86 | + | setupActionPanel(1, actionPanel1) | |
| 87 | + | setupActionPanel(2, actionPanel2) | |
| 88 | + | show() | |
| 89 | + | end | |
| 90 | + | ||
| 91 | + | function offline() | |
| 92 | + | hide() | |
| 93 | + | if hotkeyAssignWindow then | |
| 94 | + | hotkeyAssignWindow:destroy() | |
| 95 | + | hotkeyAssignWindow = nil | |
| 96 | + | end | |
| 97 | + | ||
| 98 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 99 | + | for index, panel in ipairs({actionPanel1, actionPanel2}) do | |
| 100 | + | local config = {} | |
| 101 | + | for i, child in ipairs(panel.tabBar:getChildren()) do | |
| 102 | + | if child.config then | |
| 103 | + | table.insert(config, child.config) | |
| 104 | + | if type(child.config.hotkey) == 'string' and child.config.hotkey:len() > 0 then | |
| 105 | + | g_keyboard.unbindKeyPress(child.config.hotkey, child.callback, gameRootPanel) | |
| 106 | + | end | |
| 107 | + | else | |
| 108 | + | table.insert(config, {}) | |
| 109 | + | end | |
| 110 | + | if child.cooldownEvent then | |
| 111 | + | removeEvent(child.cooldownEvent) | |
| 112 | + | end | |
| 113 | + | end | |
| 114 | + | actionConfig:setNode('actions_' .. index, config) | |
| 115 | + | panel.tabBar:destroyChildren() | |
| 116 | + | end | |
| 117 | + | actionConfig:save() | |
| 118 | + | end | |
| 119 | + | ||
| 120 | + | function setupActionPanel(index, panel) | |
| 121 | + | local rawConfig = actionConfig:getNode('actions_' .. index) or {} | |
| 122 | + | local config = {} | |
| 123 | + | for i, buttonConfig in pairs(rawConfig) do -- sorting, because key in rawConfig is string | |
| 124 | + | config[tonumber(i)] = buttonConfig | |
| 125 | + | end | |
| 126 | + | ||
| 127 | + | for i=1,actionButtonsInPanel do | |
| 128 | + | local action = g_ui.createWidget('ActionButton', panel.tabBar) | |
| 129 | + | action.config = config[i] or {} | |
| 130 | + | setupAction(action) | |
| 131 | + | end | |
| 132 | + | ||
| 133 | + | panel.nextButton.onClick = function() | |
| 134 | + | panel.tabBar:moveChildToIndex(panel.tabBar:getFirstChild(), panel.tabBar:getChildCount()) | |
| 135 | + | end | |
| 136 | + | panel.prevButton.onClick = function() | |
| 137 | + | panel.tabBar:moveChildToIndex(panel.tabBar:getLastChild(), 1) | |
| 138 | + | end | |
| 139 | + | end | |
| 140 | + | ||
| 141 | + | function setupAction(action) | |
| 142 | + | local config = action.config | |
| 143 | + | action.item:setShowCount(false) | |
| 144 | + | action.onMouseRelease = actionOnMouseRelease | |
| 145 | + | action.onTouchRelease = actionOnMouseRelease | |
| 146 | + | action.callback = function(k, c, ticks) executeAction(action, ticks) end | |
| 147 | + | action.item.onItemChange = nil -- disable callbacks for setup | |
| 148 | + | ||
| 149 | + | if config then | |
| 150 | + | if type(config.text) == 'number' then | |
| 151 | + | config.text = tostring(config.text) | |
| 152 | + | end | |
| 153 | + | if type(config.hotkey) == 'number' then | |
| 154 | + | config.hotkey = tostring(config.hotkey) | |
| 155 | + | end | |
| 156 | + | if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then | |
| 157 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 158 | + | g_keyboard.bindKeyPress(config.hotkey, action.callback, gameRootPanel) | |
| 159 | + | action.hotkeyLabel:setText(config.hotkey) | |
| 160 | + | else | |
| 161 | + | action.hotkeyLabel:setText("") | |
| 162 | + | end | |
| 163 | + | ||
| 164 | + | action.text:setImageSource("") | |
| 165 | + | action.cooldownTill = 0 | |
| 166 | + | action.cooldownStart = 0 | |
| 167 | + | if type(config.text) == 'string' and config.text:len() > 0 then | |
| 168 | + | action.text:setText(config.text) | |
| 169 | + | action.item:setBorderColor(ActionColors.text) | |
| 170 | + | action.item:setOn(true) -- removes background | |
| 171 | + | action.item:setItemId(0) | |
| 172 | + | if Spells then | |
| 173 | + | local spell, profile = Spells.getSpellByWords(config.text:lower()) | |
| 174 | + | action.spell = spell | |
| 175 | + | if action.spell and action.spell.icon and profile then | |
| 176 | + | action.text:setImageSource(SpelllistSettings[profile].iconFile) | |
| 177 | + | action.text:setImageClip(Spells.getImageClip(SpellIcons[action.spell.icon][1], profile)) | |
| 178 | + | action.text:setText("") | |
| 179 | + | end | |
| 180 | + | end | |
| 181 | + | else | |
| 182 | + | action.text:setText("") | |
| 183 | + | action.spell = nil | |
| 184 | + | if type(config.item) == 'number' and config.item > 100 then | |
| 185 | + | action.item:setOn(true) | |
| 186 | + | action.item:setItemId(config.item) | |
| 187 | + | action.item:setItemCount(config.count or 1) | |
| 188 | + | setupActionType(action, config.actionType) | |
| 189 | + | else | |
| 190 | + | action.item:setItemId(0) | |
| 191 | + | action.item:setOn(false) | |
| 192 | + | action.item:setBorderColor(ActionColors.empty) | |
| 193 | + | end | |
| 194 | + | end | |
| 195 | + | end | |
| 196 | + | ||
| 197 | + | action.item.onItemChange = actionOnItemChange | |
| 198 | + | end | |
| 199 | + | ||
| 200 | + | function setupActionType(action, actionType) | |
| 201 | + | local item = action.item:getItem() | |
| 202 | + | if action.item:getItem():isMultiUse() then | |
| 203 | + | if not actionType or actionType <= ActionTypes.USE then | |
| 204 | + | actionType = ActionTypes.USE_WITH | |
| 205 | + | end | |
| 206 | + | elseif g_game.getClientVersion() >= 910 then | |
| 207 | + | if actionType ~= ActionTypes.USE and actionType ~= ActionTypes.EQUIP then | |
| 208 | + | actionType = ActionTypes.USE | |
| 209 | + | end | |
| 210 | + | else | |
| 211 | + | actionType = ActionTypes.USE | |
| 212 | + | end | |
| 213 | + | ||
| 214 | + | action.config.actionType = actionType | |
| 215 | + | if action.config.actionType == ActionTypes.USE then | |
| 216 | + | action.item:setBorderColor(ActionColors.itemUse) | |
| 217 | + | elseif action.config.actionType == ActionTypes.USE_SELF then | |
| 218 | + | action.item:setBorderColor(ActionColors.itemUseSelf) | |
| 219 | + | elseif action.config.actionType == ActionTypes.USE_TARGET then | |
| 220 | + | action.item:setBorderColor(ActionColors.itemUseTarget) | |
| 221 | + | elseif action.config.actionType == ActionTypes.USE_WITH then | |
| 222 | + | action.item:setBorderColor(ActionColors.itemUseWith) | |
| 223 | + | elseif action.config.actionType == ActionTypes.EQUIP then | |
| 224 | + | action.item:setBorderColor(ActionColors.itemEquip) | |
| 225 | + | end | |
| 226 | + | end | |
| 227 | + | ||
| 228 | + | function updateAction(action, newConfig) | |
| 229 | + | local config = action.config | |
| 230 | + | if type(config.hotkey) == 'string' and config.hotkey:len() > 0 then | |
| 231 | + | local gameRootPanel = modules.game_interface.getRootPanel() | |
| 232 | + | g_keyboard.unbindKeyPress(config.hotkey, action.callback, gameRootPanel) | |
| 233 | + | end | |
| 234 | + | for key, val in pairs(newConfig) do | |
| 235 | + | action.config[key] = val | |
| 236 | + | end | |
| 237 | + | setupAction(action) | |
| 238 | + | end | |
| 239 | + | ||
| 240 | + | function actionOnMouseRelease(action, mousePosition, mouseButton) | |
| 241 | + | if mouseButton == MouseTouch then return end | |
| 242 | + | if mouseButton == MouseRightButton or not action.item:isOn() then | |
| 243 | + | local menu = g_ui.createWidget('PopupMenu') | |
| 244 | + | menu:setGameMenu(true) | |
| 245 | + | if action.item:getItemId() > 0 then | |
| 246 | + | if action.item:getItem():isMultiUse() then | |
| 247 | + | menu:addOption(tr('Use on yourself'), function() return setupActionType(action, ActionTypes.USE_SELF) end) | |
| 248 | + | menu:addOption(tr('Use on target'), function() return setupActionType(action, ActionTypes.USE_TARGET) end) | |
| 249 | + | menu:addOption(tr('With crosshair'), function() return setupActionType(action, ActionTypes.USE_WITH) end) | |
| 250 | + | end | |
| 251 | + | if g_game.getClientVersion() >= 910 then | |
| 252 | + | if not action.item:getItem():isMultiUse() then | |
| 253 | + | menu:addOption(tr('Use'), function() return setupActionType(action, ActionTypes.USE) end) | |
| 254 | + | end | |
| 255 | + | menu:addOption(tr('Equip'), function() return setupActionType(action, ActionTypes.EQUIP) end) | |
| 256 | + | end | |
| 257 | + | else | |
| 258 | + | menu:addOption(tr('Select item'), function() return modules.game_itemselector.show(action.item) end) | |
| 259 | + | end | |
| 260 | + | menu:addSeparator() | |
| 261 | + | menu:addOption(tr('Set text'), function() | |
| 262 | + | modules.client_textedit.singlelineEditor(action.config.text or "", function(newText) | |
| 263 | + | updateAction(action, {text=newText, item=0}) | |
| 264 | + | end) | |
| 265 | + | end) | |
| 266 | + | menu:addOption(tr('Set hotkey'), function() | |
| 267 | + | if hotkeyAssignWindow then | |
| 268 | + | hotkeyAssignWindow:destroy() | |
| 269 | + | end | |
| 270 | + | local assignWindow = g_ui.createWidget('ActionAssignWindow', rootWidget) | |
| 271 | + | assignWindow:grabKeyboard() | |
| 272 | + | assignWindow.comboPreview.keyCombo = '' | |
| 273 | + | assignWindow.onKeyDown = function(assignWindow, keyCode, keyboardModifiers) | |
| 274 | + | local keyCombo = determineKeyComboDesc(keyCode, keyboardModifiers) | |
| 275 | + | assignWindow.comboPreview:setText(tr('Current action hotkey: %s', keyCombo)) | |
| 276 | + | assignWindow.comboPreview.keyCombo = keyCombo | |
| 277 | + | assignWindow.comboPreview:resizeToText() | |
| 278 | + | return true | |
| 279 | + | end | |
| 280 | + | assignWindow.onDestroy = function(widget) | |
| 281 | + | if widget == hotkeyAssignWindow then | |
| 282 | + | hotkeyAssignWindow = nil | |
| 283 | + | end | |
| 284 | + | end | |
| 285 | + | assignWindow.addButton.onClick = function() | |
| 286 | + | updateAction(action, {hotkey=tostring(assignWindow.comboPreview.keyCombo)}) | |
| 287 | + | assignWindow:destroy() | |
| 288 | + | end | |
| 289 | + | hotkeyAssignWindow = assignWindow | |
| 290 | + | end) | |
| 291 | + | menu:addSeparator() | |
| 292 | + | menu:addOption(tr('Clear'), function() | |
| 293 | + | updateAction(action, {hotkey="", text="", item=0, count=1}) | |
| 294 | + | end) | |
| 295 | + | menu:display(mousePosition) | |
| 296 | + | return true | |
| 297 | + | elseif mouseButton == MouseLeftButton or mouseButton == MouseTouch2 or mouseButton == MouseTouch3 then | |
| 298 | + | action.callback() | |
| 299 | + | return true | |
| 300 | + | end | |
| 301 | + | return false | |
| 302 | + | end | |
| 303 | + | ||
| 304 | + | function actionOnItemChange(widget) | |
| 305 | + | updateAction(widget:getParent(), {text="", item=widget:getItemId(), count=widget:getItemCountOrSubType()}) | |
| 306 | + | end | |
| 307 | + | ||
| 308 | + | function onSpellCooldown(iconId, duration) | |
| 309 | + | for index, panel in ipairs({actionPanel1, actionPanel2}) do | |
| 310 | + | for i, child in ipairs(panel.tabBar:getChildren()) do | |
| 311 | + | if child.spell and child.spell.id == iconId then | |
| 312 | + | startCooldown(child, duration) | |
| 313 | + | end | |
| 314 | + | end | |
| 315 | + | end | |
| 316 | + | end | |
| 317 | + | ||
| 318 | + | function onSpellGroupCooldown(groupId, duration) | |
| 319 | + | for index, panel in ipairs({actionPanel1, actionPanel2}) do | |
| 320 | + | for i, child in ipairs(panel.tabBar:getChildren()) do | |
| 321 | + | if child.spell and child.spell.group then | |
| 322 | + | for group, duration in pairs(child.spell.group) do | |
| 323 | + | if groupId == group then | |
| 324 | + | startCooldown(child, duration) | |
| 325 | + | end | |
| 326 | + | end | |
| 327 | + | end | |
| 328 | + | end | |
| 329 | + | end | |
| 330 | + | end | |
| 331 | + | ||
| 332 | + | function startCooldown(action, duration) | |
| 333 | + | if type(action.cooldownTill) == 'number' and action.cooldownTill > g_clock.millis() + duration then | |
| 334 | + | return -- already has cooldown with greater duration | |
| 335 | + | end | |
| 336 | + | action.cooldownStart = g_clock.millis() | |
| 337 | + | action.cooldownTill = g_clock.millis() + duration | |
| 338 | + | updateCooldown(action) | |
| 339 | + | end | |
| 340 | + | ||
| 341 | + | function updateCooldown(action) | |
| 342 | + | if not action or not action.cooldownTill then return end | |
| 343 | + | local timeleft = action.cooldownTill - g_clock.millis() | |
| 344 | + | if timeleft <= 30 then | |
| 345 | + | action.cooldown:setPercent(100) | |
| 346 | + | action.cooldownEvent = nil | |
| 347 | + | return | |
| 348 | + | end | |
| 349 | + | local duration = action.cooldownTill - action.cooldownStart | |
| 350 | + | action.cooldown:setPercent(100 - math.floor(100 * timeleft / duration)) | |
| 351 | + | action.cooldownEvent = scheduleEvent(function() updateCooldown(action) end, 30) | |
| 352 | + | end | |
| 353 | + | ||
| 354 | + | function executeAction(action, ticks) | |
| 355 | + | if not action.config then return end | |
| 356 | + | if type(ticks) ~= 'number' then ticks = 0 end | |
| 357 | + | ||
| 358 | + | local actionDelay = 100 | |
| 359 | + | if ticks == 0 then | |
| 360 | + | actionDelay = 200 -- for first use | |
| 361 | + | elseif action.actionDelayTo ~= nil and g_clock.millis() < action.actionDelayTo then | |
| 362 | + | return | |
| 363 | + | end | |
| 364 | + | ||
| 365 | + | local actionType = action.config.actionType | |
| 366 | + | ||
| 367 | + | if type(action.config.text) == 'string' and action.config.text:len() > 0 then | |
| 368 | + | if g_app.isMobile() then -- turn to direction of targer | |
| 369 | + | local target = g_game.getAttackingCreature() | |
| 370 | + | if target then | |
| 371 | + | local pos = g_game.getLocalPlayer():getPosition() | |
| 372 | + | local tpos = target:getPosition() | |
| 373 | + | if pos and tpos then | |
| 374 | + | local offx = tpos.x - pos.x | |
| 375 | + | local offy = tpos.y - pos.y | |
| 376 | + | if offy < 0 and offx <= 0 and math.abs(offx) < math.abs(offy) then | |
| 377 | + | g_game.turn(Directions.North) | |
| 378 | + | elseif offy > 0 and offx >= 0 and math.abs(offx) < math.abs(offy) then | |
| 379 | + | g_game.turn(Directions.South) | |
| 380 | + | elseif offx < 0 and offy <= 0 and math.abs(offx) > math.abs(offy) then | |
| 381 | + | g_game.turn(Directions.West) | |
| 382 | + | elseif offx > 0 and offy >= 0 and math.abs(offx) > math.abs(offy) then | |
| 383 | + | g_game.turn(Directions.East) | |
| 384 | + | end | |
| 385 | + | end | |
| 386 | + | end | |
| 387 | + | end | |
| 388 | + | if modules.game_interface.isChatVisible() then | |
| 389 | + | modules.game_console.sendMessage(action.config.text) | |
| 390 | + | else | |
| 391 | + | g_game.talk(action.config.text) | |
| 392 | + | end | |
| 393 | + | action.actionDelayTo = g_clock.millis() + actionDelay | |
| 394 | + | elseif action.item:getItemId() > 0 then | |
| 395 | + | if actionType == ActionTypes.USE then | |
| 396 | + | if g_game.getClientVersion() < 780 then | |
| 397 | + | local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) | |
| 398 | + | if item then | |
| 399 | + | g_game.use(item) | |
| 400 | + | end | |
| 401 | + | else | |
| 402 | + | g_game.useInventoryItem(action.item:getItemId()) | |
| 403 | + | end | |
| 404 | + | action.actionDelayTo = g_clock.millis() + actionDelay | |
| 405 | + | elseif actionType == ActionTypes.USE_SELF then | |
| 406 | + | if g_game.getClientVersion() < 780 then | |
| 407 | + | local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) | |
| 408 | + | if item then | |
| 409 | + | g_game.useWith(item, g_game.getLocalPlayer()) | |
| 410 | + | end | |
| 411 | + | else | |
| 412 | + | g_game.useInventoryItemWith(action.item:getItemId(), g_game.getLocalPlayer(), action.item:getItemSubType() or -1) | |
| 413 | + | end | |
| 414 | + | action.actionDelayTo = g_clock.millis() + actionDelay | |
| 415 | + | elseif actionType == ActionTypes.USE_TARGET then | |
| 416 | + | local attackingCreature = g_game.getAttackingCreature() | |
| 417 | + | if not attackingCreature then | |
| 418 | + | local item = Item.create(action.item:getItemId()) | |
| 419 | + | if g_game.getClientVersion() < 780 then | |
| 420 | + | local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) | |
| 421 | + | if not tmpItem then return end | |
| 422 | + | item = tmpItem | |
| 423 | + | end | |
| 424 | + | ||
| 425 | + | modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) | |
| 426 | + | return | |
| 427 | + | end | |
| 428 | + | ||
| 429 | + | if not attackingCreature:getTile() then return end | |
| 430 | + | if g_game.getClientVersion() < 780 then | |
| 431 | + | local item = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) | |
| 432 | + | if item then | |
| 433 | + | g_game.useWith(item, attackingCreature, action.item:getItemSubType() or -1) | |
| 434 | + | end | |
| 435 | + | else | |
| 436 | + | g_game.useInventoryItemWith(action.item:getItemId(), attackingCreature, action.item:getItemSubType() or -1) | |
| 437 | + | end | |
| 438 | + | action.actionDelayTo = g_clock.millis() + actionDelay | |
| 439 | + | elseif actionType == ActionTypes.USE_WITH then | |
| 440 | + | local item = Item.create(action.item:getItemId()) | |
| 441 | + | if g_game.getClientVersion() < 780 then | |
| 442 | + | local tmpItem = g_game.findPlayerItem(action.item:getItemId(), action.item:getItemSubType() or -1) | |
| 443 | + | if not tmpItem then return true end | |
| 444 | + | item = tmpItem | |
| 445 | + | end | |
| 446 | + | modules.game_interface.startUseWith(item, action.item:getItemSubType() or - 1) | |
| 447 | + | elseif actionType == ActionTypes.EQUIP then | |
| 448 | + | if g_game.getClientVersion() >= 910 then | |
| 449 | + | local item = Item.create(action.item:getItemId()) | |
| 450 | + | g_game.equipItem(item) | |
| 451 | + | action.actionDelayTo = g_clock.millis() + actionDelay | |
| 452 | + | end | |
| 453 | + | end | |
| 454 | + | end | |
| 455 | + | end |
| @@ -0,0 +1,9 @@ | |||
| 1 | + | Module | |
| 2 | + | name: game_actionbar | |
| 3 | + | description: Action bar | |
| 4 | + | author: otclient@otclient.ovh | |
| 5 | + | website: otclient.ovh | |
| 6 | + | sandboxed: true | |
| 7 | + | scripts: [ actionbar ] | |
| 8 | + | @onLoad: init() | |
| 9 | + | @onUnload: terminate() |
| @@ -0,0 +1,145 @@ | |||
| 1 | + | ActionButton < Panel | |
| 2 | + | font: cipsoftFont | |
| 3 | + | anchors.top: parent.top | |
| 4 | + | anchors.bottom: parent.bottom | |
| 5 | + | width: 40 | |
| 6 | + | padding: 1 1 1 1 | |
| 7 | + | margin-left: 1 | |
| 8 | + | ||
| 9 | + | $first: | |
| 10 | + | anchors.left: parent.left | |
| 11 | + | ||
| 12 | + | $!first: | |
| 13 | + | anchors.left: prev.right | |
| 14 | + | ||
| 15 | + | Item | |
| 16 | + | id: item | |
| 17 | + | anchors.fill: parent | |
| 18 | + | &selectable: true | |
| 19 | + | &editable: false | |
| 20 | + | virtual: true | |
| 21 | + | border-width: 1 | |
| 22 | + | ||
| 23 | + | border-color: #00000000 | |
| 24 | + | ||
| 25 | + | $!on: | |
| 26 | + | image-source: /images/game/actionbarslot | |
| 27 | + | ||
| 28 | + | Label | |
| 29 | + | id: text | |
| 30 | + | anchors.fill: parent | |
| 31 | + | text-auto-resize: true | |
| 32 | + | text-wrap: true | |
| 33 | + | phantom: true | |
| 34 | + | text-align: center | |
| 35 | + | font: verdana-9px | |
| 36 | + | ||
| 37 | + | Label | |
| 38 | + | id: hotkeyLabel | |
| 39 | + | anchors.top: parent.top | |
| 40 | + | anchors.left: parent.left | |
| 41 | + | margin: 2 3 3 3 | |
| 42 | + | text-auto-resize: true | |
| 43 | + | text-wrap: false | |
| 44 | + | phantom: true | |
| 45 | + | font: small-9px | |
| 46 | + | color: yellow | |
| 47 | + | ||
| 48 | + | UIProgressRect | |
| 49 | + | id: cooldown | |
| 50 | + | background: #585858AA | |
| 51 | + | percent: 100 | |
| 52 | + | focusable: false | |
| 53 | + | phantom: true | |
| 54 | + | anchors.fill: parent | |
| 55 | + | margin: 1 1 1 1 | |
| 56 | + | ||
| 57 | + | Panel | |
| 58 | + | id: actionBar | |
| 59 | + | focusable: false | |
| 60 | + | image-source: /images/ui/panel_side | |
| 61 | + | image-border: 4 | |
| 62 | + | margin-top: -1 | |
| 63 | + | ||
| 64 | + | $on: | |
| 65 | + | height: 40 | |
| 66 | + | visible: true | |
| 67 | + | ||
| 68 | + | $!on: | |
| 69 | + | height: 0 | |
| 70 | + | visible: false | |
| 71 | + | ||
| 72 | + | TabButton | |
| 73 | + | id: prevButton | |
| 74 | + | icon: /images/game/console/leftarrow | |
| 75 | + | anchors.left: parent.left | |
| 76 | + | anchors.top: parent.top | |
| 77 | + | anchors.bottom: parent.bottom | |
| 78 | + | margin-left: 1 | |
| 79 | + | margin-top: 1 | |
| 80 | + | margin-bottom: 2 | |
| 81 | + | ||
| 82 | + | Panel | |
| 83 | + | id: tabBar | |
| 84 | + | anchors.top: parent.top | |
| 85 | + | anchors.bottom: parent.bottom | |
| 86 | + | anchors.left: prev.right | |
| 87 | + | anchors.right: next.left | |
| 88 | + | margin-right: 3 | |
| 89 | + | clipping: true | |
| 90 | + | ||
| 91 | + | TabButton | |
| 92 | + | id: nextButton | |
| 93 | + | icon: /images/game/console/rightarrow | |
| 94 | + | anchors.right: parent.right | |
| 95 | + | anchors.top: parent.top | |
| 96 | + | anchors.bottom: parent.bottom | |
| 97 | + | margin-right: 1 | |
| 98 | + | margin-top: 1 | |
| 99 | + | margin-bottom: 2 | |
| 100 | + | ||
| 101 | + | ||
| 102 | + | ActionAssignWindow < MainWindow | |
| 103 | + | id: assignWindow | |
| 104 | + | !text: tr('Button Assign') | |
| 105 | + | size: 360 150 | |
| 106 | + | @onEscape: self:destroy() | |
| 107 | + | ||
| 108 | + | Label | |
| 109 | + | !text: tr('Please, press the key you wish to use for action') | |
| 110 | + | anchors.left: parent.left | |
| 111 | + | anchors.right: parent.right | |
| 112 | + | anchors.top: parent.top | |
| 113 | + | text-auto-resize: true | |
| 114 | + | text-align: left | |
| 115 | + | ||
| 116 | + | Label | |
| 117 | + | id: comboPreview | |
| 118 | + | !text: tr('Current action hotkey: %s', 'none') | |
| 119 | + | anchors.horizontalCenter: parent.horizontalCenter | |
| 120 | + | anchors.top: prev.bottom | |
| 121 | + | margin-top: 10 | |
| 122 | + | text-auto-resize: true | |
| 123 | + | ||
| 124 | + | HorizontalSeparator | |
| 125 | + | id: separator | |
| 126 | + | anchors.left: parent.left | |
| 127 | + | anchors.right: parent.right | |
| 128 | + | anchors.bottom: next.top | |
| 129 | + | margin-bottom: 10 | |
| 130 | + | ||
| 131 | + | Button | |
| 132 | + | id: addButton | |
| 133 | + | !text: tr('Add') | |
| 134 | + | width: 64 | |
| 135 | + | anchors.right: next.left | |
| 136 | + | anchors.bottom: parent.bottom | |
| 137 | + | margin-right: 10 | |
| 138 | + | ||
| 139 | + | Button | |
| 140 | + | id: cancelButton | |
| 141 | + | !text: tr('Cancel') | |
| 142 | + | width: 64 | |
| 143 | + | anchors.right: parent.right | |
| 144 | + | anchors.bottom: parent.bottom | |
| 145 | + | @onClick: self:getParent():destroy() |