|
2025-09-26 20:49
조회: 1,159
추천: 4
에멜무지로님 보세요~에디터의 커서와 문자열 높이가 맞지 않는 문제도 해결하고..
작성된 코드를 정리해봤어요. 참고해주세요. # WoW 애드온 TextArea 에디터 만들기 가이드 ## 1. 개요 WoW Classic에서 멀티라인 텍스트 입력을 위한 스크롤 가능한 TextArea를 만드는 완전한 가이드입니다. ## 2. 핵심 구조 TextArea는 3개의 프레임으로 구성됩니다: - **배경 프레임 (Frame)**: 전체 컨테이너 - **스크롤 프레임 (ScrollFrame)**: 스크롤 기능 제공 - **편집 상자 (EditBox)**: 실제 텍스트 입력 ## 3. 단계별 구현 방법 ### 3.1 기본 프레임 생성 ```lua -- 1. 배경 프레임 생성 local bg = CreateFrame("Frame", nil, parent, "BackdropTemplate") bg:SetSize(width, height) -- 원하는 크기 설정 bg:SetBackdrop({ bgFile = "Interface\Tooltips\UI-Tooltip-Background", edgeFile = "Interface\Tooltips\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 } }) bg:SetBackdropColor(0, 0, 0, 0.8) -- 배경색 설정 ``` ### 3.2 스크롤 프레임 추가 ```lua -- 2. 스크롤 프레임 생성 (템플릿 사용이 중요!) local scrollFrame = CreateFrame("ScrollFrame", nil, bg, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", 8, -8) scrollFrame:SetPoint("BOTTOMRIGHT", -26, 8) -- 스크롤바 공간 확보 (-26) ``` ### 3.3 EditBox 생성 및 설정 ```lua -- 3. EditBox 생성 local editBox = CreateFrame("EditBox", nil, scrollFrame) editBox:SetMultiLine(true) -- 필수: 멀티라인 활성화 editBox:SetAutoFocus(false) -- 자동 포커스 방지 editBox:SetFontObject(GameFontHighlight) -- 폰트 설정 editBox:SetWidth(scrollFrame:GetWidth() - 5) -- 너비 설정 editBox:SetHeight(2000) -- 충분한 고정 높이 (중요!) editBox:SetMaxLetters(5000) -- 최대 글자 수 editBox:SetTextInsets(5, 5, 5, 25) -- 여백 (좌, 우, 상, 하) ``` ### 3.4 폰트 및 줄 간격 설정 (커서 정렬 핵심) ```lua -- 4. 폰트와 줄 간격 설정 local font, size = ChatFontNormal:GetFont() editBox:SetFont(font, size, "") editBox:SetSpacing(0) -- 줄 간격 0으로 설정 (커서 정렬에 중요!) ``` ### 3.5 ScrollChild 설정 ```lua -- 5. ScrollChild로 EditBox 등록 scrollFrame:SetScrollChild(editBox) ``` ## 4. 핵심 이벤트 처리 ### 4.1 ESC 키로 포커스 해제 ```lua editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) ``` ### 4.2 자동 스크롤 구현 (가장 중요!) ```lua -- 재귀 방지를 위한 플래그 editBox.isScrolling = false editBox:SetScript("OnCursorChanged", function(self, x, y, w, h) if self.isScrolling then return end -- 재귀 방지 local scrollOffset = scrollFrame:GetVerticalScroll() local scrollHeight = scrollFrame:GetHeight() local cursorTop = -y -- y는 음수로 전달됨 local cursorBottom = -y + h -- 커서가 위쪽 경계를 벗어난 경우 if cursorTop < scrollOffset then self.isScrolling = true scrollFrame:SetVerticalScroll(math.max(0, cursorTop - 5)) C_Timer.After(0.01, function() self.isScrolling = false end) -- 커서가 아래쪽 경계를 벗어난 경우 elseif cursorBottom > (scrollOffset + scrollHeight - 30) then self.isScrolling = true local newScroll = cursorBottom - scrollHeight + 30 local maxScroll = self:GetHeight() - scrollHeight scrollFrame:SetVerticalScroll(math.min(maxScroll, math.max(0, newScroll))) C_Timer.After(0.01, function() self.isScrolling = false end) end end) ``` ### 4.3 마우스 휠 스크롤 ```lua scrollFrame:EnableMouseWheel(true) scrollFrame:SetScript("OnMouseWheel", function(self, delta) local current = self:GetVerticalScroll() local maxScroll = self:GetVerticalScrollRange() local scrollStep = 20 if delta > 0 then -- 위로 스크롤 self:SetVerticalScroll(math.max(0, current - scrollStep)) else -- 아래로 스크롤 self:SetVerticalScroll(math.min(maxScroll, current + scrollStep)) end end) ``` ### 4.4 배경 클릭 시 포커스 ```lua bg:EnableMouse(true) bg:SetScript("OnMouseDown", function(self, button) if button == "LeftButton" then editBox:SetFocus() end end) ``` ## 5. 초기화 지연 실행 ```lua -- EditBox 너비를 실제 ScrollFrame 너비에 맞추기 C_Timer.After(0.1, function() local w = scrollFrame:GetWidth() if w and w > 0 then editBox:SetWidth(w - 5) end end) ``` ## 6. 완성된 CreateTextArea 함수 ```lua local function CreateTextArea(parent, width, height, maxLetters) -- 배경 프레임 local bg = CreateFrame("Frame", nil, parent, "BackdropTemplate") bg:SetSize(width, height) bg:SetBackdrop({ bgFile = "Interface\Tooltips\UI-Tooltip-Background", edgeFile = "Interface\Tooltips\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 } }) bg:SetBackdropColor(0, 0, 0, 0.8) -- ScrollFrame 생성 local scrollFrame = CreateFrame("ScrollFrame", nil, bg, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", 8, -8) scrollFrame:SetPoint("BOTTOMRIGHT", -26, 8) -- EditBox 생성 local editBox = CreateFrame("EditBox", nil, scrollFrame) editBox:SetMultiLine(true) editBox:SetAutoFocus(false) editBox:SetFontObject(GameFontHighlight) editBox:SetWidth(scrollFrame:GetWidth() - 5) editBox:SetHeight(2000) -- 충분히 큰 고정 높이 editBox:SetMaxLetters(maxLetters or 5000) editBox:SetTextInsets(5, 5, 5, 25) -- 하단 여백 증가 -- 폰트 설정 local font, size = ChatFontNormal:GetFont() editBox:SetFont(font, size, "") editBox:SetSpacing(0) -- 커서 정렬을 위해 0으로 설정 -- ScrollChild로 설정 scrollFrame:SetScrollChild(editBox) -- ESC 키로 포커스 해제 editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) -- 커서 위치 변경 시 자동 스크롤 editBox.isScrolling = false editBox:SetScript("OnCursorChanged", function(self, x, y, w, h) if self.isScrolling then return end local scrollOffset = scrollFrame:GetVerticalScroll() local scrollHeight = scrollFrame:GetHeight() local cursorTop = -y local cursorBottom = -y + h if cursorTop < scrollOffset then self.isScrolling = true scrollFrame:SetVerticalScroll(math.max(0, cursorTop - 5)) C_Timer.After(0.01, function() self.isScrolling = false end) elseif cursorBottom > (scrollOffset + scrollHeight - 30) then self.isScrolling = true local newScroll = cursorBottom - scrollHeight + 30 local maxScroll = self:GetHeight() - scrollHeight scrollFrame:SetVerticalScroll(math.min(maxScroll, math.max(0, newScroll))) C_Timer.After(0.01, function() self.isScrolling = false end) end end) -- 배경 클릭 시 EditBox로 포커스 bg:EnableMouse(true) bg:SetScript("OnMouseDown", function(self, button) if button == "LeftButton" then editBox:SetFocus() end end) -- 마우스 휠 스크롤 scrollFrame:EnableMouseWheel(true) scrollFrame:SetScript("OnMouseWheel", function(self, delta) local current = self:GetVerticalScroll() local maxScroll = self:GetVerticalScrollRange() local scrollStep = 20 if delta > 0 then self:SetVerticalScroll(math.max(0, current - scrollStep)) else self:SetVerticalScroll(math.min(maxScroll, current + scrollStep)) end end) -- EditBox 크기 조정 C_Timer.After(0.1, function() local w = scrollFrame:GetWidth() if w and w > 0 then editBox:SetWidth(w - 5) end end) return bg, editBox, scrollFrame end ``` ## 7. 사용 방법 ### 7.1 TextArea 생성 ```lua local bg, editBox, scrollFrame = CreateTextArea(parent, 400, 300, 5000) bg:SetPoint("CENTER", UIParent, "CENTER", 0, 0) ``` ### 7.2 텍스트 변경 감지 ```lua editBox:HookScript("OnTextChanged", function(self, userInput) if userInput then -- 사용자 입력인 경우만 local text = self:GetText() -- 텍스트 처리 로직 print("텍스트 변경됨:", text) end end) ``` ### 7.3 텍스트 가져오기/설정하기 ```lua -- 텍스트 설정 editBox:SetText("초기 텍스트") -- 텍스트 가져오기 local text = editBox:GetText() ``` ## 8. 주요 문제 해결 방법 ### 8.1 커서가 텍스트와 정렬되지 않는 경우 - **해결책**: `SetSpacing(0)` 사용 - 폰트에 따라 0, 1, 2 중 적절한 값 선택 ### 8.2 커서가 하단에서 잘리는 경우 - **해결책**: `SetTextInsets(5, 5, 5, 25)` - 하단 여백 증가 - 자동 스크롤 트리거 위치 조정 (30-40px 여유) ### 8.3 스크롤이 제대로 작동하지 않는 경우 - **해결책**: EditBox 높이를 충분히 크게 설정 (2000px) - `UIPanelScrollFrameTemplate` 템플릿 사용 필수 ### 8.4 OnTextChanged 핸들러 충돌 - **해결책**: `HookScript` 사용 (SetScript 대신) - CreateTextArea에서는 OnTextChanged를 설정하지 않음 ### 8.5 무한 루프/크래시 방지 - **해결책**: isScrolling 플래그 사용 - C_Timer.After(0.01) 사용하여 비동기 처리 ## 9. 최적화 팁 1. **고정 높이 사용**: 동적 높이 조정보다 안정적 2. **재귀 방지 플래그**: 이벤트 처리 시 필수 3. **적절한 여백**: 경계 잘림 방지 4. **템플릿 활용**: UIPanelScrollFrameTemplate 사용 5. **지연 실행**: 초기화 시 C_Timer.After 활용 ## 10. WoW API 제약사항 1. `UpdateScrollChildRect()` - 존재하지 않음 2. `GetSpacing()` - 일부 버전에서 지원 안 됨 3. EditBox 높이 제한 - 매우 큰 값은 문제 발생 4. OnTextChanged - SetScript와 HookScript 충돌 가능 ## 11. 테스트 체크리스트 - [ ] 텍스트 입력 가능 - [ ] 커서와 텍스트 정렬 확인 - [ ] 자동 스크롤 작동 - [ ] 마우스 휠 스크롤 작동 - [ ] ESC 키로 포커스 해제 - [ ] 긴 텍스트 입력 시 스크롤 - [ ] 하단 커서 표시 확인 - [ ] /reload 후 정상 작동 --- ## 현재 CreateTextArea 함수 (FoxChat/FoxChat_Config_TabUI.lua) ### 위치 `/Users/toto/devel/wow/exam01/FoxChat/FoxChat_Config_TabUI.lua` (18-88줄) ### 전체 코드 ```lua -- 재사용 가능한 TextArea 생성 함수 (안정적인 버전) local function CreateTextArea(parent, width, height, maxLetters) -- 배경 프레임 local bg = CreateFrame("Frame", nil, parent, "BackdropTemplate") bg:SetSize(width, height) bg:SetBackdrop({ bgFile = "Interface\Tooltips\UI-Tooltip-Background", edgeFile = "Interface\Tooltips\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 } }) bg:SetBackdropColor(0, 0, 0, 0.8) -- ScrollFrame 생성 local scrollFrame = CreateFrame("ScrollFrame", nil, bg, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", 8, -8) scrollFrame:SetPoint("BOTTOMRIGHT", -26, 8) -- EditBox 생성 local editBox = CreateFrame("EditBox", nil, scrollFrame) editBox:SetMultiLine(true) editBox:SetAutoFocus(false) editBox:SetFontObject(GameFontHighlight) editBox:SetWidth(scrollFrame:GetWidth() - 5) editBox:SetHeight(2000) -- 충분히 큰 고정 높이 editBox:SetMaxLetters(maxLetters or 0) editBox:SetTextInsets(5, 5, 5, 25) -- 하단 여백 증가 -- 줄 간격 조정 (커서 위치 문제 해결) editBox:SetSpacing(0) -- 줄 간격을 0으로 설정 -- ScrollChild로 설정 scrollFrame:SetScrollChild(editBox) -- ESC 키로 포커스 해제 editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) -- 커서 위치 변경 시 자동 스크롤 (안전한 버전) editBox.isScrolling = false editBox:SetScript("OnCursorChanged", function(self, x, y, w, h) if self.isScrolling then return end -- 재귀 방지 local scrollOffset = scrollFrame:GetVerticalScroll() local scrollHeight = scrollFrame:GetHeight() local cursorTop = -y local cursorBottom = -y + h -- 커서가 화면 밖으로 나가는 것을 방지 local maxScroll = self:GetHeight() - scrollHeight if maxScroll < 0 then maxScroll = 0 end -- 스크롤 업데이트가 필요한 경우만 처리 if cursorTop < scrollOffset then self.isScrolling = true scrollFrame:SetVerticalScroll(math.max(0, cursorTop - 5)) -- 상단 여유 추가 C_Timer.After(0.01, function() self.isScrolling = false end) elseif cursorBottom > (scrollOffset + scrollHeight - 40) then -- 하단 여유를 40으로 증가 self.isScrolling = true local newScroll = cursorBottom - scrollHeight + 40 scrollFrame:SetVerticalScroll(math.min(maxScroll, math.max(0, newScroll))) -- 최대 스크롤 범위 제한 C_Timer.After(0.01, function() self.isScrolling = false end) end end) -- 배경 클릭 시 EditBox로 포커스 bg:EnableMouse(true) bg:SetScript("OnMouseDown", function(self, button) if button == "LeftButton" then editBox:SetFocus() end end) -- 마우스 휠 스크롤 scrollFrame:EnableMouseWheel(true) scrollFrame:SetScript("OnMouseWheel", function(self, delta) local current = self:GetVerticalScroll() local maxScroll = self:GetVerticalScrollRange() local scrollStep = 20 if delta > 0 then self:SetVerticalScroll(math.max(0, current - scrollStep)) else self:SetVerticalScroll(math.min(maxScroll, current + scrollStep)) end end) -- EditBox 크기를 실제로 맞추기 위한 지연 실행 C_Timer.After(0.1, function() local w = scrollFrame:GetWidth() if w and w > 0 then editBox:SetWidth(w - 5) end end) bg.editBox = editBox bg.scrollFrame = scrollFrame return bg, editBox, scrollFrame end ``` ## WowPostIt 버전 CreateTextArea (WowPostIt/WowPostIt_Config.lua) ### 위치 `/Users/toto/devel/wow/exam01/WowPostIt/WowPostIt_Config.lua` (11-88줄) ### 전체 코드 ```lua -- 재사용 가능한 TextArea 생성 함수 (안정적인 버전) local function CreateTextArea(parent, width, height, maxLetters) -- 배경 프레임 local bg = CreateFrame("Frame", nil, parent, "BackdropTemplate") bg:SetSize(width, height) bg:SetBackdrop({ bgFile = "Interface\Tooltips\UI-Tooltip-Background", edgeFile = "Interface\Tooltips\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 } }) bg:SetBackdropColor(0, 0, 0, 0.8) -- ScrollFrame 생성 local scrollFrame = CreateFrame("ScrollFrame", nil, bg, "UIPanelScrollFrameTemplate") scrollFrame:SetPoint("TOPLEFT", 8, -8) scrollFrame:SetPoint("BOTTOMRIGHT", -26, 8) -- EditBox 생성 local editBox = CreateFrame("EditBox", nil, scrollFrame) editBox:SetMultiLine(true) editBox:SetAutoFocus(false) editBox:SetWidth(scrollFrame:GetWidth() - 5) editBox:SetHeight(2000) -- 충분히 큰 고정 높이 editBox:SetMaxLetters(maxLetters or 5000) editBox:SetTextInsets(5, 5, 5, 5) -- 폰트 설정 (줄 간격 문제 해결) local font, size = ChatFontNormal:GetFont() editBox:SetFont(font, size, "") editBox:SetSpacing(0) -- 줄 간격을 0으로 설정하여 커서 정렬 -- ScrollChild로 설정 scrollFrame:SetScrollChild(editBox) -- ESC 키로 포커스 해제 editBox:SetScript("OnEscapePressed", function(self) self:ClearFocus() end) -- 커서 위치 변경 시 자동 스크롤 (안전한 버전) editBox.isScrolling = false editBox:SetScript("OnCursorChanged", function(self, x, y, w, h) if self.isScrolling then return end -- 재귀 방지 local scrollOffset = scrollFrame:GetVerticalScroll() local scrollHeight = scrollFrame:GetHeight() local cursorTop = -y local cursorBottom = -y + h -- 스크롤 업데이트가 필요한 경우만 처리 if cursorTop < scrollOffset then self.isScrolling = true scrollFrame:SetVerticalScroll(math.max(0, cursorTop)) C_Timer.After(0.01, function() self.isScrolling = false end) elseif cursorBottom > (scrollOffset + scrollHeight - 30) then -- 여유 공간을 30으로 증가 self.isScrolling = true scrollFrame:SetVerticalScroll(math.max(0, cursorBottom - scrollHeight + 30)) -- 더 많이 스크롤 C_Timer.After(0.01, function() self.isScrolling = false end) end end) -- 배경 클릭 시 EditBox로 포커스 bg:EnableMouse(true) bg:SetScript("OnMouseDown", function(self, button) if button == "LeftButton" then editBox:SetFocus() end end) -- 마우스 휠 스크롤 scrollFrame:EnableMouseWheel(true) scrollFrame:SetScript("OnMouseWheel", function(self, delta) local current = self:GetVerticalScroll() local maxScroll = self:GetVerticalScrollRange() local scrollStep = 20 if delta > 0 then self:SetVerticalScroll(math.max(0, current - scrollStep)) else self:SetVerticalScroll(math.min(maxScroll, current + scrollStep)) end end) -- EditBox 크기를 실제로 맞추기 위한 지연 실행 C_Timer.After(0.1, function() local w = scrollFrame:GetWidth() if w and w > 0 then editBox:SetWidth(w - 5) end end) return bg, editBox, scrollFrame end ``` ## WowPostIt 에디터 통합 부분 ### 위치 `WowPostIt_Config.lua` (385-409줄) ### 통합 코드 ```lua -- CreateTextArea를 사용한 편집 영역 생성 local editAreaBg, editBox, editScrollFrame = CreateTextArea(editFrame, 390, 340, 5000) editAreaBg:SetPoint("TOPLEFT", editFrame, "TOPLEFT", 0, 0) editAreaBg:SetPoint("BOTTOMRIGHT", editFrame, "BOTTOMRIGHT", 0, 0) -- 포스트잇 배경색 적용 editAreaBg:SetBackdropColor(1.0, 1.0, 0.6, 0.3) -- 기본 노란색 -- 포스트잇 배경 텍스처 추가 local editBg = editAreaBg:CreateTexture(nil, "BACKGROUND") editBg:SetAllPoints(editAreaBg) editBg:SetTexture("Interface\BUTTONS\WHITE8X8") editBg:SetVertexColor(1.0, 1.0, 0.6, 0.3) editBox.bg = editBg -- 기존 코드와의 호환성을 위해 유지 -- noteEditBox를 전역 변수에 할당 noteEditBox = editBox -- noteWindow에 참조 저장 (레이아웃 업데이트를 위해) noteWindow.editScrollFrame = editScrollFrame noteWindow.editAreaBg = editAreaBg -- 텍스트 변경 시 자동 저장 noteEditBox:HookScript("OnTextChanged", function(self, userInput) if userInput and currentNoteId then addon.SaveNote(currentNoteId, self:GetText()) addon:UpdateNoteList() end end) ``` ## 전문가에게 질문할 내용 1. **커서 위치 문제** - EditBox에서 커서가 텍스트 줄 위쪽에 표시되는 문제를 어떻게 해결할 수 있을까요? - SetSpacing, SetFont, SetTextInsets 외에 영향을 줄 수 있는 다른 설정이 있을까요? 2. **동적 높이 조정** - EditBox의 높이를 텍스트 양에 따라 동적으로 조정하면서도 스크롤이 제대로 작동하게 하는 방법은? - UpdateScrollChildRect() 대신 사용할 수 있는 API는? 3. **OnTextChanged 핸들러** - CreateTextArea에서 기본 동작을 설정하고, 이후 각 용도별로 추가 핸들러를 HookScript로 연결하는 올바른 패턴은? ## 관련 파일 경로 - `/Users/toto/devel/wow/exam01/FoxChat/FoxChat_Config_TabUI.lua` - `/Users/toto/devel/wow/exam01/WowPostIt/WowPostIt_Config.lua` ## WoW API 버전 - WoW Classic 1.15 (20주년 하드코어)
EXP
7,958
(69%)
/ 8,201
|
가을의미소