From 70cc994a979c187727dee8a55a6ed57110e01518 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri, 30 Oct 2026 23:11:09 -0500 Subject: [PATCH] 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. --- api/ui-widgets.lua | 106 +++++++++++++++++++++++++++-------- env/translations_deDE.lua | 1 + env/translations_enUS.lua | 1 + env/translations_esES.lua | 1 + env/translations_frFR.lua | 1 + env/translations_koKR.lua | 1 + env/translations_ruRU.lua | 1 + env/translations_zhCN.lua | 1 + env/translations_zhTW.lua | 1 + skins/blizzard/auction.lua | 2 +- skins/blizzard/character.lua | 4 +- skins/blizzard/dressup.lua | 53 +++++++++++++++++- skins/blizzard/inspect.lua | 2 +- skins/blizzard/stable.lua | 2 +- skins/blizzard/tabard.lua | 2 +- 15 files changed, 149 insertions(+), 30 deletions(-) diff --git a/api/ui-widgets.lua b/api/ui-widgets.lua index 674cd099..1c5f4bf9 100644 --- a/api/ui-widgets.lua +++ b/api/ui-widgets.lua @@ -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() diff --git a/env/translations_deDE.lua b/env/translations_deDE.lua index c33ad46b..d27eb271 100644 --- a/env/translations_deDE.lua +++ b/env/translations_deDE.lua @@ -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, diff --git a/env/translations_enUS.lua b/env/translations_enUS.lua index 1c391784..7dcd760d 100644 --- a/env/translations_enUS.lua +++ b/env/translations_enUS.lua @@ -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, diff --git a/env/translations_esES.lua b/env/translations_esES.lua index 1fd85b2b..dcda6177 100644 --- a/env/translations_esES.lua +++ b/env/translations_esES.lua @@ -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, diff --git a/env/translations_frFR.lua b/env/translations_frFR.lua index e66fd974..3550e702 100644 --- a/env/translations_frFR.lua +++ b/env/translations_frFR.lua @@ -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, diff --git a/env/translations_koKR.lua b/env/translations_koKR.lua index 0c4b9bf9..e2853b3b 100644 --- a/env/translations_koKR.lua +++ b/env/translations_koKR.lua @@ -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, diff --git a/env/translations_ruRU.lua b/env/translations_ruRU.lua index 02a0264f..fb193eec 100644 --- a/env/translations_ruRU.lua +++ b/env/translations_ruRU.lua @@ -123,6 +123,7 @@ pfUI_translation["ruRU"] = { ["Clear Rolls"] = "Очистить броски", ["Click Action"] = "Действие при нажатии", ["Click Casting"] = "Каст по нажатию", + ["Click to undress"] = nil, ["Clock"] = "Часы", ["Close"] = "Закрыть", ["Collapse Empty Slots"] = nil, diff --git a/env/translations_zhCN.lua b/env/translations_zhCN.lua index 0ba4e83e..26177788 100644 --- a/env/translations_zhCN.lua +++ b/env/translations_zhCN.lua @@ -123,6 +123,7 @@ pfUI_translation["zhCN"] = { ["Clear Rolls"] = "清除Roll点", ["Click Action"] = "点击操作", ["Click Casting"] = "点击施法", + ["Click to undress"] = nil, ["Clock"] = "时间", ["Close"] = "关闭", ["Collapse Empty Slots"] = nil, diff --git a/env/translations_zhTW.lua b/env/translations_zhTW.lua index 2d37310b..f2d65a8b 100644 --- a/env/translations_zhTW.lua +++ b/env/translations_zhTW.lua @@ -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, diff --git a/skins/blizzard/auction.lua b/skins/blizzard/auction.lua index 12047cfe..6f566e36 100644 --- a/skins/blizzard/auction.lua +++ b/skins/blizzard/auction.lua @@ -219,7 +219,7 @@ pfUI:RegisterSkin("Auctionhouse", function () StripTextures(AuctionDressUpFrameCloseButton) SkinCloseButton(AuctionDressUpFrameCloseButton, AuctionDressUpFrame, -6, -6) - EnableClickRotate(AuctionDressUpModel) + EnableModelControls(AuctionDressUpModel) AuctionDressUpModelRotateLeftButton:Hide() AuctionDressUpModelRotateRightButton:Hide() diff --git a/skins/blizzard/character.lua b/skins/blizzard/character.lua index 3b0a34c0..2c712948 100644 --- a/skins/blizzard/character.lua +++ b/skins/blizzard/character.lua @@ -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() diff --git a/skins/blizzard/dressup.lua b/skins/blizzard/dressup.lua index 73f2281e..73252dff 100644 --- a/skins/blizzard/dressup.lua +++ b/skins/blizzard/dressup.lua @@ -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) diff --git a/skins/blizzard/inspect.lua b/skins/blizzard/inspect.lua index 1cd6b99c..bf648382 100644 --- a/skins/blizzard/inspect.lua +++ b/skins/blizzard/inspect.lua @@ -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 diff --git a/skins/blizzard/stable.lua b/skins/blizzard/stable.lua index 5faed20e..06b3a28e 100644 --- a/skins/blizzard/stable.lua +++ b/skins/blizzard/stable.lua @@ -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() diff --git a/skins/blizzard/tabard.lua b/skins/blizzard/tabard.lua index afe75c16..660e225a 100644 --- a/skins/blizzard/tabard.lua +++ b/skins/blizzard/tabard.lua @@ -23,7 +23,7 @@ pfUI:RegisterSkin("Guild Tabard", function () StripTextures(TabardFrameCostFrame) - EnableClickRotate(TabardModel) + EnableModelControls(TabardModel) TabardCharacterModelRotateLeftButton:Hide() TabardCharacterModelRotateRightButton:Hide()