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 }; } }