mirror of
https://github.com/mehah/otclient
synced 2026-08-15 16:29:06 -04:00
New Features: - Task Board (Bounty, Weekly, Shop) with Preferred & Soulseal modals, Kill Tracker, Offline Training dialog, Vocation tutorial, infobanner notifications, redesigned NPC chat/trade, and multi-slot action bar. Gameplay Enhancements: - Expanded Cyclopedia stats, talisman upgrades, weekly rewards/progress, soulseal fights, taskboard shop/offers and offline training actions. UI / Graphics: - Per-source spell-effect opacity sliders, status icon bar, HUD/scroll improvements, many new templates/styles, screenshot UI updates, and small label/icon fixes. Bug Fixes: - Miscellaneous UI behavior, layout and robustness improvements.
307 lines
9.6 KiB
Lua
307 lines
9.6 KiB
Lua
-- @docclass
|
|
UIHTML = extends(UIWidget, 'UIHTML')
|
|
|
|
-- public functions
|
|
function UIHTML.create()
|
|
local scrollarea = UIHTML.internalCreate()
|
|
scrollarea.inverted = false
|
|
scrollarea.alwaysScrollMaximum = false
|
|
return scrollarea
|
|
end
|
|
|
|
local function includeRectBounds(bounds, rect)
|
|
if not rect or rect.width <= 0 or rect.height <= 0 then
|
|
return bounds
|
|
end
|
|
|
|
local left = rect.x
|
|
local top = rect.y
|
|
local right = rect.x + rect.width
|
|
local bottom = rect.y + rect.height
|
|
|
|
if not bounds then
|
|
return { left = left, top = top, right = right, bottom = bottom }
|
|
end
|
|
|
|
if left < bounds.left then bounds.left = left end
|
|
if top < bounds.top then bounds.top = top end
|
|
if right > bounds.right then bounds.right = right end
|
|
if bottom > bounds.bottom then bounds.bottom = bottom end
|
|
return bounds
|
|
end
|
|
|
|
local function intersectRects(a, b)
|
|
if not a then
|
|
return b
|
|
end
|
|
if not b then
|
|
return a
|
|
end
|
|
|
|
local left = math.max(a.x, b.x)
|
|
local top = math.max(a.y, b.y)
|
|
local right = math.min(a.x + a.width, b.x + b.width)
|
|
local bottom = math.min(a.y + a.height, b.y + b.height)
|
|
if right <= left or bottom <= top then
|
|
return nil
|
|
end
|
|
|
|
return { x = left, y = top, width = right - left, height = bottom - top }
|
|
end
|
|
|
|
local DISPLAY_NONE = 0 -- DisplayType::None
|
|
|
|
local function collectContentBounds(widget, bounds, clipRect)
|
|
for _, child in pairs(widget:getChildren()) do
|
|
if child and not child:isDestroyed() and child:isExplicitlyVisible() and child:getDisplay() ~= DISPLAY_NONE then
|
|
local visibleRect = child:getMarginRect()
|
|
if clipRect then
|
|
visibleRect = intersectRects(visibleRect, clipRect)
|
|
end
|
|
|
|
bounds = includeRectBounds(bounds, visibleRect)
|
|
|
|
local nextClip = clipRect
|
|
if child:isClipping() then
|
|
local childClip = child:getPaddingRect()
|
|
nextClip = nextClip and intersectRects(nextClip, childClip) or childClip
|
|
end
|
|
|
|
if nextClip or not clipRect then
|
|
bounds = collectContentBounds(child, bounds, nextClip)
|
|
end
|
|
end
|
|
end
|
|
|
|
return bounds
|
|
end
|
|
|
|
local function computeContentExtent(widget)
|
|
local paddingRect = widget:getPaddingRect()
|
|
local bounds = collectContentBounds(widget, nil, nil)
|
|
local virtualOffset = widget:getVirtualOffset()
|
|
|
|
if not bounds then
|
|
return paddingRect.width, paddingRect.height
|
|
end
|
|
|
|
local paddingLeft = paddingRect.x
|
|
local paddingTop = paddingRect.y
|
|
local width = math.max((bounds.right + virtualOffset.x) - paddingLeft, paddingRect.width)
|
|
local height = math.max((bounds.bottom + virtualOffset.y) - paddingTop, paddingRect.height)
|
|
return width, height
|
|
end
|
|
|
|
function UIHTML:onStyleApply(styleName, styleNode)
|
|
for name, value in pairs(styleNode) do
|
|
if name == 'vertical-scrollbar' then
|
|
addEvent(function()
|
|
local parent = self:getParent()
|
|
if parent then
|
|
self:setVerticalScrollBar(parent:getChildById(value))
|
|
end
|
|
end)
|
|
elseif name == 'horizontal-scrollbar' then
|
|
addEvent(function()
|
|
local parent = self:getParent()
|
|
if parent then
|
|
self:setHorizontalScrollBar(self:getParent():getChildById(value))
|
|
end
|
|
end)
|
|
elseif name == 'inverted-scroll' then
|
|
self:setInvertedScroll(value)
|
|
elseif name == 'always-scroll-maximum' then
|
|
self:setAlwaysScrollMaximum(value)
|
|
end
|
|
end
|
|
end
|
|
|
|
function UIHTML:onVisibilityChange()
|
|
local scrollbar = self.verticalScrollBar
|
|
if scrollbar then
|
|
scrollbar:setDisplay(self:getDisplay())
|
|
scrollbar:setVisible(self:isVisible())
|
|
end
|
|
end
|
|
|
|
function UIHTML:updateScrollBars()
|
|
if not self.verticalScrollBar and not self.horizontalScrollBar then
|
|
return
|
|
end
|
|
|
|
local paddingRect = self:getPaddingRect()
|
|
local contentWidth, contentHeight = computeContentExtent(self)
|
|
local scrollWidth = math.max(contentWidth - paddingRect.width, 0)
|
|
local scrollHeight = math.max(contentHeight - paddingRect.height, 0)
|
|
|
|
local scrollbar = self.verticalScrollBar
|
|
if scrollbar then
|
|
if self.inverted then
|
|
scrollbar:setMinimum(-scrollHeight)
|
|
scrollbar:setMaximum(0)
|
|
else
|
|
scrollbar:setMinimum(0)
|
|
scrollbar:setMaximum(scrollHeight)
|
|
end
|
|
end
|
|
|
|
local scrollbar = self.horizontalScrollBar
|
|
if scrollbar then
|
|
if self.inverted then
|
|
scrollbar:setMinimum(-scrollWidth)
|
|
scrollbar:setMaximum(0)
|
|
else
|
|
scrollbar:setMinimum(0)
|
|
scrollbar:setMaximum(scrollWidth)
|
|
end
|
|
end
|
|
|
|
if self.lastScrollWidth ~= scrollWidth then
|
|
self:onScrollWidthChange()
|
|
end
|
|
if self.lastScrollHeight ~= scrollHeight then
|
|
self:onScrollHeightChange()
|
|
end
|
|
|
|
self.lastScrollWidth = scrollWidth
|
|
self.lastScrollHeight = scrollHeight
|
|
end
|
|
|
|
function UIHTML:setVerticalScrollBar(scrollbar)
|
|
self.verticalScrollBar = scrollbar
|
|
connect(self.verticalScrollBar, 'onValueChange', function(scrollbar, value)
|
|
local virtualOffset = self:getVirtualOffset()
|
|
virtualOffset.y = value
|
|
self._isScrollbarDrivenScroll = true
|
|
self:setVirtualOffset(virtualOffset)
|
|
self._isScrollbarDrivenScroll = false
|
|
signalcall(self.onScrollChange, self, virtualOffset)
|
|
end)
|
|
self:updateScrollBars()
|
|
end
|
|
|
|
function UIHTML:setHorizontalScrollBar(scrollbar)
|
|
self.horizontalScrollBar = scrollbar
|
|
connect(self.horizontalScrollBar, 'onValueChange', function(scrollbar, value)
|
|
local virtualOffset = self:getVirtualOffset()
|
|
virtualOffset.x = value
|
|
self._isScrollbarDrivenScroll = true
|
|
self:setVirtualOffset(virtualOffset)
|
|
self._isScrollbarDrivenScroll = false
|
|
signalcall(self.onScrollChange, self, virtualOffset)
|
|
end)
|
|
self:updateScrollBars()
|
|
end
|
|
|
|
function UIHTML:setInverted(inverted)
|
|
self.inverted = inverted
|
|
end
|
|
|
|
function UIHTML:setAlwaysScrollMaximum(value)
|
|
self.alwaysScrollMaximum = value
|
|
end
|
|
|
|
function UIHTML:onLayoutUpdate()
|
|
if self._isScrollbarDrivenScroll and self._skipScrollLayoutRecalc then
|
|
return
|
|
end
|
|
self:updateScrollBars()
|
|
end
|
|
|
|
function UIHTML:onMouseWheel(mousePos, mouseWheel)
|
|
if not self.verticalScrollBar or self.horizontalScrollBar then
|
|
return false
|
|
end
|
|
|
|
if self.verticalScrollBar then
|
|
if not self.verticalScrollBar:isOn() then
|
|
return false
|
|
end
|
|
if mouseWheel == MouseWheelUp then
|
|
local minimum = self.verticalScrollBar:getMinimum()
|
|
if self.verticalScrollBar:getValue() <= minimum then
|
|
return false
|
|
end
|
|
self.verticalScrollBar:decrement()
|
|
else
|
|
local maximum = self.verticalScrollBar:getMaximum()
|
|
if self.verticalScrollBar:getValue() >= maximum then
|
|
return false
|
|
end
|
|
self.verticalScrollBar:increment()
|
|
end
|
|
elseif self.horizontalScrollBar then
|
|
if not self.horizontalScrollBar:isOn() then
|
|
return false
|
|
end
|
|
if mouseWheel == MouseWheelUp then
|
|
local maximum = self.horizontalScrollBar:getMaximum()
|
|
if self.horizontalScrollBar:getValue() >= maximum then
|
|
return false
|
|
end
|
|
self.horizontalScrollBar:increment()
|
|
else
|
|
local minimum = self.horizontalScrollBar:getMinimum()
|
|
if self.horizontalScrollBar:getValue() <= minimum then
|
|
return false
|
|
end
|
|
self.horizontalScrollBar:decrement()
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function UIHTML:ensureChildVisible(child)
|
|
if child then
|
|
local paddingRect = self:getPaddingRect()
|
|
if self.verticalScrollBar then
|
|
local deltaY = paddingRect.y - child:getY()
|
|
if deltaY > 0 then
|
|
self.verticalScrollBar:decrement(deltaY)
|
|
end
|
|
|
|
deltaY = (child:getY() + child:getHeight()) - (paddingRect.y + paddingRect.height)
|
|
if deltaY > 0 then
|
|
self.verticalScrollBar:increment(deltaY)
|
|
end
|
|
elseif self.horizontalScrollBar then
|
|
local deltaX = paddingRect.x - child:getX()
|
|
if deltaX > 0 then
|
|
self.horizontalScrollBar:decrement(deltaX)
|
|
end
|
|
|
|
deltaX = (child:getX() + child:getWidth()) - (paddingRect.x + paddingRect.width)
|
|
if deltaX > 0 then
|
|
self.horizontalScrollBar:increment(deltaX)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function UIHTML:onChildFocusChange(focusedChild, oldFocused, reason)
|
|
if focusedChild and (reason == MouseFocusReason or reason == KeyboardFocusReason) then
|
|
self:ensureChildVisible(focusedChild)
|
|
end
|
|
end
|
|
|
|
function UIHTML:onScrollWidthChange()
|
|
if self.alwaysScrollMaximum and self.horizontalScrollBar then
|
|
self.horizontalScrollBar:setValue(self.horizontalScrollBar:getMaximum())
|
|
end
|
|
end
|
|
|
|
function UIHTML:onScrollHeightChange()
|
|
if self.alwaysScrollMaximum and self.verticalScrollBar then
|
|
self.verticalScrollBar:setValue(self.verticalScrollBar:getMaximum())
|
|
end
|
|
end
|
|
|
|
function UIHTML:setInvertedScroll(inverted)
|
|
-- 01/23/2026 "inverted-scroll: true" not working in css or html
|
|
-- [CSS].panelConsole { --inverted-scroll: true }
|
|
-- or
|
|
-- [HTML] style="overflow: scroll; inverted-scroll: true"
|
|
-- temp fix in [html]add <div inverted-scroll="true"></div>
|
|
self:setInverted(inverted)
|
|
self:updateScrollBars()
|
|
end
|