Files
esengine/packages/ui/src/components/widgets/UIScrollViewComponent.ts

371 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Component, ECSComponent, Property, Serializable, Serialize } from '@esengine/ecs-framework';
/**
* 滚动条可见性
* Scrollbar visibility mode
*/
export enum UIScrollbarVisibility {
/** 总是显示 Always visible */
Always = 'always',
/** 自动显示内容超出时Auto show when content exceeds */
Auto = 'auto',
/** 总是隐藏 Always hidden */
Hidden = 'hidden'
}
/**
* UI 滚动视图组件
* UI ScrollView Component - Scrollable container
*/
@ECSComponent('UIScrollView')
@Serializable({ version: 1, typeId: 'UIScrollView' })
export class UIScrollViewComponent extends Component {
// ===== 滚动位置 Scroll Position =====
/**
* 水平滚动位置
* Horizontal scroll position
*/
public scrollX: number = 0;
/**
* 垂直滚动位置
* Vertical scroll position
*/
public scrollY: number = 0;
/**
* 目标水平滚动位置(动画用)
* Target horizontal scroll position (for animation)
*/
public targetScrollX: number = 0;
/**
* 目标垂直滚动位置(动画用)
* Target vertical scroll position (for animation)
*/
public targetScrollY: number = 0;
// ===== 内容尺寸 Content Size =====
/**
* 内容宽度
* Content width
*/
public contentWidth: number = 0;
/**
* 内容高度
* Content height
*/
public contentHeight: number = 0;
// ===== 滚动配置 Scroll Configuration =====
/**
* 是否启用水平滚动
* Whether horizontal scroll is enabled
*/
@Serialize()
@Property({ type: 'boolean', label: 'Horizontal Scroll' })
public horizontalScroll: boolean = false;
/**
* 是否启用垂直滚动
* Whether vertical scroll is enabled
*/
@Serialize()
@Property({ type: 'boolean', label: 'Vertical Scroll' })
public verticalScroll: boolean = true;
/**
* 滚动条可见性
* Scrollbar visibility mode
*/
@Serialize()
@Property({
type: 'enum',
label: 'Scrollbar Visibility',
options: [
{ value: 'always', label: 'Always' },
{ value: 'auto', label: 'Auto' },
{ value: 'hidden', label: 'Hidden' }
]
})
public scrollbarVisibility: UIScrollbarVisibility = UIScrollbarVisibility.Auto;
/**
* 是否启用惯性滚动
* Whether inertia scrolling is enabled
*/
@Serialize()
@Property({ type: 'boolean', label: 'Inertia' })
public inertia: boolean = true;
/**
* 惯性减速率
* Inertia deceleration rate
*/
@Serialize()
@Property({ type: 'number', label: 'Deceleration Rate', min: 0, max: 1, step: 0.001 })
public decelerationRate: number = 0.135;
/**
* 是否启用弹性边界
* Whether elastic bounds are enabled
*/
@Serialize()
@Property({ type: 'boolean', label: 'Elastic Bounds' })
public elasticBounds: boolean = true;
/**
* 弹性系数
* Elasticity coefficient
*/
@Serialize()
@Property({ type: 'number', label: 'Elasticity', min: 0, max: 1, step: 0.01 })
public elasticity: number = 0.1;
// ===== 滚动条样式 Scrollbar Style =====
/**
* 滚动条宽度
* Scrollbar width
*/
@Serialize()
@Property({ type: 'number', label: 'Scrollbar Width', min: 1 })
public scrollbarWidth: number = 8;
/**
* 滚动条颜色
* Scrollbar color
*/
@Serialize()
@Property({ type: 'color', label: 'Scrollbar Color' })
public scrollbarColor: number = 0x888888;
/**
* 滚动条透明度
* Scrollbar alpha
*/
@Serialize()
@Property({ type: 'number', label: 'Scrollbar Alpha', min: 0, max: 1, step: 0.01 })
public scrollbarAlpha: number = 0.5;
/**
* 滚动条悬停透明度
* Scrollbar hover alpha
*/
@Serialize()
@Property({ type: 'number', label: 'Scrollbar Hover Alpha', min: 0, max: 1, step: 0.01 })
public scrollbarHoverAlpha: number = 0.8;
/**
* 滚动条圆角
* Scrollbar corner radius
*/
@Serialize()
@Property({ type: 'number', label: 'Scrollbar Radius', min: 0 })
public scrollbarRadius: number = 4;
/**
* 滚动条轨道颜色
* Scrollbar track color
*/
@Serialize()
@Property({ type: 'color', label: 'Scrollbar Track Color' })
public scrollbarTrackColor: number = 0x333333;
/**
* 滚动条轨道透明度
* Scrollbar track alpha
*/
@Serialize()
@Property({ type: 'number', label: 'Scrollbar Track Alpha', min: 0, max: 1, step: 0.01 })
public scrollbarTrackAlpha: number = 0.3;
// ===== 交互状态 Interaction State =====
/**
* 是否正在拖拽滚动
* Whether currently dragging to scroll
*/
public dragging: boolean = false;
/**
* 拖拽起始滚动位置 X
* Drag start scroll X
*/
public dragStartScrollX: number = 0;
/**
* 拖拽起始滚动位置 Y
* Drag start scroll Y
*/
public dragStartScrollY: number = 0;
/**
* 滚动速度 X用于惯性
* Scroll velocity X (for inertia)
*/
public velocityX: number = 0;
/**
* 滚动速度 Y用于惯性
* Scroll velocity Y (for inertia)
*/
public velocityY: number = 0;
/**
* 水平滚动条是否被悬停
* Whether horizontal scrollbar is hovered
*/
public horizontalScrollbarHovered: boolean = false;
/**
* 垂直滚动条是否被悬停
* Whether vertical scrollbar is hovered
*/
public verticalScrollbarHovered: boolean = false;
/**
* 是否正在拖拽滚动条
* Whether dragging scrollbar
*/
public draggingScrollbar: boolean = false;
// ===== 滚轮配置 Wheel Configuration =====
/**
* 滚轮滚动速度
* Mouse wheel scroll speed
*/
@Serialize()
@Property({ type: 'number', label: 'Wheel Speed', min: 1 })
public wheelSpeed: number = 40;
/**
* 是否平滑滚动
* Whether to use smooth scrolling
*/
@Serialize()
@Property({ type: 'boolean', label: 'Smooth Scroll' })
public smoothScroll: boolean = true;
/**
* 平滑滚动时长(秒)
* Smooth scroll duration in seconds
*/
@Serialize()
@Property({ type: 'number', label: 'Smooth Scroll Duration', min: 0, step: 0.01 })
public smoothScrollDuration: number = 0.2;
/**
* 获取最大水平滚动位置
* Get maximum horizontal scroll position
*/
public getMaxScrollX(viewportWidth: number): number {
return Math.max(0, this.contentWidth - viewportWidth);
}
/**
* 获取最大垂直滚动位置
* Get maximum vertical scroll position
*/
public getMaxScrollY(viewportHeight: number): number {
return Math.max(0, this.contentHeight - viewportHeight);
}
/**
* 设置滚动位置
* Set scroll position
*/
public setScroll(x: number, y: number, animate: boolean = true): this {
this.targetScrollX = x;
this.targetScrollY = y;
if (!animate) {
this.scrollX = x;
this.scrollY = y;
}
return this;
}
/**
* 滚动到顶部
* Scroll to top
*/
public scrollToTop(animate: boolean = true): this {
return this.setScroll(this.scrollX, 0, animate);
}
/**
* 滚动到底部
* Scroll to bottom
*/
public scrollToBottom(viewportHeight: number, animate: boolean = true): this {
return this.setScroll(this.scrollX, this.getMaxScrollY(viewportHeight), animate);
}
/**
* 滚动到指定位置(百分比)
* Scroll to position by percentage
*/
public scrollToPercent(percentX: number, percentY: number, viewportWidth: number, viewportHeight: number, animate: boolean = true): this {
const x = this.getMaxScrollX(viewportWidth) * Math.max(0, Math.min(1, percentX));
const y = this.getMaxScrollY(viewportHeight) * Math.max(0, Math.min(1, percentY));
return this.setScroll(x, y, animate);
}
/**
* 是否需要显示水平滚动条
* Whether horizontal scrollbar should be visible
*/
public needsHorizontalScrollbar(viewportWidth: number): boolean {
if (!this.horizontalScroll) return false;
if (this.scrollbarVisibility === UIScrollbarVisibility.Hidden) return false;
if (this.scrollbarVisibility === UIScrollbarVisibility.Always) return true;
return this.contentWidth > viewportWidth;
}
/**
* 是否需要显示垂直滚动条
* Whether vertical scrollbar should be visible
*/
public needsVerticalScrollbar(viewportHeight: number): boolean {
if (!this.verticalScroll) return false;
if (this.scrollbarVisibility === UIScrollbarVisibility.Hidden) return false;
if (this.scrollbarVisibility === UIScrollbarVisibility.Always) return true;
return this.contentHeight > viewportHeight;
}
/**
* 获取垂直滚动条手柄尺寸和位置
* Get vertical scrollbar handle size and position
*/
public getVerticalScrollbarMetrics(viewportHeight: number): { size: number; position: number } {
const maxScroll = this.getMaxScrollY(viewportHeight);
if (maxScroll <= 0) return { size: viewportHeight, position: 0 };
const size = Math.max(20, (viewportHeight / this.contentHeight) * viewportHeight);
const availableTrack = viewportHeight - size;
const position = (this.scrollY / maxScroll) * availableTrack;
return { size, position };
}
/**
* 获取水平滚动条手柄尺寸和位置
* Get horizontal scrollbar handle size and position
*/
public getHorizontalScrollbarMetrics(viewportWidth: number): { size: number; position: number } {
const maxScroll = this.getMaxScrollX(viewportWidth);
if (maxScroll <= 0) return { size: viewportWidth, position: 0 };
const size = Math.max(20, (viewportWidth / this.contentWidth) * viewportWidth);
const availableTrack = viewportWidth - size;
const position = (this.scrollX / maxScroll) * availableTrack;
return { size, position };
}
}