KLingo Project Documentation 1.0.0
Unreal Engine 5.6 C++ Project Documentation
로딩중...
검색중...
일치하는것 없음
UPageScrollView.cpp
이 파일의 문서화 페이지로 가기
1// Copyright (c) 2025 Doppleddiggong. All rights reserved. Unauthorized copying, modification, or distribution of this file, via any medium is strictly prohibited. Proprietary and confidential.
2
3#include "UPageScrollView.h"
4#include "UPageScrollItem.h"
5#include "UPageScrollDot.h"
6#include "Components/CanvasPanel.h"
7#include "Components/CanvasPanelSlot.h"
8#include "TimerManager.h"
9
11{
12 Super::NativeConstruct();
13
14 // 뷰포트 크기 계산 (SizeBox의 크기)
15 if (UCanvasPanelSlot* RootSlot = Cast<UCanvasPanelSlot>(Slot))
16 {
17 ViewportSize = RootSlot->GetSize();
18 }
19
20 // ViewportSize 계산 실패 시 PageSize 사용
21 if (ViewportSize.X <= 1.0f || ViewportSize.Y <= 1.0f)
22 {
24 }
25
26 // 기본 페이지로 이동 (애니메이션 없이)
27 if (pageItemList.Num() > 0 && pageItemList.IsValidIndex(DefaultPageIndex))
28 {
30 }
31}
32
34{
35 Super::NativeDestruct();
36
37 // 타이머 정리
39}
40
41FReply UPageScrollView::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
42{
43 if (!InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
44 return FReply::Unhandled();
45
46 // 애니메이션 중지
48
49 // 드래그 시작
50 bIsDragging = true;
51 dragStartPosition = InMouseEvent.GetScreenSpacePosition();
53 dragStartTime = GetWorld() ? GetWorld()->GetTimeSeconds() : 0.0f;
55
56 // TakeWidget()으로 SWidget을 가져와서 Mouse Capture
57 TSharedPtr<SWidget> CachedWidget = GetCachedWidget();
58 if (CachedWidget.IsValid())
59 {
60 return FReply::Handled().CaptureMouse(CachedWidget.ToSharedRef());
61 }
62
63 return FReply::Handled();
64}
65
66FReply UPageScrollView::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
67{
68 if (!bIsDragging)
69 return FReply::Unhandled();
70
71 bIsDragging = false;
72
73 // 드래그 거리 계산
74 FVector2D dragDelta = dragCurrentPosition - dragStartPosition;
75 float dragDistance = dragDelta.Size();
76
77 // 작은 드래그는 클릭으로 처리 (버튼 이벤트 전달)
78 if (dragDistance <= 10.0f)
79 {
80 return FReply::Handled().ReleaseMouseCapture();
81 }
82
83 // 드래그 속도 계산 (픽셀/초)
84 float dragTime = GetWorld() ? (GetWorld()->GetTimeSeconds() - dragStartTime) : 0.01f;
85 float velocity = dragDistance / FMath::Max(dragTime, 0.01f);
86
87 // 목표 페이지 계산
88 int32 targetPage = CalculateTargetPage(dragDelta, velocity);
89
90 // 목표 페이지로 이동
91 MovePage(targetPage, true);
92
93 return FReply::Handled().ReleaseMouseCapture();
94}
95
96FReply UPageScrollView::NativeOnMouseMove(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
97{
98 if (!bIsDragging)
99 return FReply::Unhandled();
100
101 dragCurrentPosition = InMouseEvent.GetScreenSpacePosition();
102
103 // 드래그 중 실시간 컨테이너 위치 업데이트
105
106 return FReply::Handled();
107}
108
109void UPageScrollView::MovePage(int32 PageIndex, bool bAnimate)
110{
111 if (!pageItemList.IsValidIndex(PageIndex))
112 return;
113
114 int32 prevPage = CurPageIndex;
115 CurPageIndex = PageIndex;
116
117 FVector2D targetPosition = GetPagePosition(PageIndex);
118
119 // Phase 1: 애니메이션 없이 즉시 이동
120 if (!bAnimate || SnapAnimationSpeed <= 0.0f)
121 {
122 SetContainerPosition(targetPosition);
123
124 // 페이지 변경 이벤트 발생
125 if (prevPage != CurPageIndex)
126 {
127 OnPageChanged.Broadcast(prevPage, CurPageIndex);
128 }
129
130 // 인디케이터 업데이트
132
133 // 스케일 효과 업데이트
135 {
137 }
138 return;
139 }
140
141 // Phase 2에서 구현: 애니메이션 이동
142 UCanvasPanelSlot* containerSlot = Cast<UCanvasPanelSlot>(PageContainer->Slot);
143 if (!containerSlot)
144 {
145 SetContainerPosition(targetPosition);
146 return;
147 }
148
149 animStartPosition = containerSlot->GetPosition();
150 animTargetPosition = targetPosition;
151 animElapsedTime = 0.0f;
152 bIsAnimating = true;
153
154 // 타이머 시작
155 if (GetWorld())
156 {
157 GetWorld()->GetTimerManager().ClearTimer(snapAnimTimerHandle);
158 GetWorld()->GetTimerManager().SetTimer(
160 this,
162 0.016f, // ~60 FPS
163 true
164 );
165 }
166}
167
169{
171 return;
172
173 // 기존 페이지 제거
174 pageItemList.Empty();
175 PageContainer->ClearChildren();
176
177 // 새 페이지 생성
178 for (int32 i = 0; i < ItemCount; ++i)
179 {
180 UPageScrollItem* newItem = CreateWidget<UPageScrollItem>(GetWorld(), PageItemClass);
181 if (!newItem)
182 continue;
183
184 // 페이지 초기화
185 newItem->InitData(i, this);
186
187 // PageContainer에 추가
188 UCanvasPanelSlot* slot = PageContainer->AddChildToCanvas(newItem);
189 if (slot)
190 {
191 // 페이지 아이템 위치 설정 (0부터 오른쪽으로 배치)
192 float stride = GetPageStride();
193 FVector2D itemPosition;
194 if (ScrollDirection == EPageScrollDirection::Horizontal)
195 itemPosition = FVector2D(stride * i, 0.0f);
196 else
197 itemPosition = FVector2D(0.0f, stride * i);
198
199 slot->SetAnchors(FAnchors(0.0f, 0.0f, 0.0f, 0.0f)); // 왼쪽 위 고정
200 slot->SetAlignment(FVector2D(0.0f, 0.0f));
201 slot->SetPosition(itemPosition);
202 slot->SetSize(PageSize);
203 }
204
205 pageItemList.Add(newItem);
206 }
207
208 // 인디케이터 업데이트
210 {
211 PageDotIndicator->SetNumberOfPages(ItemCount);
212
213 // 페이지가 1개 이하면 인디케이터 숨김
214 PageDotIndicator->SetVisibility(ItemCount > 1 ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
215 }
216
217 // 기본 페이지로 이동
218 if (pageItemList.Num() > 0 && pageItemList.IsValidIndex(DefaultPageIndex))
219 {
221 }
222}
223
225{
226 int32 targetPage = FMath::Clamp(CurPageIndex + 1, 0, pageItemList.Num() - 1);
227 MovePage(targetPage, true);
228}
229
231{
232 int32 targetPage = FMath::Clamp(CurPageIndex - 1, 0, pageItemList.Num() - 1);
233 MovePage(targetPage, true);
234}
235
236int32 UPageScrollView::CalculateTargetPage(FVector2D DragDelta, float Velocity) const
237{
238 float delta = (ScrollDirection == EPageScrollDirection::Horizontal)
239 ? DragDelta.X
240 : DragDelta.Y;
241
242 int32 targetPage = CurPageIndex;
243
244 // 빠른 스와이프 감지
245 if (Velocity >= FastSwipeThreshold)
246 {
247 // 양수: 오른쪽/아래로 드래그 → 이전 페이지
248 // 음수: 왼쪽/위로 드래그 → 다음 페이지
249 targetPage = (delta > 0) ? CurPageIndex - 1 : CurPageIndex + 1;
250 }
251 else
252 {
253 // 느린 드래그: 가장 가까운 페이지로 스냅
254 float dragRatio = delta / GetPageStride();
255 int32 dragPages = FMath::RoundToInt(dragRatio);
256 targetPage = CurPageIndex - dragPages; // 음수 방향 (Unity와 동일)
257 }
258
259 // 범위 제한
260 return FMath::Clamp(targetPage, 0, pageItemList.Num() - 1);
261}
262
264{
265 // Canvas Slot 유효성 검사
266 UCanvasPanelSlot* containerSlot = Cast<UCanvasPanelSlot>(PageContainer->Slot);
267 if (!containerSlot)
268 {
270 return;
271 }
272
273 // 애니메이션 진행
274 animElapsedTime += 0.016f; // ~60 FPS 기준
275 const float Alpha = FMath::Clamp(animElapsedTime / SnapAnimationSpeed, 0.0f, 1.0f);
276
277 // Ease-out curve 적용
278 float easedAlpha = FMath::InterpEaseOut(0.0f, 1.0f, Alpha, 2.0f);
279
280 // Lerp를 사용한 부드러운 이동
281 FVector2D currentPosition = FMath::Lerp(animStartPosition, animTargetPosition, easedAlpha);
282 containerSlot->SetPosition(currentPosition);
283
284 // 스케일 효과 업데이트
286 {
288 }
289
290 // 애니메이션 완료 확인
291 if (Alpha >= 1.0f)
292 {
294
295 // 페이지 변경 이벤트 발생
297 {
299 }
300
301 // 인디케이터 업데이트
303 }
304}
305
307{
308 if (GetWorld())
309 {
310 GetWorld()->GetTimerManager().ClearTimer(snapAnimTimerHandle);
311 }
312
313 bIsAnimating = false;
314}
315
316FVector2D UPageScrollView::GetPagePosition(int32 PageIndex) const
317{
318 float stride = GetPageStride();
319 float offset = -stride * PageIndex; // 음수: 컨테이너를 왼쪽/위로 이동
320
321 return ScrollDirection == EPageScrollDirection::Horizontal ? FVector2D(offset, 0.0f) : FVector2D(0.0f, offset);
322}
323
325{
326 if (UCanvasPanelSlot* containerSlot = Cast<UCanvasPanelSlot>(PageContainer->Slot))
327 {
328 containerSlot->SetPosition(Position);
329 }
330}
331
333{
335 {
336 PageDotIndicator->SetCurrentPage(CurPageIndex);
337 }
338}
339
341{
343 return;
344
345 UCanvasPanelSlot* containerSlot = Cast<UCanvasPanelSlot>(PageContainer->Slot);
346 if (!containerSlot)
347 return;
348
349 FVector2D containerPos = containerSlot->GetPosition();
350 // ViewportSize 대신 PageSize 사용 (ViewportSize 계산이 실패할 수 있음)
351 float viewportCenter = (ScrollDirection == EPageScrollDirection::Horizontal)
352 ? PageSize.X * 0.5f
353 : PageSize.Y * 0.5f;
354
355 for (int32 i = 0; i < pageItemList.Num(); ++i)
356 {
357 UPageScrollItem* item = pageItemList[i];
358 if (!item)
359 continue;
360
361 // 페이지 아이템의 실제 위치 (0부터 오른쪽으로 배치됨)
362 float stride = GetPageStride();
363 FVector2D pagePos;
364 if (ScrollDirection == EPageScrollDirection::Horizontal)
365 pagePos = FVector2D(stride * i, 0.0f);
366 else
367 pagePos = FVector2D(0.0f, stride * i);
368
369 float pageCenter = (ScrollDirection == EPageScrollDirection::Horizontal)
370 ? (containerPos.X + pagePos.X + PageSize.X * 0.5f)
371 : (containerPos.Y + pagePos.Y + PageSize.Y * 0.5f);
372
373 float distance = FMath::Abs(pageCenter - viewportCenter);
374 float maxDistance = GetPageStride();
375 float normalizedDistance = FMath::Clamp(distance / maxDistance, 0.0f, 1.0f);
376
377 // Lerp from focus scale to side scale
378 float scale = FMath::Lerp(FocusScale, SideScale, normalizedDistance);
379
380 item->SetRenderScale(FVector2D(scale, scale));
381 item->UpdateFocusState(normalizedDistance < 0.1f, scale);
382 }
383}
384
386{
387 if (!bIsDragging)
388 return;
389
390 UCanvasPanelSlot* containerSlot = Cast<UCanvasPanelSlot>(PageContainer->Slot);
391 if (!containerSlot)
392 return;
393
394 // 드래그 델타 계산
395 FVector2D dragDelta = dragCurrentPosition - dragStartPosition;
396
397 // 이전 위치에서 드래그 델타만큼 이동
398 FVector2D basePosition = GetPagePosition(PrevPageIndex);
399 FVector2D newPosition = basePosition;
400
401 if (ScrollDirection == EPageScrollDirection::Horizontal)
402 newPosition.X += dragDelta.X;
403 else
404 newPosition.Y += dragDelta.Y;
405
406 containerSlot->SetPosition(newPosition);
407
408 // 스케일 효과 업데이트
410 {
412 }
413}
페이지 스크롤 뷰의 개별 페이지 아이템 UPageScrollView에 의해 동적으로 생성되며, 개별 페이지의 콘텐츠를 담는 컨테이너입니다.
void UpdateFocusState(bool bInFocused, float InScale)
포커스 상태를 업데이트합니다.
virtual void InitData(int32 InIndex, class UPageScrollView *InOwner)
페이지 아이템을 초기화합니다.
void MovePage(int32 PageIndex, bool bAnimate=true)
특정 페이지로 이동합니다.
void UpdateItemScales()
페이지 아이템들의 스케일을 거리 기반으로 업데이트합니다.
FVector2D PageSize
각 페이지의 크기 (Width × Height)
FVector2D animTargetPosition
void SetContainerPosition(FVector2D Position)
컨테이너 위치를 설정합니다.
void StopAnimation()
스냅 애니메이션을 중지합니다.
TObjectPtr< class UPageScrollDot > PageDotIndicator
페이지 인디케이터 (선택적)
virtual void NativeDestruct() override
int32 PrevPageIndex
이전 페이지 인덱스 (스냅 계산용)
float SideScale
중앙에서 벗어난 페이지의 스케일 값
int32 CurPageIndex
현재 선택된 페이지 인덱스
FVector2D dragCurrentPosition
float FocusScale
중앙에 있는 페이지의 스케일 값
FORCEINLINE float GetPageStride() const
페이지 간 거리를 계산합니다.
bool bIsDragging
드래그 상태 추적
void TickSnapAnimation()
스냅 애니메이션을 Tick합니다.
void UpdatePageDotIndicator()
페이지 인디케이터를 업데이트합니다.
void SetNumberOfPages(int32 ItemCount)
페이지 개수를 설정하고 동적으로 생성합니다.
FVector2D animStartPosition
TObjectPtr< class UCanvasPanel > PageContainer
페이지 아이템들을 담을 컨테이너 (드래그로 위치 이동)
virtual FReply NativeOnMouseMove(const FGeometry &InGeometry, const FPointerEvent &InMouseEvent) override
마우스 이동 이벤트
TSubclassOf< class UPageScrollItem > PageItemClass
페이지 아이템으로 생성할 위젯 블루프린트 클래스
FVector2D GetPagePosition(int32 PageIndex) const
페이지 인덱스에 해당하는 목표 위치를 계산합니다.
EPageScrollDirection ScrollDirection
스크롤 방향 (수평/수직)
void PrevPage()
이전 페이지로 이동합니다.
FOnPageChanged OnPageChanged
페이지 변경 이벤트
FVector2D dragStartPosition
void NextPage()
다음 페이지로 이동합니다.
FVector2D ViewportSize
뷰포트 크기 (SizeBox에서 계산)
FTimerHandle snapAnimTimerHandle
애니메이션 상태
virtual void NativeConstruct() override
virtual FReply NativeOnMouseButtonDown(const FGeometry &InGeometry, const FPointerEvent &InMouseEvent) override
마우스 버튼 다운 이벤트
void UpdateContainerPositionDuringDrag()
드래그 중 컨테이너 위치를 업데이트합니다.
float FastSwipeThreshold
빠른 스와이프 인식 임계값 (픽셀/초)
virtual FReply NativeOnMouseButtonUp(const FGeometry &InGeometry, const FPointerEvent &InMouseEvent) override
마우스 버튼 업 이벤트
float SnapAnimationSpeed
스냅 애니메이션 속도 (초 단위)
bool bEnableScaleEffect
중앙 스케일 효과 활성화 여부
int32 CalculateTargetPage(FVector2D DragDelta, float Velocity) const
드래그 종료 시 목표 페이지를 계산합니다.
int32 DefaultPageIndex
기본으로 선택될 페이지 인덱스
TArray< TObjectPtr< class UPageScrollItem > > pageItemList
생성된 페이지 아이템 목록