在使用这个库前你需要学习SolidJS,虽然写起来跟 react 很像,但是功能方面是有很大不同的。
目前可正常使用,可参考solid-panorama-example。
针对 PUI 的 API 进行了优化,比如createElement加入属性和父元素两个参数,就极大减少了过多的 API 调用,也解决了无法调用$.CreatePanelWithProperties的问题,加入了许多便利功能,兼容了 react-panorama 的部分代码。
yarn add solid-js \
solid-panorama-runtime \
babel-plugin-jsx-panorama-expressions \
babel-preset-solid-panorama
babel.config.js
module.exports = {
targets: 'node 18.12',
presets: [
'@babel/preset-env',
'@babel/preset-typescript',
[
'babel-preset-solid-panorama',
{
moduleName: 'solid-panorama-runtime',
generate: 'universal'
}
]
],
plugins: ['@babel/plugin-transform-typescript']
};app.tsx
import { onMount } from 'solid-js';
import { render } from 'solid-panorama-runtime';
function HelloWorld() {
let root: Panel | undefined;
onMount(() => {
$.Msg(root);
});
return <Panel ref={root}>Hello World!</Panel>;
}
render(() => <HelloWorld />, $('#app'));Thanks to ark120202 for creating react-panorama, some of the code for this project was copied from react-panorama and adapted.
- 如果你想使用
console.log或者setTimeout等 Web 函数,可以参考solid-panorama-polyfill,或者使用panorama-polyfill - 如果你想把 xml 或 css 放在 jsx/tsx 里面,可以参考solid-panorama-all-in-jsx。
- 如果你想用
useGameEvent和useNetTable,可以参考solid-panorama-all-in-jsx。
对 style 进行了兼容,如果 style 是字符串,在 PUI 里 style 末尾不写分号会弹出错误,所以在编译时会解析自动加上分号。
当 style 是 Object 时,某些属性可以赋值数字,会自动转换成 px,支持列表可查看:packages/runtime/src/config.ts
class和className两个属性都是支持的,由于 solid.js 提供classList属性可按true | false动态添加,所以也提供类似的功能,三个属性可同时存在。
<Button
class={current() === 'foo' ? 'selected' : ''}
onClick={() => setCurrent('foo')}
>
foo
</Button>
<Button
class="my-button"
classList={{selected: current() === 'foo'}}
onClick={() => setCurrent('foo')}
>foo</Button>PUI 的元素事件与 WEB 的完全不同,PUI 是较为简单的,而且绝大多数情况下也不需要向上冒泡,所以不会支持事件冒泡的功能。
对事件进行了优化,事件的回调函数第一个参数是元素本身。
在 HTML 中<div> Hi </div>这类情况下Hi会渲染成文本节点,也就是 textNode,
文本节点会自动创建 Label,并且默认启用 html 渲染,如果包含 HTML 标签,需要用字符串。
需要注意的是如果文本是以#开头的文本,比如#addon_game_name,此类会自动调用$.Localize,但是不能参杂其它文本。
例如,以下是正确的写法:
// 纯文本
<Panel>
Welcome My Game
</Panel>
// 带HTML标签
<Panel>
{`<strong>Welcome</strong> My Game`}
</Panel>
// 拼接本地化字段
<Panel>
<Label text="Welcome" />
#addon_game_name
<Label text="(~ ̄▽ ̄)~" />
</Panel>类型:string
自动载入 snippet
<Panel snippet="MyBtton" />类型:Record<string, string | number | Date>
两者是一样的,dialogVariables 是为了兼容ark120202/react-panorama
- 当值为
string时,调用SetDialogVariable,如果以#开头则调用SetDialogVariableLocString - 当值为
number时,调用SetDialogVariableInt - 当值为
Date时,调用SetDialogVariableTime
针对 Label 做了一些调整,vars 和 dialogVariables 会先写入,然后再写入Label.text, 如果 text 以#开头会调用$.Localize(text, Label)。
如果 text 是动态改变的,则应当将 vars 和 dialogVariables 放在 text 前面。
<Label vars={{ name: '#addon_game_name' }} text="Welcome {d:name}" />类型:Record<string, string | number>
- 当值为
string时,调用SetAttributeString - 当值为
number时,调用SetAttributeInt
<Panel attrs={{ name: 'my name' }} />支持data-*属性,注意这里跟 HTML 的不一样,这里是将这些属性存储在Panel.Data()中,所以可以很方便的存储 JS 的数据对象,比如data-list={['name']},那么可以通过Panel.Data()['list'][0]获得该值。
<Panel data-my-data={{ name: 'my name' }} />类型:string
自动设置onmouseover="DOTAShowTextTooltip(<token>)"和onmouseout="DOTAHideTextTooltip()"
注意:不能与 onmouseover 和 onmouseout 事件同时存在
<Panel tooltip_text="#addon_game_name" />类型:[string, string]
对应[<tooltip name>, <xml file path>]
自动设置onmouseover="UIShowCustomLayoutParametersTooltip()"和onmouseout="UIHideCustomLayoutTooltip()"
注意:不能与 onmouseover 和 onmouseout 事件同时存在
<Panel custom_tooltip={['Item', 'file://{resources}/layout/custom_game/tooltip_example.xml']} custom_tooltip_params={{ name: 'item_xxx' }} />
// OR
<Panel custom_tooltip={['Item', 'tooltip_example']} custom_tooltip_params={{ name: 'item_xxx' }} />类型:Record<string, string | number>
onDragStart?: (source: Panel, dragCallbacks: IDragCallbacks) => void;
onDragEnd?: (source: Panel, draggedPanel: Panel) => void;
onDragEnter?: (source: Panel, draggedPanel: Panel) => void;
onDragDrop?: (source: Panel, draggedPanel: Panel) => void;
onDragLeave?: (source: Panel, draggedPanel: Panel) => void;如果设置了onDragStart,会自动调用SetDraggable(true),所以可以不用draggable属性。
function onItemDragStart(source: Panel, dragCallbacks: IDragCallbacks) {
// ...
}
<Panel onDragStart={onItemDragStart} />;- 如何正确使用 children,可参考https://www.solidjs.com/docs/latest/api#children
import { children, splitProps } from 'solid-js';
interface MyButtonProps {
children?: JSX.Element;
}
function MyButton(props: MyButtonProps) {
const [local, others] = splitProps(props, ['children']);
// 注意,如果你的 children 是固定的布局,不是变化的,则应该直接使用`{local.children}`
const resolved = children(() => local.children);
createEffect(() => {
const list = resolved.toArray();
for (const [index, child] of list.entries()) {
(child as Panel).SetHasClass(
'LastChild',
index === list.length - 1
);
}
});
return (
<Button className={className || 'ButtonBevel'} {...others}>
{resolved()}
</Button>
);
}- 函数组件的参数尽量不要使用 Object 展开语法(Spread syntax),如果需要分割 props,应当用
splitProps,主要是这种语法会导致无法更新属性。
import { splitProps } from 'solid-js';
// ✅ 推荐
function MyButton(props: MyButtonProps) {
const [local, others] = splitProps(props, ['class', 'children']);
return (
<Button class={local.class + ' MyButtonStyle'} {...others}>
<Label text={local.class} />
</Button>
);
}
// 😞 这是不推荐的,即使没有分割出属性也一样会导致属性无法更新
function MyButton({ ...props }: MyButtonProps);- UI的焦点问题
PUI在默认情况如果点击任意元素之后会导致获得聚焦,从而导致快捷键等失效,一般情况下都不需要将焦点聚焦在UI的元素上,所以任何元素在创建时都会调用SetDisableFocusOnMouseDown(true)。