press-pix
Version:
基于 PixUI 的 Press 组件库
276 lines (245 loc) • 7.13 kB
text/typescript
import { AegisReportInPixui } from 'pixui-runtime';
import { safeJsonParse } from 't-comm/es/json/json-parse';
import { GameletAPI } from 'gamelet-pixui-frame';
import { checkI18n } from '../i18n/check';
/**
* 最小安全边距(单位:px)
* 当从客户端获取的边距小于此值时,使用此值作为最小保证
*/
const ORIGIN_PADDING = 44;
/**
* px 转 rem 的转换比例
* 计算公式:rem = px * PX_TO_REM_RATIO
*/
const PX_TO_REM_RATIO = 2 / 100;
const originData = {
left: 0,
top: 0,
right: 0,
bottom: 0,
};
const safeDistance = {
data: originData,
isReady: false, // 标记数据是否已经获取完成
readyPromise: null as Promise<void> | null, // 用于等待数据准备完成
resolveReady: null as (() => void) | null, // resolve 函数
listeners: [] as Array<() => void>, // 数据变化监听器列表
get() {
return this.data;
},
set(data) {
this.data = data;
this.isReady = true;
if (this.resolveReady) {
this.resolveReady();
}
// 通知所有监听器数据已更新
this.listeners.forEach(listener => listener());
},
// 订阅数据变化
subscribe(listener: () => void) {
this.listeners.push(listener);
// 返回取消订阅函数
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
},
};
export async function tryGetSafeAreaInfo() {
// 如果已经初始化过,直接返回
if (safeDistance.isReady) {
return;
}
// 创建一个 Promise 用于等待数据返回
if (!safeDistance.readyPromise) {
safeDistance.readyPromise = new Promise((resolve) => {
safeDistance.resolveReady = resolve;
});
}
GameletAPI.addOnGameCommandListener((msg) => {
setTimeout(() => {
AegisReportInPixui.report({
msg: 'GAME_COMMAND_0',
params: {
msg,
},
});
}, 3000);
const ret = safeJsonParse(msg);
setTimeout(() => {
AegisReportInPixui.report({
msg: 'GAME_COMMAND_1',
params: {
msg,
ret,
},
});
}, 3000);
if (ret && ret.type === 'GetMarginRtn') {
const margin = (ret.content || '').split(',').map(val => parseInt(val, 10));
setTimeout(() => {
AegisReportInPixui.report({
msg: 'GAME_COMMAND_SAFE_AREA_INFO',
params: {
msg,
ret,
margin,
},
});
}, 3000);
safeDistance.set({
left: margin[0],
top: margin[1],
right: margin[2],
bottom: margin[3],
});
}
});
GameletAPI.callGame(JSON.stringify({
type: 'GetMargin',
appid: GameletAPI.getAppID(),
appId: GameletAPI.getAppID(),
appName: GameletAPI.getAppName(),
}));
// 等待数据返回,最多等待 3 秒
await Promise.race([
safeDistance.readyPromise,
new Promise(resolve => setTimeout(resolve, 3000)),
]);
}
// 模块加载时自动初始化 Safe Area 数据
// 这样所有页面导入时,数据获取就已经在后台进行了
tryGetSafeAreaInfo().catch((err) => {
console.error('Failed to initialize Safe Area:', err);
});
/**
* 订阅 Safe Area 数据变化
* 当数据更新时会调用回调函数
*
* @param callback - 数据变化时的回调函数
* @returns 取消订阅的函数
*
* @example
* const unsubscribe = subscribeSafeAreaChange(() => {
* console.log('Safe Area data updated');
* });
* // 取消订阅
* unsubscribe();
*/
export function subscribeSafeAreaChange(callback: () => void) {
return safeDistance.subscribe(callback);
}
/**
* 检查 Safe Area 数据是否已准备好
*
* @returns 数据是否已准备好
*/
export function isSafeAreaReady() {
return safeDistance.isReady;
}
/**
* px 转 rem
* @param px - 像素值
* @returns rem 字符串(如 "1rem")
*/
function pxToRem(px: number): string {
return `${(px * PX_TO_REM_RATIO).toFixed(2)}rem`;
}
/**
* 计算实际的 padding 值(确保不小于最小安全边距)
* @param left - 左边距原始值
* @param right - 右边距原始值
* @returns 计算后的左右 padding 值
*/
function calculatePadding(left: number, right: number) {
return {
paddingLeft: Math.max(left, ORIGIN_PADDING),
paddingRight: Math.max(right, ORIGIN_PADDING),
};
}
/**
* 检查当前语言是否为 RTL(从右到左)
*/
export function isRTLLanguage(): boolean {
const { lang } = checkI18n();
return lang === 'ur' || lang === 'ar';
}
/**
* 获取 safe area 左右 padding 样式,返回可直接用于 style 属性的对象
* import { getSafeAreaStyle } from 'src/local-logic/utils/safe-area';
*
* @param options - 可选配置
* @param options.left - 是否包含 paddingLeft,默认 true
* @param options.right - 是否包含 paddingRight,默认 true
* @param options.position - 定位方式,'left' 或 'right'
*
* @example
* // 直接用于 style 属性(左右都设置)
* <div style={getSafeAreaStyle()}>...</div>
*
* // 只设置左边距
* <div style={getSafeAreaStyle({ right: false })}>...</div>
*
* // 只设置右边距
* <div style={getSafeAreaStyle({ left: false })}>...</div>
*
* // 设置定位 left
* <div style={getSafeAreaStyle({ position: 'left' })}>...</div>
*
* // 设置定位 right
* <div style={getSafeAreaStyle({ position: 'right' })}>...</div>
*
* // 或者与其他样式合并
* <div style={{ ...getSafeAreaStyle(), backgroundColor: 'red' }}>...</div>
*/
export function getSafeAreaStyle(options?: { left?: boolean; right?: boolean; position?: 'left' | 'right' }) {
const { left, right } = safeDistance.data;
const { paddingLeft, paddingRight } = calculatePadding(left, right);
let result: Record<string, string> = {};
// 转换为 rem
const leftRem = pxToRem(paddingLeft);
const rightRem = pxToRem(paddingRight);
// 如果指定了 position,只返回对应方位的定位值
if (options?.position === 'left') {
result = { left: leftRem };
} else if (options?.position === 'right') {
result = { right: rightRem };
} else {
// 没有指定 position,按照 left/right 选项设置 padding
const includeLeft = options?.left !== false;
const includeRight = options?.right !== false;
if (includeLeft) {
result = { ...result, paddingLeft: leftRem };
}
if (includeRight) {
result = { ...result, paddingRight: rightRem };
}
}
return result;
}
/**
* 获取 safe area 原始数据和处理后的 padding 值
* import { getSafeAreaInfo } from 'src/local-logic/utils/safe-area';
*
* @example
* const safeArea = getSafeAreaInfo();
* // 返回值包含:
* // { left, top, right, bottom, paddingLeft, paddingTop, paddingRight, paddingBottom }
*/
export function getSafeAreaInfo() {
const { left, top, right, bottom } = safeDistance.data;
const { paddingLeft, paddingRight } = calculatePadding(left, right);
return {
left,
top,
right,
bottom,
paddingLeft,
paddingTop: top,
paddingRight,
paddingBottom: bottom,
};
}