--Vetical Scrollbar Base ------------------------ local OFF_ALPHA = 0.5 local SCROLL_AREA_ALPHA = 0.8 local ON_ALPHA = 1 local STATE_CHANGE_DURATION = 250 local MIN_SCROLL_VALUE = 0 local MAX_SCROLL_VALUE = 100 local MAX_FADE_DISTANCE_UI = 64 local DEFAULT_Y_DISTANCE_FROM_EDGE_WHERE_SELECTION_CAUSES_SCROLL = 150 local NO_SELECTED_DATA = nil local NO_DATA_CONTROL = nil local RESELECTING_DURING_REBUILD = true local NOT_RESELECTING_DURING_REBUILD = false local ANIMATE_INSTANTLY = true local DONT_ANIMATE_INSTANTLY = false ZO_SCROLL_LIST_OPERATION_ADVANCE_CURSOR = "advance_cursor" ZO_SCROLL_LIST_OPERATION_LINE_BREAK = "line_break" ZO_SCROLL_MOVEMENT_DIRECTION_NEGATIVE = -1 ZO_SCROLL_MOVEMENT_DIRECTION_NONE = 0 ZO_SCROLL_MOVEMENT_DIRECTION_POSITIVE = 1 ZO_SCROLL_BUILD_DIRECTION_LEFT_TO_RIGHT = 1 ZO_SCROLL_BUILD_DIRECTION_RIGHT_TO_LEFT = -1 ZO_SCROLL_SELECT_CATEGORY_PREVIOUS = -1 ZO_SCROLL_SELECT_CATEGORY_NEXT = 1 ZO_SCROLL_BAR_WIDTH = 16 -- Used by both ZO_VerticalScrollbarBase and ZO_Scroll local function OnInteractWithScrollBar(self) if self.onInteractWithScrollbarCallback then self.onInteractWithScrollbarCallback() end end ---- -- Start of VerticalScrollbarBase functions ---- local function VerticalScrollbarBase_SetOnInteractWithScrollbarCallback(self, onInteractWithScrollbarCallback) self.onInteractWithScrollbarCallback = onInteractWithScrollbarCallback end function ZO_VerticalScrollbarBase_OnInitialized(self) self:SetMinMax(MIN_SCROLL_VALUE, MAX_SCROLL_VALUE) self:SetValue(MIN_SCROLL_VALUE) self.targetAlpha = OFF_ALPHA self:SetAlpha(self.targetAlpha) self.alphaAnimation, self.timeline = CreateSimpleAnimation(ANIMATION_ALPHA, self) self.alphaAnimation:SetDuration(STATE_CHANGE_DURATION) local function OnUpdate() if self.thumbHeld then OnInteractWithScrollBar(self) end end self:SetHandler("OnUpdate", OnUpdate) end local function UpdateAlpha(self) local newAlpha = OFF_ALPHA if self.areaOver then newAlpha = SCROLL_AREA_ALPHA end if self.thumbHeld or self.over then newAlpha = ON_ALPHA end if newAlpha ~= self:GetAlpha() then self.targetAlpha = newAlpha if self:IsHidden() then self:SetAlpha(newAlpha) else self.timeline:Stop() self.alphaAnimation:SetAlphaValues(self:GetAlpha(), newAlpha) self.timeline:PlayFromStart() end end end function ZO_VerticalScrollbarBase_OnMouseEnter(self) self.over = true UpdateAlpha(self) end function ZO_VerticalScrollbarBase_OnMouseExit(self) self.over = false UpdateAlpha(self) end function ZO_VerticalScrollbarBase_OnMouseDown(self) local thumb = self:GetThumbTextureControl() if MouseIsOver(thumb) then self.thumbHeld = true UpdateAlpha(self) end end function ZO_VerticalScrollbarBase_OnScrollBarArrowClicked(self) OnInteractWithScrollBar(self) end function ZO_VerticalScrollbarBase_OnMouseUp(self) self.thumbHeld = false UpdateAlpha(self) end function ZO_VerticalScrollbarBase_OnEffectivelyHidden(self) if self.timeline then self.timeline:Stop() end self:SetAlpha(self.targetAlpha) end function ZO_VerticalScrollbarBase_OnScrollAreaEnter(self) self.areaOver = true UpdateAlpha(self) end function ZO_VerticalScrollbarBase_OnScrollAreaExit(self) self.areaOver = false UpdateAlpha(self) end ---- -- End of VerticalScrollbarBase functions ---- local function CheckMouseInScrollArea(self) local inScrollArea = MouseIsOver(self) if inScrollArea ~= self.inScrollArea then self.inScrollArea = inScrollArea if inScrollArea then ZO_VerticalScrollbarBase_OnScrollAreaEnter(GetControl(self, "ScrollBar")) else ZO_VerticalScrollbarBase_OnScrollAreaExit(GetControl(self, "ScrollBar")) end end end function ZO_ScrollAreaBarBehavior_OnEffectivelyShown(self) self.inScrollArea = nil self:SetHandler("OnUpdate", CheckMouseInScrollArea) end function ZO_ScrollAreaBarBehavior_OnEffectivelyHidden(self) self:SetHandler("OnUpdate", nil) ZO_VerticalScrollbarBase_OnScrollAreaExit(GetControl(self, "ScrollBar")) end --Shared Scroll Animation Functions --------------------------------------- local SCROLL_ANIMATION_UNITS_PERCENT = 1 local SCROLL_ANIMATION_UNITS_REAL = 2 local SCROLL_ANIMATION_DEFAULT_DURATION_MS = 400 local function OnScrollAnimationUpdate(animationObject, progress) local scrollObject = animationObject.scrollObject local value = scrollObject.animationStart + (scrollObject.animationTarget - scrollObject.animationStart) * progress if scrollObject.animationUnits == SCROLL_ANIMATION_UNITS_REAL then local _, verticalExtents = scrollObject.scroll:GetScrollExtents() if verticalExtents > 0 then -- Store off raw offset if the value is out of the bounds of the extents -- and extents have not yet updated and animation progress is complete. if progress == 1 and verticalExtents < value then scrollObject.scrollRawOffset = value end value = MAX_SCROLL_VALUE * (value / verticalExtents) else value = 0 end end scrollObject.scrollbar:SetValue(value) end local function SetScrollOffset(self, targetOffset, animateInstantly, overrideDurationMS) local scroll = self.scroll local _, currentOffset = scroll:GetScrollOffsets() if zo_abs(targetOffset - currentOffset) > 0.001 then self.timeline:Stop() self.animationStart = currentOffset self.animationTarget = targetOffset self.animationUnits = SCROLL_ANIMATION_UNITS_REAL self.animation:SetDuration(overrideDurationMS or SCROLL_ANIMATION_DEFAULT_DURATION_MS) if animateInstantly then self.timeline:PlayInstantlyToEnd() else self.timeline:PlayFromStart() end elseif self.onScrollCompleteCallback then local SCROLL_ANIMATION_COMPLETE = true self.onScrollCompleteCallback(SCROLL_ANIMATION_COMPLETE) self.onScrollCompleteCallback = nil end end local function SetSliderValue(self, targetValue, animateInstantly, overrideDurationMS) if self.scrollbar then local startValue = self.scrollbar:GetValue() local scrollMin, scrollMax = self.scrollbar:GetMinMax() targetValue = zo_clamp(targetValue, scrollMin, scrollMax) if zo_abs(startValue - targetValue) > 0.001 then self.timeline:Stop() self.animationStart = startValue self.animationTarget = targetValue self.animationUnits = SCROLL_ANIMATION_UNITS_PERCENT self.animation:SetDuration(overrideDurationMS or SCROLL_ANIMATION_DEFAULT_DURATION_MS) if animateInstantly then self.timeline:PlayInstantlyToEnd() else self.timeline:PlayFromStart() end elseif self.onScrollCompleteCallback then local SCROLL_ANIMATION_COMPLETE = true self.onScrollCompleteCallback(SCROLL_ANIMATION_COMPLETE) self.onScrollCompleteCallback = nil end end end local function OnAnimationStop(animationObject, control, completedPlaying) local scrollObject = animationObject.scrollObject scrollObject.animationStart = nil scrollObject.animationTarget = nil if scrollObject.onScrollCompleteCallback then scrollObject.onScrollCompleteCallback(completedPlaying) scrollObject.onScrollCompleteCallback = nil end end local function CreateScrollAnimation(scrollObject) local animation, timeline = CreateSimpleAnimation(ANIMATION_CUSTOM) animation.scrollObject = scrollObject animation:SetEasingFunction(ZO_BezierInEase) animation:SetUpdateFunction(OnScrollAnimationUpdate) animation:SetHandler("OnStop", OnAnimationStop) return animation, timeline end --Shared Scroll Edge Fades ----------------------------------------------------------------- local function ComputeScrollFadeDistancesFromRealValues(sliderValue, sliderMin, sliderMax, maxFadeDistance) local topFadeDistance = zo_max(0, zo_min(sliderValue - sliderMin, maxFadeDistance)) local bottomFadeDistance = zo_max(0, zo_min(sliderMax - sliderValue, maxFadeDistance)) return topFadeDistance, bottomFadeDistance end local function ComputeScrollFadeDistancesFromPercentValues(sliderValue, verticalExtents, maxFadeDistance) local realSliderMin = 0 local realSliderMax = verticalExtents local realSliderValue = (sliderValue / MAX_SCROLL_VALUE) * verticalExtents return ComputeScrollFadeDistancesFromRealValues(realSliderValue, realSliderMin, realSliderMax, maxFadeDistance) end local function UpdateScrollFade(self, scroll, isPercent, sliderValue) if self.useFadeGradient then local slider = self.scrollbar sliderValue = sliderValue or slider:GetValue() local maxFadeDistance = ZO_Scroll_GetMaxFadeDistance(self) local topFadeDistance, bottomFadeDistance if isPercent then local _, verticalExtents = scroll:GetScrollExtents() topFadeDistance, bottomFadeDistance = ComputeScrollFadeDistancesFromPercentValues(sliderValue, verticalExtents, maxFadeDistance) else local sliderMin, sliderMax = slider:GetMinMax() topFadeDistance, bottomFadeDistance = ComputeScrollFadeDistancesFromRealValues(sliderValue, sliderMin, sliderMax, maxFadeDistance) end if topFadeDistance > 0 then scroll:SetFadeGradient(1, 0, 1, topFadeDistance * scroll:GetScale()) else scroll:SetFadeGradient(1, 0, 0, 0) end if bottomFadeDistance > 0 then scroll:SetFadeGradient(2, 0, -1, bottomFadeDistance * scroll:GetScale()) else scroll:SetFadeGradient(2, 0, 0, 0); end else scroll:SetFadeGradient(1, 0, 0, 0) scroll:SetFadeGradient(2, 0, 0, 0) end end --Scroll Control - Encapsulates a scroll control with a scrollbar ----------------------------------------------------------------- --Init local function ZO_ScrollUp_OnMouseDown(self) ZO_Scroll_ScrollRelative(self:GetParent():GetParent(), -40) end local function ZO_ScrollDown_OnMouseDown(self) ZO_Scroll_ScrollRelative(self:GetParent():GetParent(), 40) end local function ZO_Scroll_ScrollOrBarOnHeightChanged(scrollOrBarControl, newHeight) local scrollPane = scrollOrBarControl:GetParent() ZO_Scroll_UpdateScrollBar(scrollPane) end function ZO_Scroll_Initialize(self) self.scroll = GetControl(self, "Scroll") self.scroll:SetHandler("OnRectHeightChanged", ZO_Scroll_ScrollOrBarOnHeightChanged) self.scrollbar = GetControl(self, "ScrollBar") if self.scrollbar then self.scrollUpButton = GetControl(self.scrollbar, "Up") self.scrollUpButton:SetHandler("OnMouseDown", ZO_ScrollUp_OnMouseDown) self.scrollDownButton = GetControl(self.scrollbar, "Down") self.scrollDownButton:SetHandler("OnMouseDown", ZO_ScrollDown_OnMouseDown) self.scrollbar:SetHandler("OnRectHeightChanged", ZO_Scroll_ScrollOrBarOnHeightChanged) end self.isScrollBarEthereal = false self.useScrollbar = true self.hideScrollBarOnDisabled = true self.useFadeGradient = true self.animation, self.timeline = CreateScrollAnimation(self) ZO_Scroll_UpdateScrollBar(self) end --Scrolling functions function ZO_Scroll_ResetToTop(self) if self.timeline then self.timeline:Stop() end if self.scrollbar then self.scrollbar:SetValue(MIN_SCROLL_VALUE) end end function ZO_Scroll_ScrollAbsolute(self, value) local scroll = self.scroll local _, verticalExtents = scroll:GetScrollExtents() if verticalExtents > 0 then SetSliderValue(self, (value / verticalExtents) * MAX_SCROLL_VALUE) end end function ZO_Scroll_ScrollAbsoluteInstantly(self, value) local scroll = self.scroll local scrollbar = self.scrollbar local _, verticalExtents = scroll:GetScrollExtents() local targetValue = (value / verticalExtents) * MAX_SCROLL_VALUE local scrollMin, scrollMax = scrollbar:GetMinMax() targetValue = zo_clamp(targetValue, scrollMin, scrollMax) SetSliderValue(self, targetValue, ANIMATE_INSTANTLY) end function ZO_Scroll_ScrollRelative(self, verticalDelta) local scroll = self.scroll local _, verticalExtents = scroll:GetScrollExtents() if verticalExtents > 0 then if self.animationTarget then local oldVerticalOffset if self.animationUnits == SCROLL_ANIMATION_UNITS_PERCENT then oldVerticalOffset = (self.animationTarget * verticalExtents) / MAX_SCROLL_VALUE else oldVerticalOffset = self.animationTarget end local newVerticalOffset = oldVerticalOffset + verticalDelta SetSliderValue(self, (newVerticalOffset / verticalExtents) * MAX_SCROLL_VALUE) else local _, currentVerticalOffset = scroll:GetScrollOffsets() local newVerticalOffset = currentVerticalOffset + verticalDelta SetSliderValue(self, (newVerticalOffset / verticalExtents) * MAX_SCROLL_VALUE) end end end function ZO_Scroll_MoveWindow(self, value) local scroll = self.scroll local _, verticalExtents = scroll:GetScrollExtents() scroll:SetVerticalScroll((value/MAX_SCROLL_VALUE) * verticalExtents) ZO_Scroll_UpdateScrollBar(self) end local SCROLL_TO_SIDE_TOP = 1 local SCROLL_TO_SIDE_BOTTOM = 2 function ZO_Scroll_GetScrollDistanceToControl(self, otherControl, scrollToSide) local scroll = self.scroll local scrollTop = scroll:GetTop() local scrollBottom = scroll:GetBottom() local controlTop = otherControl:GetTop() local controlBottom = otherControl:GetBottom() local scrollDistance = 0 if scrollToSide == SCROLL_TO_SIDE_TOP then -- The control's top is above the top edge of the scroll, must scroll up to fully contain the control. scrollDistance = controlTop - scrollTop elseif scrollToSide == SCROLL_TO_SIDE_BOTTOM then -- The control's bottom is below the bottom edge of the scroll, must scroll down to fully contain the control. scrollDistance = controlBottom - scrollBottom end if scrollDistance ~= 0 and self.useFadeGradient then local _, verticalOffset = scroll:GetScrollOffsets() local _, verticalExtents = scroll:GetScrollExtents() local verticalOffsetAfterScroll = zo_clamp(verticalOffset + scrollDistance, 0, verticalExtents) local topFadeDistance, bottomFadeDistance = ComputeScrollFadeDistancesFromRealValues(verticalOffsetAfterScroll, 0, verticalExtents, ZO_Scroll_GetMaxFadeDistance(self)) if scrollToSide == SCROLL_TO_SIDE_TOP then -- divide by 2 for effect scrollDistance = scrollDistance - topFadeDistance * 0.5 else -- divide by 2 for effect scrollDistance = scrollDistance + bottomFadeDistance * 0.5 end end return scrollDistance end function ZO_Scroll_ScrollToControl(self, otherControl) local scroll = self.scroll local scrollTop = scroll:GetTop() local scrollBottom = scroll:GetBottom() local controlTop = otherControl:GetTop() local controlBottom = otherControl:GetBottom() local scrollToSide if controlTop < scrollTop then -- The control's top is above the top edge of the scroll, must scroll up to fully contain the control. scrollToSide = SCROLL_TO_SIDE_TOP elseif controlBottom > scrollBottom then -- The control's bottom is below the bottom edge of the scroll, must scroll down to fully contain the control. scrollToSide = SCROLL_TO_SIDE_BOTTOM end local scrollDistance = ZO_Scroll_GetScrollDistanceToControl(self, otherControl, scrollToSide) if scrollDistance ~= 0 then ZO_Scroll_ScrollRelative(self, scrollDistance) end end function ZO_Scroll_ScrollControlToTop(self, otherControl) local scrollDistance = ZO_Scroll_GetScrollDistanceToControl(self, otherControl, SCROLL_TO_SIDE_TOP) if scrollDistance ~= 0 then ZO_Scroll_ScrollRelative(self, scrollDistance) end end function ZO_Scroll_ScrollControlToBottom(self, otherControl) local scrollDistance = ZO_Scroll_GetScrollDistanceToControl(self, otherControl, SCROLL_TO_SIDE_BOTTOM) if scrollDistance ~= 0 then ZO_Scroll_ScrollRelative(self, scrollDistance) end end function ZO_Scroll_IsControlFullyInView(self, control) local scroll = self.scroll local scrollTop = scroll:GetTop() local scrollBottom = scroll:GetBottom() local controlTop = control:GetTop() local controlBottom = control:GetBottom() return controlTop >= scrollTop and controlBottom <= scrollBottom end function ZO_Scroll_ScrollControlIntoView(self, otherControl) local scroll = self.scroll local scrollTop = scroll:GetTop() local scrollBottom = scroll:GetBottom() local controlTop = otherControl:GetTop() local controlBottom = otherControl:GetBottom() if controlTop < scrollTop then ZO_Scroll_ScrollRelative(self, controlTop - scrollTop) elseif controlBottom > scrollBottom then ZO_Scroll_ScrollRelative(self, controlBottom - scrollBottom) end end function ZO_Scroll_ScrollControlIntoCentralView(self, otherControl, scrollInstantly) local scroll = self.scroll local _, verticalExtents = scroll:GetScrollExtents() local _, verticalOffset = scroll:GetScrollOffsets() local scrollTop = scroll:GetTop() local scrollBottom = scroll:GetBottom() local controlTop = otherControl:GetTop() local controlBottom = otherControl:GetBottom() local halfControlHeight = (controlBottom - controlTop) * 0.5 local halfScrollHeight = (scrollBottom - scrollTop) * 0.5 local scrollDistance = controlTop + halfControlHeight - scrollTop - halfScrollHeight local scrollToValue = zo_clamp(verticalOffset + scrollDistance, 0, verticalExtents) if scrollInstantly then ZO_Scroll_ResetToTop(self) local targetValue = (scrollToValue / verticalExtents) * MAX_SCROLL_VALUE SetSliderValue(self, targetValue, scrollInstantly) else ZO_Scroll_ScrollAbsolute(self, scrollToValue) end end function ZO_Scroll_SetScrollToRealOffsetAccountingForGradients(self, finalTotalHeight, controlFinalTopOffset, durationMS) local scroll = self.scroll local scrollHeight = scroll:GetHeight() local finalVerticalExtents = zo_max(finalTotalHeight - scrollHeight, 0) local finalVerticalOffset = controlFinalTopOffset if self.useFadeGradient then local topGradientHeight = ComputeScrollFadeDistancesFromRealValues(finalVerticalOffset, 0, finalVerticalExtents, ZO_Scroll_GetMaxFadeDistance(self)) -- divide by 2 for effect finalVerticalOffset = finalVerticalOffset - topGradientHeight * 0.5 end finalVerticalOffset = zo_clamp(finalVerticalOffset, 0, finalVerticalExtents) if durationMS == 0 then SetScrollOffset(self, finalVerticalOffset, ANIMATE_INSTANTLY) else SetScrollOffset(self, finalVerticalOffset, DONT_ANIMATE_INSTANTLY, durationMS) end end --Scroll update functions function ZO_Scroll_OnExtentsChanged(self) if self and self.scroll and not self.targetControl then ZO_Scroll_UpdateScrollBar(self) end end function ZO_Scroll_OnMouseWheel(self, delta) ZO_Scroll_ScrollRelative(self, -delta * 40) OnInteractWithScrollBar(self) end function ZO_Scroll_SetOnInteractWithScrollbarCallback(self, onInteractWithScrollbarCallback) self.onInteractWithScrollbarCallback = onInteractWithScrollbarCallback if self.scrollbar then VerticalScrollbarBase_SetOnInteractWithScrollbarCallback(self.scrollbar, onInteractWithScrollbarCallback) end end function ZO_Scroll_UpdateScrollBar(self, forceUpdateBarValue) local scroll = self.scroll local _, verticalOffset = scroll:GetScrollOffsets() local _, verticalExtents = scroll:GetScrollExtents() local scrollEnabled = verticalExtents > 0 or verticalOffset > 0 local scrollbar = self.scrollbar local scrollIndicator = self.scrollIndicator local scrollbarHidden = not self.useScrollbar or (self.hideScrollBarOnDisabled and not scrollEnabled) or self.isScrollBarEthereal local verticalExtentsChanged = self.verticalExtents ~= nil and not zo_floatsAreEqual(self.verticalExtents, verticalExtents) self.verticalExtents = verticalExtents if scrollbar then --thumb resizing local scale = scroll:GetScale() local scrollBarHeight = scrollbar:GetHeight() / scale local scrollAreaHeight = scroll:GetHeight() / scale if verticalExtents > 0 and scrollBarHeight >= 0 and scrollAreaHeight >= 0 then local thumbHeight = scrollBarHeight * scrollAreaHeight /(verticalExtents + scrollAreaHeight) scrollbar:SetThumbTextureHeight(thumbHeight) else scrollbar:SetThumbTextureHeight(scrollBarHeight) end --set mouse input enabled based on scrollability scroll:SetMouseEnabled(scrollEnabled) --auto scroll bar hiding local wasHidden = scrollbar:IsHidden() scrollbar:SetHidden(scrollbarHidden) local maxScrollValue = (not scrollbarHidden or self.isScrollBarEthereal) and MAX_SCROLL_VALUE or MIN_SCROLL_VALUE scrollbar:SetMinMax(MIN_SCROLL_VALUE, maxScrollValue) if wasHidden and not scrollbarHidden and scrollbar.resetScrollbarOnShow then ZO_Scroll_ResetToTop(self) self.scrollValue = MIN_SCROLL_VALUE end --update the scrollBar value when the extents changes or explicitly ask to if verticalExtentsChanged or forceUpdateBarValue then if verticalExtents > 0 then local previousScrollBarValue = scrollbar:GetValue() verticalOffset = self.scrollRawOffset and self.scrollRawOffset or verticalOffset local finalValue = MAX_SCROLL_VALUE * (verticalOffset / verticalExtents) if not zo_floatsAreEqual(previousScrollBarValue, finalValue) then scrollbar:SetValue(finalValue) end -- If the previous and current scrollBar values are the same, -- the onValueChanged function will not be invoked to update the scroll child position. -- We need to explicitly call to recalculate our scroll child position since our extents changed. if previousScrollBarValue == scrollbar:GetValue() then scroll:SetVerticalScroll(verticalOffset) end else ZO_Scroll_ResetToTop(self) end end self.scrollRawOffset = nil local IS_PERCENT = true UpdateScrollFade(self, scroll, IS_PERCENT) elseif scrollIndicator then --auto scroll indicator hiding local wasHidden = scrollIndicator:IsHidden() scrollIndicator:SetHidden(scrollbarHidden) if wasHidden and not scrollbarHidden then ZO_Scroll_ResetToTop(self) self.scrollValue = 0 end --extents updating if verticalExtentsChanged then if verticalExtents <= 0 then ZO_Scroll_ResetToTop(self) end end ZO_UpdateScrollFade(self.useFadeGradient, scroll, ZO_SCROLL_DIRECTION_VERTICAL) end end --Visual Config function ZO_Scroll_GetScrollIndicator(self) return self.scrollIndicator end function ZO_Scroll_SetScrollbarEthereal(self, isEthereal) if self.isScrollBarEthereal ~= isEthereal then self.isScrollBarEthereal = isEthereal ZO_Scroll_UpdateScrollBar(self) end end function ZO_Scroll_SetHideScrollbarOnDisable(self, hide) self.hideScrollBarOnDisabled = hide ZO_Scroll_UpdateScrollBar(self) end function ZO_Scroll_SetUseScrollbar(self, useScrollbar) self.useScrollbar = useScrollbar ZO_Scroll_UpdateScrollBar(self) end function ZO_Scroll_SetUseFadeGradient(self, useFadeGradient) self.useFadeGradient = useFadeGradient end function ZO_Scroll_SetMaxFadeDistance(self, maxFadeDistance) self.maxFadeDistanceUI = maxFadeDistance end function ZO_Scroll_GetMaxFadeDistance(self) return self.maxFadeDistanceUI or MAX_FADE_DISTANCE_UI end function ZO_Scroll_SetupGutterTexture(self, textureControl) textureControl:ClearAnchors() textureControl:SetAnchor(TOPLEFT, self.scrollUpButton, BOTTOMLEFT, 0, 0) textureControl:SetAnchor(BOTTOMRIGHT, self.scrollDownButton, TOPRIGHT, 0, 0) textureControl:SetParent(self.scrollbar) self.gutter = textureControl ZO_Scroll_UpdateScrollBar(self) end function ZO_Scroll_SetResetScrollbarOnShow(self, resetOnShow) self.resetScrollbarOnShow = resetOnShow end --Scroll List Control --A scrollable list of controls that reuses controls as they scroll out of view. --Use this control when you have a very large number of a couple different types of controls to display. --To use: --(1) Add a scroll list to your XML. --(2) Add data types to the scroll list, one for each type of control. A data type includes an XML control template, a height, and a callback that can setup the control given data. --(3) Add data to the scroll list. First, use GetDataList to get the table holding the data. Next, use CreateDataEntry to create a list element of a certain data type. You may pass -- in an arbitrary piece of data that will be given to the setup callback when the control is shown. Once you have made as many data entries as you need, add them to the data -- list in any way you want. Finally, call Commit to update the scroll list with your data. -- Note: The scroll list can use faster update logic if all controls are the same height. ------------------------------------------------------------------------------------------------------------- local SCROLL_LIST_UNIFORM = 1 local SCROLL_LIST_NON_UNIFORM = 2 local SCROLL_LIST_OPERATIONS = 3 local NO_HEIGHT_SET = -1 local function ZO_ScrollListUp_OnMouseDown(self) ZO_ScrollList_ScrollRelative(self:GetParent():GetParent(), -40) end local function ZO_ScrollListDown_OnMouseDown(self) ZO_ScrollList_ScrollRelative(self:GetParent():GetParent(), 40) end function ZO_ScrollList_Initialize(self) self.dataTypes = {} self.data = {} self.offset = 0 self.yDistanceFromEdgeWhereSelectionCausesScroll = DEFAULT_Y_DISTANCE_FROM_EDGE_WHERE_SELECTION_CAUSES_SCROLL self.activeControls = {} self.visibleData = {} self.categories = {} self.mode = SCROLL_LIST_UNIFORM self.buildDirection = ZO_SCROLL_BUILD_DIRECTION_LEFT_TO_RIGHT self.uniformControlHeight = NO_HEIGHT_SET self.highlightLocked = false self.highlightedControl = nil self.highlightCallback = nil self.pendingHighlightControl = nil self.selectedControl = nil self.selectedData = nil self.selectedDataIndex = nil self.lastSelectedDataIndex = nil self.selectionDataTypes = nil self.deselectOnReselect = true self.autoSelect = false self.contents = GetControl(self, "Contents") self.scrollbar = GetControl(self, "ScrollBar") self.upButton = GetControl(self.scrollbar, "Up") self.upButton:SetHandler("OnMouseDown", ZO_ScrollListUp_OnMouseDown) self.downButton = GetControl(self.scrollbar, "Down") self.downButton:SetHandler("OnMouseDown", ZO_ScrollListDown_OnMouseDown) self.scrollbar:SetEnabled(false) self.animation, self.timeline = CreateScrollAnimation(self) self.isScrollBarEthereal = false self.hideScrollBarOnDisabled = true self.useScrollbar = true self.useFadeGradient = true ZO_ScrollList_Commit(self) end function ZO_ScrollList_SetYDistanceFromEdgeWhereSelectionCausesScroll(self, yDistanceFromEdgeWhereSelectionCausesScroll) self.yDistanceFromEdgeWhereSelectionCausesScroll = yDistanceFromEdgeWhereSelectionCausesScroll end function ZO_ScrollList_SetHeight(self, height) self:SetHeight(height) end function ZO_ScrollList_GetHeight(self) return self:GetHeight() end local function AreSelectionsEnabled(self) if self.selectionTemplate or self.selectionCallback then return true else return false end end function ZO_ScrollList_AddResizeOnScreenResize(self) local function OnScreenResized() ZO_ScrollList_SetHeight(self, self:GetHeight()) ZO_ScrollList_Commit(self) end self:RegisterForEvent(EVENT_SCREEN_RESIZED, OnScreenResized) end do local function OnRectHeightChanged(self, newHeight) if self:IsHidden() then self:SetHandler("OnUpdate", function() self:SetHandler("OnUpdate", nil, "HeightChanged") ZO_ScrollList_Commit(self) end, "HeightChanged") else ZO_ScrollList_Commit(self) end end function ZO_ScrollList_AddCommitOnHeightChange(self) self:SetHandler("OnRectHeightChanged", OnRectHeightChanged) end end local function UpdateModeFromHeight(self, height) if self.mode == SCROLL_LIST_UNIFORM then if self.uniformControlHeight == NO_HEIGHT_SET then self.uniformControlHeight = height elseif height ~= self.uniformControlHeight then self.uniformControlHeight = nil self.mode = SCROLL_LIST_NON_UNIFORM ZO_ScrollList_Commit(self) end end end --Adds a new control type for the list to handle. It must maintain a consistent size. --@typeId - A unique identifier to give to CreateDataEntry when you want to add an element of this type. --@templateName - The name of the virtual control template that will be used to hold this data --@height - The control height --@setupCallback - The function that will be called when a control of this type becomes visible. Signature: setupCallback(control, data) --@dataTypeSelectSound - An optional sound to play when a row of this data type is selected. --@resetControlCallback - An optional callback when the datatype control gets reset. function ZO_ScrollList_AddDataType(self, typeId, templateName, height, setupCallback, hideCallback, dataTypeSelectSound, resetControlCallback) if internalassert(not self.dataTypes[typeId], "Data type already registered to scroll list") then local factoryFunction = function(objectPool) return ZO_ObjectPool_CreateNamedControl(string.format("%s%dRow", self:GetName(), typeId), templateName, objectPool, self.contents) end local pool = ZO_ObjectPool:New(factoryFunction, resetControlCallback or ZO_ObjectPool_DefaultResetControl) self.dataTypes[typeId] = { height = height, setupCallback = setupCallback, hideCallback = hideCallback, pool = pool, selectSound = dataTypeSelectSound, selectable = true, } --automatically choose the scrolling logic based on if the controls are all the same height or not UpdateModeFromHeight(self, height) end end -- Scroll List Construction Operations -- local function GetLineBreakPositions(layoutInfo, currentX, currentY, lineBreakAmount, indentX) indentX = indentX or 0 currentX = layoutInfo.startPos + indentX * layoutInfo.direction currentY = currentY + lineBreakAmount + layoutInfo.lineBreakModifier layoutInfo.lineBreakModifier = 0 return currentX, currentY end -- ZO_ScrollListOperation -- ZO_ScrollListOperation = ZO_Object:Subclass() function ZO_ScrollListOperation:New() local operation = ZO_Object.New(self) operation:Initialize() return operation end function ZO_ScrollListOperation:Initialize() self.selectable = false end function ZO_ScrollListOperation:IsDataVisible(data) return true -- Can be overridden in derived classes end function ZO_ScrollListOperation:GetControlWidth() return nil -- Can be overridden in derived classes end function ZO_ScrollListOperation:GetControlHeight() return nil -- Can be overridden in derived classes end function ZO_ScrollListOperation:GetPositionsAndAdvance(layoutInfo, currentX, currentY, data) assert(false) -- Override in derived classes end function ZO_ScrollListOperation:AddToScrollContents(contents, control, offset) assert(false) -- Override in derived classes end -- ZO_ScrollList_AdvanceCursor_Operation -- ZO_ScrollList_AdvanceCursor_Operation = ZO_ScrollListOperation:Subclass() function ZO_ScrollList_AdvanceCursor_Operation:New() return ZO_ScrollListOperation.New(self) end function ZO_ScrollList_AdvanceCursor_Operation:GetPositionsAndAdvance(layoutInfo, currentX, currentY, data) local instanceData = data.data data.top = currentY data.bottom = currentY + instanceData.moveY data.left = currentX data.right = currentX + instanceData.moveX * layoutInfo.direction currentX = data.right currentY = currentY + instanceData.moveY return currentX, currentY end function ZO_ScrollList_AdvanceCursor_Operation:AddToScrollContents(contents, control, offset) assert(false) -- Cannot add this operation to contents end -- ZO_ScrollList_LineBreak_Operation -- ZO_ScrollList_LineBreak_Operation = ZO_ScrollListOperation:Subclass() function ZO_ScrollList_LineBreak_Operation:New() return ZO_ScrollListOperation.New(self) end function ZO_ScrollList_LineBreak_Operation:GetPositionsAndAdvance(layoutInfo, currentX, currentY, data) local instanceData = data.data data.top = currentY data.bottom = currentY + instanceData.lineBreakAmount + layoutInfo.lineBreakModifier if layoutInfo.startPos < layoutInfo.endPos then data.left = layoutInfo.startPos data.right = layoutInfo.endPos else data.left = layoutInfo.endPos data.right = layoutInfo.startPos end local indentX = instanceData.indentX or 0 currentX, currentY = GetLineBreakPositions(layoutInfo, currentX, currentY, instanceData.lineBreakAmount, indentX) return currentX, currentY end function ZO_ScrollList_LineBreak_Operation:AddToScrollContents(contents, control, offset) assert(false) -- Cannot add a line break to contents end -- ZO_ScrollList_AddControl_Operation -- ZO_ScrollList_AddControl_Operation = ZO_ScrollListOperation:Subclass() function ZO_ScrollList_AddControl_Operation:New() return ZO_ScrollListOperation.New(self) end function ZO_ScrollList_AddControl_Operation:Initialize() ZO_ScrollListOperation.Initialize(self) -- Defined set of actions this operation can use self.controlWidth = 0 self.controlHeight = 0 self.spacingX = 0 self.spacingY = 0 self.selectable = true self.templateName = nil self.pool = nil self.showCallback = nil self.hideCallback = nil self.onSelectSound = nil end function ZO_ScrollList_AddControl_Operation:SetSpacingValues(spacingX, spacingY) self.spacingX = spacingX self.spacingY = spacingY end -- A controlWidth of nil will cause controls to be added Anchor TOPLEFT Anchor TOPRIGHT to fill the horizontal space of the parent control function ZO_ScrollList_AddControl_Operation:SetControlTemplate(templateName, parentControl, operationId, controlWidth, controlHeight, resetControlCallback) if self.templateName then return end self.controlWidth = controlWidth self.controlHeight = controlHeight self.templateName = templateName local factoryFunction = function(objectPool) local controlName = string.format("%s%dControl", parentControl:GetName(), operationId) return ZO_ObjectPool_CreateNamedControl(controlName, templateName, objectPool, parentControl) end self.pool = ZO_ObjectPool:New(factoryFunction, resetControlCallback or ZO_ObjectPool_DefaultResetControl) end function ZO_ScrollList_AddControl_Operation:SetSelectable(isSelectable) self.selectable = isSelectable end function ZO_ScrollList_AddControl_Operation:SetIndentAmount(indentX) self.indentX = indentX end function ZO_ScrollList_AddControl_Operation:SetOnSelectedSound(onSelectSound) self.onSelectSound = onSelectSound end function ZO_ScrollList_AddControl_Operation:IsDataVisible(data) if self.visibilityFunction then return self.visibilityFunction(data) end return true end function ZO_ScrollList_AddControl_Operation:SetScrollUpdateCallbacks(setupCallback, hideCallback) self.setupCallback = setupCallback self.hideCallback = hideCallback end function ZO_ScrollList_AddControl_Operation:GetPositionsAndAdvance(layoutInfo, currentX, currentY, dataEntry) local instanceData = dataEntry.data local controlWidth = self:GetControlWidth(instanceData, layoutInfo) local controlHeight = self:GetControlHeight(instanceData) local controlEndPos = currentX * layoutInfo.direction + controlWidth local lineBreakAfterControl = false if controlEndPos > layoutInfo.endPos then currentX, currentY = GetLineBreakPositions(layoutInfo, currentX, currentY, self.spacingY, self.indentX) elseif zo_floatsAreEqual(controlEndPos, layoutInfo.endPos) then lineBreakAfterControl = true end -- Calculate left and right based on the direction we are told to advance -- Since we want Left to always be less than Right, and having Right_To_Left -- build direction will cause Left to be greater than Right -- we need to compensate for that fact in this case, but keep the calculation the same -- when we are in the direction of Left_To_Right local halfControlWidth = controlWidth / 2 dataEntry.left = currentX - halfControlWidth + halfControlWidth * layoutInfo.direction dataEntry.right = currentX + halfControlWidth + halfControlWidth * layoutInfo.direction dataEntry.top = currentY dataEntry.bottom = currentY + controlHeight currentX = currentX + (controlWidth + self.spacingX) * layoutInfo.direction layoutInfo.lineBreakModifier = zo_max(layoutInfo.lineBreakModifier, controlHeight) if lineBreakAfterControl then currentX, currentY = GetLineBreakPositions(layoutInfo, currentX, currentY, self.spacingY, self.indentX) end return currentX, currentY end function ZO_ScrollList_AddControl_Operation:AddToScrollContents(contents, control, currentX, currentY, offset) control:ClearAnchors() local xOffset = currentX local yOffset = currentY - offset if self.controlWidth then control:SetAnchor(TOPLEFT, contents, TOPLEFT, xOffset, yOffset) else control:SetAnchor(TOPLEFT, contents, TOPLEFT, xOffset, yOffset) control:SetAnchor(TOPRIGHT, contents, TOPRIGHT, xOffset, yOffset) end end function ZO_ScrollList_AddControl_Operation:GetControlWidth(instanceData, layoutInfo) if self.controlWidth then if type(self.controlWidth) == "function" then return self.controlWidth(instanceData) else return self.controlWidth end else if layoutInfo then return zo_abs(layoutInfo.endPos - layoutInfo.startPos) else -- Nil width (i.e.: fill) requires derivitive layoutInfo to calculate. -- If we're not calling this from a place to knows that information, -- just return nil to denote that the size is "fill" but we can't calculate it in this context return nil end end end function ZO_ScrollList_AddControl_Operation:GetControlHeight(instanceData) if type(self.controlHeight) == "function" then return self.controlHeight(instanceData) else return self.controlHeight end end -- ZO_ScrollList_AddControl_Centered_Operation -- ZO_ScrollList_AddControl_Centered_Operation = ZO_ScrollList_AddControl_Operation:Subclass() function ZO_ScrollList_AddControl_Centered_Operation:New() return ZO_ScrollList_AddControl_Operation.New(self) end function ZO_ScrollList_AddControl_Centered_Operation:AddToScrollContents(contents, control, currentX, currentY, offset) assert(self.controlWidth) -- Nil width not supported for centered operations control:ClearAnchors() local xOffset = currentX local yOffset = currentY - offset if self.controlWidth then local instanceData = ZO_ScrollList_GetData(control) control:SetAnchor(CENTER, contents, TOPLEFT, xOffset + self:GetControlWidth(instanceData) / 2, yOffset + self:GetControlHeight(instanceData) / 2) else control:SetAnchor(TOPLEFT, contents, TOPLEFT, xOffset, yOffset) control:SetAnchor(TOPRIGHT, contents, TOPRIGHT, xOffset, yOffset) end end local GetDataTypeInfo do local advanceCursorOperation = ZO_ScrollList_AdvanceCursor_Operation:New() local lineBreakOperation = ZO_ScrollList_LineBreak_Operation:New() function GetDataTypeInfo(self, typeId) if typeId == ZO_SCROLL_LIST_OPERATION_ADVANCE_CURSOR then return advanceCursorOperation elseif typeId == ZO_SCROLL_LIST_OPERATION_LINE_BREAK then return lineBreakOperation end return self.dataTypes[typeId] end end function ZO_ScrollList_SetBuildDirection(self, buildDirection) self.buildDirection = buildDirection end -- If items at the end of the scroll are not selectable, max out the scroll in that direction. function ZO_ScrollList_SetScrollToExtent(self, scrollToExtent) self.scrollToExtent = scrollToExtent end -- A controlWidth of nil will cause controls to be added Anchor TOPLEFT Anchor TOPRIGHT to fill the horizontal space of the parent control function ZO_ScrollList_AddControlOperation(self, operationId, templateName, controlWidth, controlHeight, resetControlCallback, showCallback, hideCallback, spacingX, spacingY, indentX, selectable, centerEntries) if not self.dataTypes[operationId] then local operation = centerEntries and ZO_ScrollList_AddControl_Centered_Operation:New() or ZO_ScrollList_AddControl_Operation:New() operation:SetSpacingValues(spacingX, spacingY) operation:SetControlTemplate(templateName, self.contents, operationId, controlWidth, controlHeight, resetControlCallback) operation:SetScrollUpdateCallbacks(showCallback, hideCallback) operation:SetSelectable(selectable) operation:SetIndentAmount(indentX) self.dataTypes[operationId] = operation self.mode = SCROLL_LIST_OPERATIONS end end function ZO_ScrollList_GetDataTypeTable(self, typeId) return self.dataTypes[typeId] end function ZO_ScrollList_UpdateDataTypeHeight(self, typeId, newHeight) local dataTable = ZO_ScrollList_GetDataTypeTable(self, typeId) if dataTable and dataTable.height ~= newHeight then dataTable.height = newHeight UpdateModeFromHeight(self, newHeight) end end function ZO_ScrollList_SetTypeSelectable(self, typeId, selectable) self.dataTypes[typeId].selectable = selectable end function ZO_ScrollList_SetTypeCategoryHeader(self, typeId, isHeader) self.dataTypes[typeId].categoryHeader = isHeader end function ZO_ScrollList_SetEqualityFunction(self, typeId, equalityFunction) self.dataTypes[typeId].equalityFunction = equalityFunction end function ZO_ScrollList_SetVisibilityFunction(self, typeId, visibilityFunction) self.dataTypes[typeId].visibilityFunction = visibilityFunction end function ZO_ScrollList_SetDeselectOnReselect(self, deselectOnReselect) self.deselectOnReselect = deselectOnReselect end function ZO_ScrollList_SetAutoSelect(self, autoSelect) self.autoSelect = autoSelect end function ZO_ScrollList_SetScrollBarVisibilityCallback(self, callback) self.ScrollBarVisibilityCallback = callback --ScrollBarHiddenCallback is deprecated. Included here for addon backwards compatibility self.ScrollBarHiddenCallback = callback end function ZO_ScrollList_AddCategory(self, categoryId, parentId) if self.categories[categoryId] then return end --if a parent id is given and it doesn't exist, give up local parent = nil if parentId then parent = self.categories[parentId] if not parent then return end end local category = {id = categoryId, parent = parent, children = {}, hidden = false} self.categories[categoryId] = category if parent then table.insert(parent.children, category) end end function ZO_ScrollList_Clear(self) ZO_ClearNumericallyIndexedTable(self.data) ZO_ClearNumericallyIndexedTable(self.visibleData) self.categories = {} if AreSelectionsEnabled(self) then ZO_ScrollList_SelectData(self, NO_SELECTED_DATA, NO_DATA_CONTROL, RESELECTING_DURING_REBUILD, ANIMATE_INSTANTLY) self.lastSelectedDataIndex = nil end end function ZO_ScrollList_GetCategoryHidden(self, categoryId) local category = self.categories[categoryId] if category then return category.hidden end end --Creates a data entry for use in the scroll list. Add it to the data list then commit it. function ZO_ScrollList_CreateDataEntry(typeId, data, categoryId) local entry = { typeId = typeId, categoryId = categoryId, data = data, } data.dataEntry = entry return entry end function ZO_ScrollList_AddOperation(self, operationId, data) local operation = { typeId = operationId, data = data } if data then data.dataEntry = operation -- Used in the comparison function end local scrollData = ZO_ScrollList_GetDataList(self) table.insert(scrollData, operation) end function ZO_ScrollList_GetDataEntryData(entry) return entry.data end function ZO_ScrollList_GetData(control) if control.dataEntry then return control.dataEntry.data end end function ZO_ScrollList_GetDataList(self) return self.data end function ZO_ScrollList_HasVisibleData(self) return #self.visibleData > 0 end function ZO_ScrollList_GetSelectedDataIndex(self) if AreSelectionsEnabled(self) then return self.selectedDataIndex end return nil end function ZO_ScrollList_GetSelectedData(self) if AreSelectionsEnabled(self) then return self.selectedData end return nil end function ZO_ScrollList_GetSelectedControl(self) local data = ZO_ScrollList_GetSelectedData(self) return ZO_ScrollList_GetDataControl(self, data) end --Allows you to prevent the list from scrolling function ZO_ScrollList_SetLockScrolling(self, lock) self.lock = lock end function ZO_ScrollList_GetMouseOverControl(self) for i = 0, #self.activeControls do local control = self.activeControls[i] if MouseIsOver(control) then return control end end end local function PlayAnimationOnControl(control, controlTemplate, animationFieldName, animateInstantly, overrideEndAlpha) if controlTemplate then if not control[animationFieldName] then local highlight = CreateControlFromVirtual("$(parent)Scroll", control, controlTemplate, animationFieldName) control[animationFieldName] = ANIMATION_MANAGER:CreateTimelineFromVirtual("ShowOnMouseOverLabelAnimation", highlight) if overrideEndAlpha then control[animationFieldName]:GetAnimation(1):SetAlphaValues(0, overrideEndAlpha) end end if animateInstantly then control[animationFieldName]:PlayInstantlyToEnd() else control[animationFieldName]:PlayForward() end end end local function RemoveAnimationOnControl(control, animationFieldName, animateInstantly) if control[animationFieldName] then if animateInstantly then control[animationFieldName]:PlayInstantlyToStart() else control[animationFieldName]:PlayBackward() end end end local function HighlightControl(self, control) local highlightTemplate, animationFieldName if type(self.highlightTemplateOrFunction) == "function" then highlightTemplate, animationFieldName = self.highlightTemplateOrFunction(control) else highlightTemplate = self.highlightTemplateOrFunction end control.highlightAnimationFieldName = animationFieldName or "HighlightAnimation" PlayAnimationOnControl(control, highlightTemplate, control.highlightAnimationFieldName, DONT_ANIMATE_INSTANTLY, self.overrideHighlightEndAlpha) self.highlightedControl = control if self.highlightCallback then self.highlightCallback(control, true) end end local function UnhighlightControl(self, control) RemoveAnimationOnControl(control, control.highlightAnimationFieldName) control.highlightAnimationFieldName = nil self.highlightedControl = nil if self.highlightCallback then self.highlightCallback(control, false) end end local function SelectControl(self, control, animateInstantly) PlayAnimationOnControl(control, self.selectionTemplate, "SelectionAnimation", animateInstantly) self.selectedControl = control end local function UnselectControl(self, control, animateInstantly) RemoveAnimationOnControl(control, "SelectionAnimation", animateInstantly) self.selectedControl = nil end --Allows you to lock the highlight in place. The highlight will automatically unlock if the list is recommitted. function ZO_ScrollList_SetLockHighlight(self, lock) if not self.highlightTemplateOrFunction or (self.highlightLocked == lock) then return end self.highlightLocked = lock if lock then self.pendingHighlightControl = self.highlightedControl else if self.highlightedControl then UnhighlightControl(self, self.highlightedControl) end if self.pendingHighlightControl then HighlightControl(self, self.pendingHighlightControl) end end end --Reinitializes the highlight (used mostly when a mouse enter would have been missed) local function RefreshHighlight(self) if not self.highlightTemplateOrFunction then return end self.highlightLocked = false if self.highlightedControl then UnhighlightControl(self, self.highlightedControl) end --find the control to highlight if any for i = 0, #self.activeControls do local control = self.activeControls[i] if MouseIsOver(control) then HighlightControl(self, control) return end end end function ZO_ScrollList_MouseEnter(self, control) if not self.highlightTemplateOrFunction then return end --allows us to place the highlight correctly when we unlock if self.highlightLocked then self.pendingHighlightControl = control return end HighlightControl(self, control) end function ZO_ScrollList_MouseExit(self, control) if not self.highlightTemplateOrFunction then return end if self.highlightLocked then self.pendingHighlightControl = nil return end UnhighlightControl(self, control) end function ZO_ScrollList_MouseClick(self, control) if AreSelectionsEnabled(self) then if control == self.selectedControl then if self.deselectOnReselect then ZO_ScrollList_SelectData(self, nil) end else local data = ZO_ScrollList_GetData(control) local typeId = control.dataEntry.typeId local selectSound = GetDataTypeInfo(self, typeId).selectSound ZO_ScrollList_SelectData(self, data, control) if selectSound then PlaySound(selectSound) end end end end -- highlightTemplateOrFunction can be the name of a template control, or it can be a function that returns the name of a control. -- If a function, it can optionally have an additional return for an animation field name. Highlight templates and -- animation field names are one-to-one, so a unique animation field name is required if and only if the scroll list -- needs to display multiple different highlights. function ZO_ScrollList_EnableHighlight(self, highlightTemplateOrFunction, highlightCallback, overrideEndAlpha) if not self.highlightTemplateOrFunction then self.highlightTemplateOrFunction = highlightTemplateOrFunction self.highlightLocked = false self.pendingHighlightControl = nil self.highlightCallback = highlightCallback self.overrideHighlightEndAlpha = overrideEndAlpha RefreshHighlight(self) end end function ZO_ScrollList_SetScrollbarEthereal(self, isEthereal) self.isScrollBarEthereal = isEthereal end function ZO_ScrollList_SetHideScrollbarOnDisable(self, hideOnDisable) -- Not updating state here, you should call this when the list is being created. -- The bar will update state properly when the list has data committed. self.hideScrollBarOnDisabled = hideOnDisable end function ZO_ScrollList_SetUseScrollbar(self, useScrollbar) -- Not updating state here, you should call this when the list is being created. -- The bar will update state properly when the list has data committed. self.useScrollbar = useScrollbar end function ZO_ScrollList_SetUseFadeGradient(self, useFadeGradient) self.useFadeGradient = useFadeGradient end function ZO_ScrollList_IgnoreMouseDownEditFocusLoss(self) ZO_PreHookHandler(self.scrollbar, "OnMouseDown", IgnoreMouseDownEditFocusLoss) ZO_PreHookHandler(self.upButton, "OnMouseDown", IgnoreMouseDownEditFocusLoss) ZO_PreHookHandler(self.downButton, "OnMouseDown", IgnoreMouseDownEditFocusLoss) end function ZO_ScrollList_EnableSelection(self, selectionTemplate, selectionCallback) if not self.selectionTemplate then self.selectionTemplate = selectionTemplate self.selectionCallback = selectionCallback end end --Determines if one piece of selected data is the "same" as the other. Used mainly to --keep an item selected even when the data for the list is updated if they share some --property determined by the equality function. For example, if you have an item with --id=1 and state=up and replace it with id=1 and state=down, the selection will be maintained --if the equality function only compares ids. local function AreDataEqualSelections(self, data1, data2) if data1 == data2 then return true end if data1 == nil or data2 == nil then return false end local dataEntry1 = data1.dataEntry local dataEntry2 = data2.dataEntry if dataEntry1.typeId == dataEntry2.typeId then local dataTypeInfo = GetDataTypeInfo(self, dataEntry1.typeId) local equalityFunction = dataTypeInfo.equalityFunction if equalityFunction then return equalityFunction(data1, data2) end end return false end function ZO_ScrollList_IsDataSelected(self, data) if AreSelectionsEnabled(self) and AreDataEqualSelections(self, self.selectedData, data) then return true end return false end function ZO_ScrollList_GetDataControl(self, data) if data then local numActive = #self.activeControls for i = 1, numActive do local currentControl = self.activeControls[i] local currentDataEntry = currentControl.dataEntry if AreDataEqualSelections(self, currentDataEntry.data, data) then return currentControl end end end return nil end function ZO_ScrollList_GetDataIndex(self, data) if data then for i, entryData in ipairs(self.data) do if entryData == data then return i end end end return nil end function ZO_ScrollList_FindDataIndexByDataEntry(self, dataEntry, optionalTypeId) if dataEntry then for i, data in ipairs(self.data) do if data.data == dataEntry then return i end if not optionalTypeId or data.typeId == optionalTypeId then local dataTypeInfo = GetDataTypeInfo(self, data.typeId) local equalityFunction = dataTypeInfo.equalityFunction if equalityFunction and equalityFunction(data.data, dataEntry) then return i end end end end return nil end function ZO_ScrollList_SelectData(self, data, control, reselectingDuringRebuild, animateInstantly) if not AreSelectionsEnabled(self) then return end if reselectingDuringRebuild == nil then reselectingDuringRebuild = false end -- Update the current selection to the new data -- If it's already the selected entry then we still need to do some cleanup if this is a rebuild -- Specifically we need to make sure the selected index is still valid and correct local notAlreadySelected = self.selectedData ~= data if notAlreadySelected or reselectingDuringRebuild then if animateInstantly == nil then animateInstantly = false end -- Find the data index for the data in the scroll list local dataIndex if data ~= nil then for i = 1, #self.data do if AreDataEqualSelections(self, self.data[i].data, data) then dataIndex = i break end end --this data we tried to select isn't in the scroll list at all, just abort if dataIndex == nil then return end end -- if this is a new selection, unselect the old control and save off any necessary info local previouslySelectedData = nil if notAlreadySelected then previouslySelectedData = self.selectedData if self.selectedData then self.selectedData = nil self.selectedDataIndex = nil if self.selectedControl then UnselectControl(self, self.selectedControl, animateInstantly) end end end -- if we have a selected data then update the selected control and any necessary info if data ~= nil then self.selectedDataIndex = dataIndex self.lastSelectedDataIndex = dataIndex self.selectedData = data -- don't need to select the control if it's already the selected data if notAlreadySelected then if not control then control = ZO_ScrollList_GetDataControl(self, data) end if control then SelectControl(self, control, animateInstantly) end end end if self.selectionCallback and notAlreadySelected then self.selectionCallback(previouslySelectedData, self.selectedData, reselectingDuringRebuild) end end end local function OnContentsUpdate(self) local _, windowHeight = self:GetDimensions() if windowHeight > 0 then self:SetHandler("OnUpdate", nil) ZO_ScrollList_SetHeight(self, windowHeight) ZO_ScrollList_Commit(self:GetParent()) end end local function FreeActiveScrollListControl(self, i) local currentControl = self.activeControls[i] local currentDataEntry = currentControl.dataEntry local dataType = GetDataTypeInfo(self, currentDataEntry.typeId) if self.highlightTemplateOrFunction then if currentControl == self.highlightedControl then UnhighlightControl(self, currentControl) if self.highlightLocked then self.highlightLocked = false end else RemoveAnimationOnControl(currentControl, "HighlightAnimation", ANIMATE_INSTANTLY) end end if currentControl == self.pendingHighlightControl then self.pendingHighlightControl = nil end if AreSelectionsEnabled(self) then if currentControl == self.selectedControl then UnselectControl(self, currentControl, ANIMATE_INSTANTLY) else RemoveAnimationOnControl(currentControl, "SelectionAnimation", ANIMATE_INSTANTLY) end end local controlPool = dataType.pool local hideCallback = dataType.hideCallback if hideCallback then hideCallback(currentControl, currentControl.dataEntry.data) end controlPool:ReleaseObject(currentControl.key) currentControl.key = nil currentControl.dataEntry = nil currentDataEntry.control = nil currentControl.index = nil self.activeControls[i] = self.activeControls[#self.activeControls] self.activeControls[#self.activeControls] = nil end local function ResizeScrollBar(self, scrollableDistance) local shouldHideScrollbar = self.isScrollBarEthereal local scrollBarHeight = self.scrollbar:GetHeight() local scrollListHeight = ZO_ScrollList_GetHeight(self) if scrollableDistance > 0 then if self.offset > scrollableDistance then self.offset = scrollableDistance end self.scrollbar:SetThumbTextureHeight(scrollBarHeight * scrollListHeight / (scrollableDistance + scrollListHeight)) self.scrollbar:SetMinMax(0, scrollableDistance) self.scrollbar:SetEnabled(true) else self.offset = 0 self.scrollbar:SetThumbTextureHeight(scrollBarHeight) self.scrollbar:SetMinMax(0, 0) self.scrollbar:SetEnabled(false) shouldHideScrollbar = shouldHideScrollbar or self.hideScrollBarOnDisabled end shouldHideScrollbar = shouldHideScrollbar or not self.useScrollbar if self.ScrollBarVisibilityCallback then self.ScrollBarVisibilityCallback(self, shouldHideScrollbar) else if self.scrollbar:IsControlHidden() ~= shouldHideScrollbar then self.scrollbar:SetHidden(shouldHideScrollbar) end end end local function CheckRunHandler(self, handlerName) local mouseOverControl = WINDOW_MANAGER:GetMouseOverControl() if mouseOverControl and not mouseOverControl:IsHidden() and mouseOverControl:IsChildOf(self) then local handler = mouseOverControl:GetHandler(handlerName) if handler then handler(mouseOverControl) end end end local function CanSelectData(self, index) local dataEntry = self.data[index] if dataEntry == nil then return false end local dataTypeInfo = GetDataTypeInfo(self, dataEntry.typeId) return dataTypeInfo.selectable end local function IsCategoryHeader(self, index) local dataEntry = self.data[index] local dataTypeInfo = GetDataTypeInfo(self, dataEntry.typeId) return dataTypeInfo.categoryHeader end local function GetClampedSelectedIndex(self) local selectedIndex = self.selectedDataIndex or self.lastSelectedDataIndex if selectedIndex then return zo_clamp(selectedIndex, 1, #self.data) end return nil end local function AutoSelect(self, animateInstantly, scrollIntoView) if #self.data > 0 then local selectedIndex = GetClampedSelectedIndex(self) if selectedIndex then for i = selectedIndex, 1, -1 do if CanSelectData(self, i) then if scrollIntoView then local NO_CALLBACK = nil ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[i].data, NO_CALLBACK, animateInstantly) else ZO_ScrollList_SelectData(self, self.data[i].data, NO_DATA_CONTROL, NOT_RESELECTING_DURING_REBUILD, animateInstantly) end return end end end if ZO_ScrollList_TrySelectFirstData(self) then return end end ZO_ScrollList_SelectData(self, NO_SELECTED_DATA, NO_DATA_CONTROL, NOT_RESELECTING_DURING_REBUILD, animateInstantly) end local function GetDataControlPositions(self, dataEntry, dataIndex) local controlTop local controlBottom if self.mode == SCROLL_LIST_UNIFORM then controlTop = (dataIndex - 1) * self.uniformControlHeight controlBottom = controlTop + self.uniformControlHeight else controlTop = dataEntry.top controlBottom = dataEntry.bottom end return controlTop, controlBottom end local function GetDataControlDimensions(self, dataEntry) local controlWidth local controlHeight local dataTypeInfo = ZO_ScrollList_GetDataTypeTable(self, dataEntry.typeId) if self.mode == SCROLL_LIST_OPERATIONS then local instanceData = dataEntry.data controlWidth = dataTypeInfo:GetControlWidth(instanceData) controlHeight = dataTypeInfo:GetControlHeight(instanceData) else controlHeight = dataTypeInfo.height end return controlWidth, controlHeight end -- If an animation is in progress and we call to change the scrollBar's value, -- we must account for the distance left to travel in our calculations -- of the new scrollBar value so we do not over/under shoot the target local function CalculateScrollAnimationOffset(self) local animationOffset = 0 if self.animationTarget then animationOffset = self.animationTarget - self.scrollbar:GetValue() end return animationOffset end function ZO_ScrollList_ScrollDataIntoView(self, dataIndex, onScrollCompleteCallback, animateInstantly) local data = self.data[dataIndex] local scrollTop = self.scrollbar:GetValue() local scrollBottom = self.scrollbar:GetValue() + ZO_ScrollList_GetHeight(self) local controlTop, controlBottom = GetDataControlPositions(self, data, dataIndex) local scrollAnimationOffset = CalculateScrollAnimationOffset(self) local calculatedControlTop = controlTop - self.yDistanceFromEdgeWhereSelectionCausesScroll - scrollAnimationOffset local calculatedControlBottom = controlBottom + self.yDistanceFromEdgeWhereSelectionCausesScroll - scrollAnimationOffset if calculatedControlTop < scrollTop then ZO_ScrollList_ScrollRelative(self, calculatedControlTop - scrollTop, onScrollCompleteCallback, animateInstantly) elseif calculatedControlBottom > scrollBottom then ZO_ScrollList_ScrollRelative(self, calculatedControlBottom - scrollBottom, onScrollCompleteCallback, animateInstantly) elseif onScrollCompleteCallback then onScrollCompleteCallback(true) end end function ZO_ScrollList_GetScrollValue(self) return self.scrollbar:GetValue() end function ZO_ScrollList_ScrollDataToCenter(self, dataIndex, onScrollCompleteCallback, animateInstantly) local data = self.data[dataIndex] local scrollCenter = self.scrollbar:GetValue() + ZO_ScrollList_GetHeight(self) / 2 local controlTop = GetDataControlPositions(self, data, dataIndex) local _, controlHeight = GetDataControlDimensions(self, data) local controlCenter = controlTop + controlHeight / 2 local scrollAnimationOffset = CalculateScrollAnimationOffset(self) ZO_ScrollList_ScrollRelative(self, controlCenter - scrollCenter - scrollAnimationOffset, onScrollCompleteCallback, animateInstantly) end function ZO_ScrollList_SelectNextData(self, onScrollCompleteCallback, shouldAnimateInstantly) if not self.selectedDataIndex then return end for i = 1, #self.data do -- Allow Wrapping local newIndex = ((self.selectedDataIndex + i - 1) % #self.data) + 1 local hasWrapped = newIndex < self.selectedDataIndex if CanSelectData(self, newIndex) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[newIndex].data, onScrollCompleteCallback, hasWrapped or shouldAnimateInstantly) return end end end function ZO_ScrollList_SelectPreviousData(self, onScrollCompleteCallback, shouldAnimateInstantly) if not self.selectedDataIndex then return end for i = 1, #self.data do -- Allow Wrapping local newIndex = ((self.selectedDataIndex + #self.data - i - 1) % #self.data) + 1 local hasWrapped = newIndex > self.selectedDataIndex if CanSelectData(self, newIndex) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[newIndex].data, onScrollCompleteCallback, hasWrapped or shouldAnimateInstantly) return end end end -- x and y direction must use the values for directions described at the top of this file function ZO_ScrollList_SelectNextDataInDirection(self, xDirection, yDirection) if not self.selectedDataIndex then return end local numDataEntries = #self.data -- Move Y Direction if yDirection ~= ZO_SCROLL_MOVEMENT_DIRECTION_NONE then local currentData = self.data[self.selectedDataIndex] local holdXPos = self.lastHoldXPos or 0 local nextIndex = self.selectedDataIndex + yDirection local currentTopValue = currentData.top * yDirection local bestDistance = math.huge local bestIndex = nil while nextIndex <= numDataEntries and nextIndex > 0 do if CanSelectData(self, nextIndex) then local nextData = self.data[nextIndex] local nextDataTopValue = nextData.top * yDirection -- check and see if the next data is within the y direction we are searching, -- and has an x direction greater than or equal to our current data's x if nextDataTopValue > currentTopValue then local deltaDistance = zo_abs(nextData.left - holdXPos) if deltaDistance < bestDistance then bestIndex = nextIndex bestDistance = deltaDistance end end -- we only want to select an entry that is only one y delta away. -- We must look ahead and see if the next data after current next data has an even greater y local lookAheadIndex = nextIndex + yDirection local nextSelectableData local noFurtherDataToExplore = false while not nextSelectableData do -- we can't look ahead anymore, bail out if lookAheadIndex > numDataEntries or lookAheadIndex < 1 then break end if CanSelectData(self, lookAheadIndex) then nextSelectableData = self.data[lookAheadIndex] if currentTopValue < nextDataTopValue and nextDataTopValue < nextSelectableData.top * yDirection then noFurtherDataToExplore = true break end end lookAheadIndex = lookAheadIndex + yDirection end -- we found that there is no other data to look at -- and we no longer need to look any further if noFurtherDataToExplore then break end end nextIndex = nextIndex + yDirection end if bestIndex then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[bestIndex].data) if self.scrollToExtent then if yDirection == ZO_SCROLL_MOVEMENT_DIRECTION_NEGATIVE and nextIndex == 0 and ZO_ScrollList_CanScrollUp(self) then ZO_ScrollList_ScrollDataIntoView(self, 1) elseif yDirection == ZO_SCROLL_MOVEMENT_DIRECTION_POSITIVE and nextIndex >= numDataEntries and ZO_ScrollList_CanScrollDown(self) then ZO_ScrollList_ScrollDataIntoView(self, numDataEntries) end end end end -- Move X Direction -- Since data is given in X direction order, simply pick the next selectable data -- in the correct layout direction in contigious order -- we also save this X position for use in calculating Y direction -- since this emulates how people already expect this form of selection to work (see text editors) local nextIndex = self.selectedDataIndex + xDirection if xDirection == ZO_SCROLL_MOVEMENT_DIRECTION_POSITIVE then while nextIndex <= numDataEntries do if CanSelectData(self, nextIndex) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[nextIndex].data) break end nextIndex = nextIndex + xDirection end self.lastHoldXPos = self.data[self.selectedDataIndex].left elseif xDirection == ZO_SCROLL_MOVEMENT_DIRECTION_NEGATIVE then while nextIndex > 0 do if CanSelectData(self, nextIndex) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[nextIndex].data) break end nextIndex = nextIndex + xDirection end self.lastHoldXPos = self.data[self.selectedDataIndex].left end end function ZO_ScrollList_ResetLastHoldPosition(self) self.lastHoldXPos = nil end function ZO_ScrollList_RefreshLastHoldPosition(self) if self.selectedDataIndex then self.lastHoldXPos = self.data[self.selectedDataIndex].left end end function ZO_ScrollList_TrySelectFirstData(self, onScrollCompleteCallback, shouldAnimateInstantly) for i = 1, #self.data do if CanSelectData(self, i) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[i].data, onScrollCompleteCallback, shouldAnimateInstantly) return true end end return false end function ZO_ScrollList_TrySelectLastData(self, onScrollCompleteCallback, shouldAnimateInstantly) for i = #self.data, 1, -1 do if CanSelectData(self, i) then ZO_ScrollList_SelectDataAndScrollIntoView(self, self.data[i].data, onScrollCompleteCallback, shouldAnimateInstantly) return true end end return false end function ZO_ScrollList_AutoSelectData(self, animateInstantly, scrollIntoView) AutoSelect(self, animateInstantly, scrollIntoView) end --When the list in inactive, you can't get selected data. Auto select is a mechanic of lists that will reselect the last thing that was selected -- Assuming it wasn't reset, or manually set to something else. If another party needs to know what data would be selected if the list were active, this is how function ZO_ScrollList_GetAutoSelectIndex(self) if #self.data > 0 then local selectedIndex = GetClampedSelectedIndex(self) if selectedIndex and CanSelectData(self, selectedIndex) then return selectedIndex end end return nil end function ZO_ScrollList_GetAutoSelectData(self) local recalledIndex = ZO_ScrollList_GetAutoSelectIndex(self) if recalledIndex then return self.data[recalledIndex].data end return nil end function ZO_ScrollList_ResetAutoSelectIndex(self) self.lastSelectedDataIndex = nil end -- This should be used before a commit/activate. It won't affect an already commited/activated list. function ZO_ScrollList_SetAutoSelectToMatchingDataEntry(self, dataEntry, optionalTypeId) self.lastSelectedDataIndex = ZO_ScrollList_FindDataIndexByDataEntry(self, dataEntry, optionalTypeId) end -- direction: ZO_SCROLL_SELECT_CATEGORY_PREVIOUS or ZO_SCROLL_SELECT_CATEGORY_NEXT function ZO_ScrollList_SelectFirstIndexInCategory(self, direction) local currentlySelectedData = ZO_ScrollList_GetSelectedData(self) if not currentlySelectedData then return end local listData = ZO_ScrollList_GetDataList(self) local nextDataIndex = ZO_ScrollList_GetDataIndex(self, currentlySelectedData.dataEntry) + direction if nextDataIndex < 1 or nextDataIndex > #listData then -- we are already at the end of our list return end -- if we are going backwards and hit a non-selectable entry, we need to keep going until we find a selectable entry so we don't just select the same value if direction == ZO_SCROLL_SELECT_CATEGORY_PREVIOUS then while not CanSelectData(self, nextDataIndex) do nextDataIndex = nextDataIndex + direction if nextDataIndex == 0 or nextDataIndex == #listData then -- could not find an acceptable target, we are at the selectable end of our list return end end end while nextDataIndex > 0 and nextDataIndex <= #listData do if IsCategoryHeader(self, nextDataIndex) then -- we found header data, select the first selectable entry under it local lookAheadIndex = nextDataIndex + 1 ZO_ScrollList_SelectData(self, listData[lookAheadIndex].data) ZO_ScrollList_ScrollDataToCenter(self, lookAheadIndex) ZO_ScrollList_RefreshLastHoldPosition(self) return end nextDataIndex = nextDataIndex + direction end -- could not find another header data, so just pick the last selectable value we found ZO_ScrollList_SelectData(self, listData[nextDataIndex - direction].data) ZO_ScrollList_ScrollDataToCenter(self, nextDataIndex - direction) ZO_ScrollList_RefreshLastHoldPosition(self) end --Updates the scroll control with new data. Call this when you modify the data list by adding or removing entries. function ZO_ScrollList_Commit(self) local windowHeight = ZO_ScrollList_GetHeight(self) local selectionsEnabled = AreSelectionsEnabled(self) --the window isn't big enough to show anything (its anchors probably haven't been processed yet), so delay the commit until that happens if windowHeight <= 0 then self.contents:SetHandler("OnUpdate", OnContentsUpdate) return end self.contents:SetHandler("OnUpdate", nil) CheckRunHandler(self, "OnMouseExit") ZO_ClearNumericallyIndexedTable(self.visibleData) local scrollableDistance = 0 local foundSelected = false if self.mode == SCROLL_LIST_UNIFORM then for i, currentData in ipairs(self.data) do table.insert(self.visibleData, i) if selectionsEnabled and AreDataEqualSelections(self, currentData.data, self.selectedData) then foundSelected = true ZO_ScrollList_SelectData(self, currentData.data, NO_DATA_CONTROL, RESELECTING_DURING_REBUILD, ANIMATE_INSTANTLY) end end scrollableDistance = #self.data * self.uniformControlHeight - windowHeight elseif self.mode == SCROLL_LIST_NON_UNIFORM then local currentY = 0 for i, currentData in ipairs(self.data) do currentData.top = currentY currentY = currentY + GetDataTypeInfo(self, currentData.typeId).height currentData.bottom = currentY table.insert(self.visibleData, i) if selectionsEnabled and AreDataEqualSelections(self, currentData.data, self.selectedData) then foundSelected = true ZO_ScrollList_SelectData(self, currentData.data, NO_DATA_CONTROL, RESELECTING_DURING_REBUILD, ANIMATE_INSTANTLY) end end scrollableDistance = currentY - windowHeight elseif self.mode == SCROLL_LIST_OPERATIONS then local layoutInfo = {} layoutInfo.lineBreakModifier = 0 layoutInfo.direction = self.buildDirection if self.buildDirection == ZO_SCROLL_BUILD_DIRECTION_LEFT_TO_RIGHT then layoutInfo.startPos = 0 layoutInfo.endPos = self.contents:GetWidth() else layoutInfo.startPos = self.contents:GetWidth() layoutInfo.endPos = 0 end local currentX = layoutInfo.startPos local currentY = 0 for i, currentData in ipairs(self.data) do local currentOperation = GetDataTypeInfo(self, currentData.typeId) if currentOperation:IsDataVisible(currentData.data) then currentX, currentY = currentOperation:GetPositionsAndAdvance(layoutInfo, currentX, currentY, currentData) table.insert(self.visibleData, i) if selectionsEnabled and AreDataEqualSelections(self, currentData.data, self.selectedData) then foundSelected = true ZO_ScrollList_SelectData(self, currentData.data, NO_DATA_CONTROL, RESELECTING_DURING_REBUILD, ANIMATE_INSTANTLY) end end end if #self.visibleData > 0 then local lastVisibleDataIndex = self.visibleData[#self.visibleData] scrollableDistance = self.data[lastVisibleDataIndex].bottom - windowHeight else scrollableDistance = 0 end end ResizeScrollBar(self, scrollableDistance) --nuke the active list since things may have left it local i = #self.activeControls while i >= 1 do FreeActiveScrollListControl(self, i) i = i - 1 end if selectionsEnabled then if not foundSelected then if self.autoSelect then AutoSelect(self, ANIMATE_INSTANTLY) else ZO_ScrollList_SelectData(self, NO_SELECTED_DATA, NO_DATA_CONTROL, RESELECTING_DURING_REBUILD, ANIMATE_INSTANTLY) end end end ZO_ScrollList_UpdateScroll(self) CheckRunHandler(self, "OnMouseEnter") end --updates the layout of visible controls --data: optionally allows you to only update the control backed by the specified data table --overrideSetupCallback: optionally allows you to call this function instead of the normal setup function if you only need to do a very specific update function ZO_ScrollList_RefreshVisible(self, data, overrideSetupCallback) for i = 1, #self.activeControls do local control = self.activeControls[i] local dataEntry = control.dataEntry if not data or data == dataEntry.data then local dataTypeInfo = GetDataTypeInfo(self, dataEntry.typeId) if overrideSetupCallback then overrideSetupCallback(control, dataEntry.data, self) elseif dataTypeInfo.setupCallback then dataTypeInfo.setupCallback(control, dataEntry.data, self) end end end end local function UpdateAfterDataVisibilityChange(self) if self.mode == SCROLL_LIST_UNIFORM then --nuke the active list since things may have left it local i = #self.activeControls while i >= 1 do FreeActiveScrollListControl(self, i) i = i - 1 end --update scroll distance local windowHeight = ZO_ScrollList_GetHeight(self) local scrollSize = self.uniformControlHeight * #self.visibleData ResizeScrollBar(self, math.max(0, scrollSize-windowHeight)) ZO_ScrollList_UpdateScroll(self) end end function ZO_ScrollList_HideData(self, index) if self.mode == SCROLL_LIST_UNIFORM then for i = 1, #self.visibleData do if self.visibleData[i] == index then table.remove(self.visibleData, i) break end end UpdateAfterDataVisibilityChange(self) end end function ZO_ScrollList_HideCategory(self, categoryId) local data = self.data local visibleData = self.visibleData local categories = self.categories if self.mode == SCROLL_LIST_UNIFORM then local category = self.categories[categoryId] if category then category.hidden = true local numRemoved = 0 local i = 1 while i <= #self.visibleData do local curCategoryId = data[visibleData[i]].categoryId local curCategory = categories[curCategoryId] local found = false --climb the hierarchy to see if this piece of data is under this category while curCategory do curCategoryId = curCategory.id if curCategoryId == categoryId then table.remove(visibleData, i) found = true numRemoved = numRemoved + 1 break end curCategory = curCategory.parent end if not found then i = i + 1 end end if numRemoved > 0 then UpdateAfterDataVisibilityChange(self) end end end end function ZO_ScrollList_ShowData(self, index) if self.mode == SCROLL_LIST_UNIFORM then local inserted = false for i = 1, #self.visibleData do if self.visibleData[i] == index then return elseif self.visibleData[i] > index then table.insert(self.visibleData, index, i) inserted = true break end end if not inserted then table.insert(self.visibleData, index) end UpdateAfterDataVisibilityChange(self) end end local function CompareIndices(search, compare) return search - compare end function ZO_ScrollList_HideAllCategories(self) if self.mode == SCROLL_LIST_UNIFORM then for categoryId in pairs(self.categories) do ZO_ScrollList_HideCategory(self, categoryId) end end end function ZO_ScrollList_ShowCategory(self, categoryId) if self.mode == SCROLL_LIST_UNIFORM then local category = self.categories[categoryId] if category then category.hidden = false local numShown = 0 local i = 1 while i <= #self.data do local curCategoryId = self.data[i].categoryId local curCategory = self.categories[curCategoryId] local shouldInsert = true while curCategory do if curCategory.hidden then shouldInsert = false break end curCategory = curCategory.parent end if shouldInsert then local found, insertionPoint = zo_binarysearch(i, self.visibleData, CompareIndices) if not found then numShown = numShown + 1 table.insert(self.visibleData, insertionPoint, i) end end i = i + 1 end if numShown > 0 then UpdateAfterDataVisibilityChange(self) end end end end --Used to locate the point in the data list where we should start looking for in view controls local function FindStartPoint(self, topEdge) if self.mode == SCROLL_LIST_UNIFORM then return zo_floor(topEdge / self.uniformControlHeight) + 1 else local function CompareEntries(topEdge, compareDataIndex) local compareData = self.data[compareDataIndex] return topEdge - compareData.bottom end local _, insertPoint = zo_binarysearch(topEdge, self.visibleData, CompareEntries) return insertPoint end end --holds controls that have already been evaluated and do not need to be looked at again this update scroll call local consideredMap = {} function ZO_ScrollList_UpdateScroll(self) local windowHeight = ZO_ScrollList_GetHeight(self) local activeControls = self.activeControls local offset = self.offset local IS_REAL_NUMBER = false UpdateScrollFade(self, self.contents, IS_REAL_NUMBER, offset) --remove active controls that are now hidden local activeIndex = 1 local numActive = #activeControls while activeIndex <= numActive do local currentDataEntry = activeControls[activeIndex].dataEntry if currentDataEntry.bottom < offset or currentDataEntry.top > offset + windowHeight then FreeActiveScrollListControl(self, activeIndex) numActive = numActive - 1 else activeIndex = activeIndex + 1 end consideredMap[currentDataEntry] = true end local allData = self.data local visibleDataIndices = self.visibleData local mode = self.mode --add revealed controls local firstInViewVisibleIndex = FindStartPoint(self, offset) local nextCandidateVisibleIndex = firstInViewVisibleIndex local currentDataIndex = visibleDataIndices[nextCandidateVisibleIndex] local dataEntry = allData[currentDataIndex] local bottomEdge = offset + windowHeight local controlTop local uniformControlHeight = self.uniformControlHeight if dataEntry then if mode == SCROLL_LIST_UNIFORM then controlTop = (nextCandidateVisibleIndex - 1) * uniformControlHeight else controlTop = dataEntry.top end end while dataEntry and controlTop <= bottomEdge do if not consideredMap[dataEntry] then local dataType = GetDataTypeInfo(self, dataEntry.typeId) local controlPool = dataType.pool if controlPool then local control, key = controlPool:AcquireObject() local setupCallback = dataType.setupCallback control:SetHidden(false) control.dataEntry = dataEntry dataEntry.control = control control.key = key control.index = currentDataIndex if setupCallback then setupCallback(control, dataEntry.data, self) end table.insert(activeControls, control) consideredMap[dataEntry] = true if AreDataEqualSelections(self, dataEntry.data, self.selectedData) then SelectControl(self, control, ANIMATE_INSTANTLY) end end --even uniform active controls need to know their position to determine if they are still active if self.mode == SCROLL_LIST_UNIFORM then dataEntry.top = controlTop dataEntry.bottom = controlTop + uniformControlHeight end end nextCandidateVisibleIndex = nextCandidateVisibleIndex + 1 currentDataIndex = visibleDataIndices[nextCandidateVisibleIndex] dataEntry = allData[currentDataIndex] if dataEntry then if mode == SCROLL_LIST_UNIFORM then controlTop = (nextCandidateVisibleIndex - 1) * uniformControlHeight else controlTop = dataEntry.top end end end --update positions local contents = self.contents local numNowActive = #activeControls for activeControlIndex = 1, numNowActive do local currentControl = activeControls[activeControlIndex] local currentData = currentControl.dataEntry if self.mode == SCROLL_LIST_OPERATIONS then local currentOperation = GetDataTypeInfo(self, currentData.typeId) currentOperation:AddToScrollContents(contents, currentControl, currentData.left, currentData.top, offset) else local yOffset = currentData.top - offset local xOffset = currentData.left currentControl:ClearAnchors() currentControl:SetAnchor(TOPLEFT, contents, TOPLEFT, xOffset, yOffset) currentControl:SetAnchor(TOPRIGHT, contents, TOPRIGHT, xOffset, yOffset) end end --reset considered for k,v in pairs(consideredMap) do consideredMap[k] = nil end end function ZO_ScrollList_ResetToTop(self) self.timeline:Stop() self.scrollbar:SetValue(MIN_SCROLL_VALUE) end function ZO_ScrollList_ScrollRelative(self, delta, onScrollCompleteCallback, animateInstantly) if not self.lock then local scrollValue if self.animationTarget then scrollValue = self.animationTarget + delta else scrollValue = self.scrollbar:GetValue() + delta end self.onScrollCompleteCallback = onScrollCompleteCallback SetSliderValue(self, scrollValue, animateInstantly) elseif onScrollCompleteCallback then onScrollCompleteCallback(true) end end function ZO_ScrollList_ScrollAbsolute(self, value) if not self.lock then SetSliderValue(self, value) end end --This function actually moves the scroll window. The only thing that should ever call it is the slider's value changed handler. --All other scrolling behavior should call scroll absolute or scroll relative which moves the slider and activates the value changed handler. function ZO_ScrollList_MoveWindow(self, value) self.offset = value ZO_ScrollList_UpdateScroll(self) end -- These functions are used for scrolling through the scroll list as a block of text instead of scrolling though each entry. function ZO_ScrollList_CanScrollUp(self) return self.selectedDataIndex and self.selectedDataIndex ~= 1 end function ZO_ScrollList_CanScrollDown(self) return self.selectedDataIndex and self.selectedDataIndex ~= #self.data end function ZO_ScrollList_AtTopOfVisible(self) local minIndex = #self.data local minIndexData = nil for i = 1, #self.activeControls do local currentControl = self.activeControls[i] local currentData = currentControl.dataEntry.data local currentIndex = currentControl.index if currentIndex < minIndex then minIndex = currentIndex minIndexData = currentData end end return self.selectedDataIndex == minIndex, minIndexData end function ZO_ScrollList_AtBottomOfVisible(self) local maxIndex = 1 local maxIndexData = nil for i = 1, #self.activeControls do local currentControl = self.activeControls[i] local currentData = currentControl.dataEntry.data local currentIndex = currentControl.index if currentIndex > maxIndex then maxIndex = currentIndex maxIndexData = currentData end end return self.selectedDataIndex == maxIndex, maxIndexData end function ZO_ScrollList_AtTopOfList(self) if AreSelectionsEnabled(self) then if self.selectedDataIndex then local selectedData = self.data[self.selectedDataIndex] local checkIndex = self.selectedDataIndex - 1 while checkIndex >= 1 do local checkData = self.data[checkIndex] if self.mode ~= SCROLL_LIST_OPERATIONS or checkData.top < selectedData.top then if CanSelectData(self, checkIndex) then return false end end checkIndex = checkIndex - 1 end return true end else return false end end function ZO_ScrollList_AtBottomOfList(self) if AreSelectionsEnabled(self) then if self.selectedDataIndex then local selectedData = self.data[self.selectedDataIndex] local checkIndex = self.selectedDataIndex + 1 local numData = #self.data while checkIndex <= numData do local checkData = self.data[checkIndex] if self.mode ~= SCROLL_LIST_OPERATIONS or checkData.top > selectedData.top then if CanSelectData(self, checkIndex) then return false end end checkIndex = checkIndex + 1 end return true end else return false end end function ZO_ScrollList_SelectDataAndScrollIntoView(self, data, onScrollCompleteCallback, shouldAnimateInstantly) ZO_ScrollList_SelectData(self, data, NO_DATA_CONTROL, NOT_RESELECTING_DURING_REBUILD, shouldAnimateInstantly) ZO_ScrollList_ScrollDataIntoView(self, self.selectedDataIndex, onScrollCompleteCallback, shouldAnimateInstantly) end function ZO_ScrollList_EnoughEntriesToScroll(self) local _, scrollableDistance = self.scrollbar:GetMinMax() return scrollableDistance > 0 end