Zoom, pan and per-slot undress in the dressing room

EnableClickRotate becomes EnableModelControls, the controls of 4.3.4's
ModelWithControlsTemplate: left-drag turns the model without ClassicAPI's
step animation, the mouse wheel moves the camera toward the face with
SetPortraitZoom, and right-drag pans with 4.3.4's per-race pan speed and
bounds. Showing the frame resets zoom and pan. Every model frame pfUI
skins gets them.

The dressing room gains an Undress button and a column of slot buttons
on each side, in the paperdoll's order; clicking one calls UndressSlot.
This commit is contained in:
Brues
2026-10-30 23:11:09 -05:00
parent 2c7ca8afdd
commit 70cc994a97
15 changed files with 149 additions and 30 deletions
+83 -23
View File
@@ -186,34 +186,94 @@ function pfUI.api.CreateTextBox(name, parent)
return f
end
-- [ EnableClickRotate ]
-- Enables Modelframes to be rotated by click-drag
-- [ EnableModelControls ]
-- Gives a modelframe the controls of 4.3.4's ModelWithControlsTemplate:
-- left-drag turns it, the mouse wheel zooms toward the face and right-drag
-- pans it. Zoom and pan reset each time the frame is shown.
-- 'frame' [frame] the modelframe that should be used
function pfUI.api.EnableClickRotate(frame)
frame:EnableMouse(true)
frame:HookScript("OnUpdate", function()
if this.rotate then
local x,_ = GetCursorPosition()
if this.curx > x then
this.rotation = this.rotation - abs(x-this.curx) * 0.025
elseif this.curx < x then
this.rotation = this.rotation + abs(x-this.curx) * 0.025
end
this:SetRotation(this.rotation)
this.curx, this.cury = x, y
end
end)
do
local ZOOM_STEP, MAX_ZOOM = .15, .7
frame:HookScript("OnMouseDown", function()
-- 4.3.4's pan speed and bounds for each player model (ModelFrames.lua)
local PAN_SETTINGS = {
HumanMale = { panMaxLeft = -.4, panMaxRight = .4, panMaxTop = 1.2, panMaxBottom = -.3, panValue = 38 },
HumanFemale = { panMaxLeft = -.3, panMaxRight = .3, panMaxTop = 1.2, panMaxBottom = -.2, panValue = 45 },
OrcMale = { panMaxLeft = -.7, panMaxRight = .8, panMaxTop = 1.2, panMaxBottom = -.3, panValue = 30 },
OrcFemale = { panMaxLeft = -.4, panMaxRight = .3, panMaxTop = 1.2, panMaxBottom = -.3, panValue = 37 },
DwarfMale = { panMaxLeft = -.4, panMaxRight = .6, panMaxTop = .9, panMaxBottom = -.2, panValue = 44 },
DwarfFemale = { panMaxLeft = -.3, panMaxRight = .3, panMaxTop = .9, panMaxBottom = -.2, panValue = 47 },
NightElfMale = { panMaxLeft = -.5, panMaxRight = .5, panMaxTop = 1.5, panMaxBottom = -.4, panValue = 30 },
NightElfFemale = { panMaxLeft = -.4, panMaxRight = .4, panMaxTop = 1.4, panMaxBottom = -.4, panValue = 33 },
ScourgeMale = { panMaxLeft = -.4, panMaxRight = .4, panMaxTop = 1.1, panMaxBottom = -.3, panValue = 35 },
ScourgeFemale = { panMaxLeft = -.3, panMaxRight = .4, panMaxTop = 1.1, panMaxBottom = -.3, panValue = 36 },
TaurenMale = { panMaxLeft = -.7, panMaxRight = .9, panMaxTop = 1.1, panMaxBottom = -.5, panValue = 31 },
TaurenFemale = { panMaxLeft = -.5, panMaxRight = .6, panMaxTop = 1.3, panMaxBottom = -.4, panValue = 32 },
GnomeMale = { panMaxLeft = -.3, panMaxRight = .3, panMaxTop = .5, panMaxBottom = -.2, panValue = 52 },
GnomeFemale = { panMaxLeft = -.3, panMaxRight = .3, panMaxTop = .5, panMaxBottom = -.2, panValue = 60 },
TrollMale = { panMaxLeft = -.5, panMaxRight = .6, panMaxTop = 1.3, panMaxBottom = -.4, panValue = 27 },
TrollFemale = { panMaxLeft = -.4, panMaxRight = .4, panMaxTop = 1.5, panMaxBottom = -.4, panValue = 31 },
GoblinMale = { panMaxLeft = -.3, panMaxRight = .4, panMaxTop = .7, panMaxBottom = -.2, panValue = 43 },
GoblinFemale = { panMaxLeft = -.3, panMaxRight = .3, panMaxTop = .7, panMaxBottom = -.3, panValue = 43 },
BloodElfMale = { panMaxLeft = -.5, panMaxRight = .4, panMaxTop = 1.3, panMaxBottom = -.3, panValue = 36 },
BloodElfFemale = { panMaxLeft = -.3, panMaxRight = .2, panMaxTop = 1.2, panMaxBottom = -.3, panValue = 38 },
}
local function OnUpdate()
if this.rotating then
local x = GetCursorPosition()
this.rotation = this.rotation + (x - this.cursorX) * .025
this.cursorX = x
this:SetRotation(this.rotation, false)
elseif this.panning then
local settings = this.panning
local x, y = GetCursorPosition()
local scale = UIParent:GetEffectiveScale()
local modelScale = this:GetModelScale()
-- moves the model with the cursor at any zoom (4.3.4's formula)
local ratio = settings.panValue * 2 ^ ((1 + this.zoom) * 2) * scale / modelScale
local bound = scale * modelScale
this:SetPosition(this.panX,
Clamp(this.panY + (x - this.cursorX) / ratio, settings.panMaxLeft * bound, settings.panMaxRight * bound),
Clamp(this.panZ + (y - this.cursorY) / ratio, settings.panMaxBottom * bound, settings.panMaxTop * bound))
end
end
local function OnMouseDown()
this.cursorX, this.cursorY = GetCursorPosition()
if arg1 == "LeftButton" then
this.rotate = true
this.curx, this.cury = GetCursorPosition()
this.rotating = true
elseif arg1 == "RightButton" then
local _, race = UnitRace("player")
this.panning = PAN_SETTINGS[race .. (UnitSex("player") == 2 and "Male" or "Female")]
this.panX, this.panY, this.panZ = this:GetPosition()
end
end)
end
frame:HookScript("OnMouseUp", function()
this.rotate, this.curx, this.cury = nil, nil, nil
end)
local function OnMouseUp()
this.rotating, this.panning = nil, nil
end
local function OnMouseWheel()
this.zoom = Clamp(this.zoom + arg1 * ZOOM_STEP, 0, MAX_ZOOM)
this:SetPortraitZoom(this.zoom)
end
local function OnShow()
this.zoom = 0
this:SetPortraitZoom(0)
this:SetPosition(0, 0, 0)
end
function pfUI.api.EnableModelControls(frame)
frame.zoom = 0
frame:EnableMouse(true)
frame:EnableMouseWheel(true)
frame:HookScript("OnUpdate", OnUpdate)
frame:HookScript("OnMouseDown", OnMouseDown)
frame:HookScript("OnMouseUp", OnMouseUp)
frame:HookScript("OnMouseWheel", OnMouseWheel)
frame:HookScript("OnShow", OnShow)
end
end
local function SetHighlightEnter()
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["deDE"] = {
["Clear Rolls"] = nil,
["Click Action"] = nil,
["Click Casting"] = nil,
["Click to undress"] = nil,
["Clock"] = nil,
["Close"] = nil,
["Collapse Empty Slots"] = nil,
+1
View File
@@ -129,6 +129,7 @@ pfUI_translation["enUS"] = {
["Clear Rolls"] = nil,
["Click Action"] = nil,
["Click Casting"] = nil,
["Click to undress"] = nil,
["Clock"] = nil,
["Close"] = nil,
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["esES"] = {
["Clear Rolls"] = "Borrar los dados",
["Click Action"] = "Acción al hacer clic",
["Click Casting"] = "Lanzamiento al hacer click",
["Click to undress"] = nil,
["Clock"] = "Reloj",
["Close"] = "Cerrar",
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["frFR"] = {
["Clear Rolls"] = "Clear les roulements",
["Click Action"] = "Action au clic",
["Click Casting"] = "Clique sur le lancement de sort",
["Click to undress"] = nil,
["Clock"] = "Horloge",
["Close"] = "Fermer",
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["koKR"] = {
["Clear Rolls"] = nil,
["Click Action"] = "클릭 액션",
["Click Casting"] = nil,
["Click to undress"] = nil,
["Clock"] = "시계",
["Close"] = "닫기",
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["ruRU"] = {
["Clear Rolls"] = "Очистить броски",
["Click Action"] = "Действие при нажатии",
["Click Casting"] = "Каст по нажатию",
["Click to undress"] = nil,
["Clock"] = "Часы",
["Close"] = "Закрыть",
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["zhCN"] = {
["Clear Rolls"] = "清除Roll点",
["Click Action"] = "点击操作",
["Click Casting"] = "点击施法",
["Click to undress"] = nil,
["Clock"] = "时间",
["Close"] = "关闭",
["Collapse Empty Slots"] = nil,
+1
View File
@@ -123,6 +123,7 @@ pfUI_translation["zhTW"] = {
["Clear Rolls"] = "清除Roll點",
["Click Action"] = "點擊操作",
["Click Casting"] = nil,
["Click to undress"] = nil,
["Clock"] = "時間",
["Close"] = "關閉",
["Collapse Empty Slots"] = nil,
+1 -1
View File
@@ -219,7 +219,7 @@ pfUI:RegisterSkin("Auctionhouse", function ()
StripTextures(AuctionDressUpFrameCloseButton)
SkinCloseButton(AuctionDressUpFrameCloseButton, AuctionDressUpFrame, -6, -6)
EnableClickRotate(AuctionDressUpModel)
EnableModelControls(AuctionDressUpModel)
AuctionDressUpModelRotateLeftButton:Hide()
AuctionDressUpModelRotateRightButton:Hide()
+2 -2
View File
@@ -171,7 +171,7 @@ pfUI:RegisterSkin("Character", function ()
StripTextures(CharacterAttributesFrame)
StripTextures(CharacterResistanceFrame)
EnableClickRotate(CharacterModelFrame)
EnableModelControls(CharacterModelFrame)
CharacterModelFrameRotateLeftButton:Hide()
CharacterModelFrameRotateRightButton:Hide()
@@ -207,7 +207,7 @@ pfUI:RegisterSkin("Character", function ()
PetNameText:ClearAllPoints()
PetNameText:SetPoint("TOP", CharacterFrame.backdrop, "TOP", 0, -10)
EnableClickRotate(PetModelFrame)
EnableModelControls(PetModelFrame)
PetModelFrameRotateLeftButton:Hide()
PetModelFrameRotateRightButton:Hide()
+52 -1
View File
@@ -21,7 +21,7 @@ pfUI:RegisterSkin("Dress Up Frame", function ()
DressUpFrameDescriptionText:ClearAllPoints()
DressUpFrameDescriptionText:SetPoint("TOP", DressUpFrame.backdrop, "TOP", 0, -30)
EnableClickRotate(DressUpModel)
EnableModelControls(DressUpModel)
DressUpModelRotateLeftButton:Hide()
DressUpModelRotateRightButton:Hide()
@@ -29,4 +29,55 @@ pfUI:RegisterSkin("Dress Up Frame", function ()
SkinButton(DressUpFrameResetButton)
DressUpFrameResetButton:ClearAllPoints()
DressUpFrameResetButton:SetPoint("RIGHT", DressUpFrameCancelButton, "LEFT", -2*bpad, 0)
local undress = CreateFrame("Button", "pfDressUpFrameUndressButton", DressUpFrame, "UIPanelButtonTemplate")
undress:SetSize(80, 22)
undress:SetText(UNDRESS)
undress:SetPoint("RIGHT", DressUpFrameResetButton, "LEFT", -2*bpad, 0)
undress:SetScript("OnClick", function()
DressUpModel:Undress()
PlaySound("gsTitleOptionOK")
end)
SkinButton(undress)
-- one button per slot the model can undress, laid out like the paperdoll
local columns = {
{ point = "TOPLEFT", x = 8, anchor = "ANCHOR_LEFT", slots = { 1, 3, 15, 5, 4, 19, 9 } },
{ point = "TOPRIGHT", x = -8, anchor = "ANCHOR_RIGHT", slots = { 10, 6, 7, 8, 16, 17 } },
}
local function SlotOnClick()
DressUpModel:UndressSlot(this:GetID())
PlaySound("gsTitleOptionOK")
end
local function SlotOnEnter()
GameTooltip:SetOwner(this, this.anchor)
GameTooltip:SetText(this.label)
GameTooltip:AddLine(T["Click to undress"], 1, 1, 1)
GameTooltip:Show()
end
for _, column in ipairs(columns) do
for row, slot in ipairs(column.slots) do
local _, texture, _, slotName = C_PaperDollInfo.GetInventorySlotInfoForInvSlot(slot)
local button = CreateFrame("Button", nil, DressUpFrame)
button:SetID(slot)
button:SetSize(26, 26)
button:SetPoint(column.point, DressUpFrame.backdrop, column.point, column.x, -50 - (row - 1) * 30)
button:SetFrameLevel(DressUpModel:GetFrameLevel() + 1)
button.anchor, button.label = column.anchor, _G[slotName:upper()]
button.icon = button:CreateTexture(nil, "ARTWORK")
button.icon:SetTexture(texture)
CreateBackdrop(button)
SetAllPointsOffset(button.backdrop, button, 0)
HandleIcon(button.backdrop, button.icon)
button:SetScript("OnClick", SlotOnClick)
button:SetScript("OnEnter", SlotOnEnter)
button:SetScript("OnLeave", GameTooltip_Hide)
SetHighlight(button)
end
end
end)
+1 -1
View File
@@ -60,7 +60,7 @@ pfUI:RegisterSkin("Inspect", function ()
do -- Character Tab
StripTextures(InspectPaperDollFrame)
EnableClickRotate(InspectModelFrame)
EnableModelControls(InspectModelFrame)
local rotL = InspectModelRotateLeftButton or InspectModelFrameRotateLeftButton
if rotL then rotL:Hide() end
local rotR = InspectModelRotateRightButton or InspectModelFrameRotateRightButton
+1 -1
View File
@@ -15,7 +15,7 @@ pfUI:RegisterSkin("Pet Stable Master", function ()
PetStableTitleLabel:ClearAllPoints()
PetStableTitleLabel:SetPoint("TOP", PetStableFrame.backdrop, "TOP", 0, -10)
EnableClickRotate(PetStableModel)
EnableModelControls(PetStableModel)
PetStableModelRotateLeftButton:Hide()
PetStableModelRotateRightButton:Hide()
+1 -1
View File
@@ -23,7 +23,7 @@ pfUI:RegisterSkin("Guild Tabard", function ()
StripTextures(TabardFrameCostFrame)
EnableClickRotate(TabardModel)
EnableModelControls(TabardModel)
TabardCharacterModelRotateLeftButton:Hide()
TabardCharacterModelRotateRightButton:Hide()