Skip to main content

自定义组件

例子#

最简单例子#

我们使用 Placeholder 来实现自定义组件:

export function App() {    const onPaintPost = useCallback<IPlaceholderComponentProps['onPaintPost']>(        (sender: IPlaceholder, painter: IPainter, rect: Rect) => {            painter.SetPenColor(new Vec4(255, 0, 0, 255));            painter.DrawRectangle(0, 0, rect.w, rect.h);        },        [],    );
    return (        <Window>            <DemoLayout width="500dpx" height="500dpx">                <Placeholder onPaintPost={onPaintPost} />            </DemoLayout>        </Window>    );}
export interface IDemoLayoutProps {    children?: any[] | any;    width?: string;    height?: string;}
export function DemoLayout(props: IDemoLayoutProps) {    const width = props?.width ?? '120dpx';    const height = props?.height ?? '32dpx';
    const demoLayout = {        columns: `1 ${width} 1`,        rows: `1 ${height} 1`,        areas: {            center: { row: 1, column: 1 },        },    };    return (        <Grid style={{ layout: demoLayout }}>            <Grid style={{ area: demoLayout.areas.center }}>                {props.children}            </Grid>        </Grid>    );}

比如,这样画一个红色矩形:

placeholder basic

自定义组件其实就是自己画 UI 并处理事件,画 UI 用的是 painter 相关的 API,painter 可以理解为类似 web 中 canvas 的东西。

API#

export interface IPlaceholderComponentProps extends IComponentProps {    onPaintPost?: Parameters<IControl["OnPaintPost"]>[0];    onPointerEnter?: Parameters<IControl["OnPointerEnter"]>[0];    onPointerLeave?: Parameters<IControl["OnPointerLeave"]>[0];    onPointerPress?: Parameters<IControl["OnPointerPress"]>[0];    onPointerRelease?: Parameters<IControl["OnPointerRelease"]>[0];    onPointerMove?: Parameters<IControl["OnPointerMove"]>[0];}
export interface IPainter {    SetPenColor(vColor: Vec4): void;    DrawRectangle(x: number, y: number, w: number, h: number): void;    ...}

自定义按钮#

这个例子演示如何监听事件,然后实现一个自定义按钮:

custom button

export function App() {    return (        <Window>            <DemoLayout>                <Button text="Primary Button" />            </DemoLayout>        </Window>    );}
export interface IDemoLayoutProps {    ...}
export function DemoLayout(props: IDemoLayoutProps) {    ...}
interface IButtonStyle {    border: {        radius: number;    };    color: Vec4;    backgroundColor: Vec4;    font: {        size: number;        family: string[];    };}
interface IButtonProps {    text: string;    style?: IButtonStyle;}
const colors = {    normal: new Vec4(24, 144, 255, 255),    hover: new Vec4(64, 169, 255, 255),    active: new Vec4(9, 109, 217, 255),    white: new Vec4(255, 255, 255, 255),};
const defaultButtonStyle = {    border: {        radius: 3,    },    color: colors.white,    backgroundColor: colors.normal,    font: {        size: 9,        family: ["Segoe UI", "Microsoft YaHei UI", "Meiryo UI", "SimSun-ExtB"],    },};
function Button(props: IButtonProps) {    const [style, setStyle] = useState(props.style ?? defaultButtonStyle);    const text = props.text ?? "Button";
    const [isEntered, setIsEntered] = useState(false);    const [isPressed, setIsPressed] = useState(false);
    const onPaintPost = useCallback<IPlaceholderComponentProps["onPaintPost"]>(        (sender: IPlaceholder, painter: IPainter, rect: Rect) => {            const { border, color, backgroundColor, font: fontStyle } = style;
            const fontDesc = new FontDescription();            fontDesc.Name = fontStyle.family;            fontDesc.Size = fontStyle.size;
            const context = getAppContext();            const window = context.getWindow();            const font = new Byo2Font(window, fontDesc);
            painter.SetFillColor(backgroundColor);            painter.FillRoundedRectangle(rect.x, rect.y, rect.w, rect.h, border.radius, border.radius);            painter.SetTextColor(color);            painter.DrawString(font, rect, text, DrawTextFlag.Center | DrawTextFlag.VCenter, text.length);        },        [style, text]    );
    const onPointerEnter = useCallback<IPlaceholderComponentProps["onPointerEnter"]>((sender: IPlaceholder) => {        setIsEntered(true);        setStyle({            ...style,            backgroundColor: colors.hover,        });        sender.Redraw();    }, []);
    const onPointerLeave = useCallback<IPlaceholderComponentProps["onPointerLeave"]>(        (sender: IPlaceholder) => {            setIsEntered(false);            if (!isPressed) {                setStyle({                    ...style,                    backgroundColor: colors.normal,                });            }            sender.Redraw();        },        [isPressed]    );
    const onPointerPress = useCallback<IPlaceholderComponentProps["onPointerPress"]>((sender: IPlaceholder) => {        setIsPressed(true);        setStyle({            ...style,            backgroundColor: colors.active,        });        sender.Redraw();    }, []);
    const onPointerRelease = useCallback<IPlaceholderComponentProps["onPointerRelease"]>((sender: IPlaceholder) => {        setIsPressed(false);        setStyle({            ...style,            backgroundColor: colors.normal,        });        sender.Redraw();    }, []);
    return <Placeholder onPaintPost={onPaintPost} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave} onPointerPress={onPointerPress} onPointerRelease={onPointerRelease} />;}

画 UI#

一个按钮其实就是一个矩形 + 上面的字:

const onPaintPost = useCallback<IPlaceholderComponentProps['onPaintPost']>(    (sender: IPlaceholder, painter: IPainter, rect: Rect) => {        const { border, color, backgroundColor, font: fontStyle } = style;
        const fontDesc = new FontDescription();        fontDesc.Name = fontStyle.family;        fontDesc.Size = fontStyle.size;
        const context = getAppContext();        const window = context.getWindow();        const font = new Byo2Font(window, fontDesc);
        painter.SetFillColor(backgroundColor);        painter.FillRoundedRectangle(            rect.x,            rect.y,            rect.w,            rect.h,            border.radius,            border.radius,        );        painter.SetTextColor(color);        painter.DrawString(            font,            rect,            text,            DrawTextFlag.Center | DrawTextFlag.VCenter,            text.length,        );    },    [style, text],);

事件#

例子中处理了 4 种事件:Enter & Leave, Press & Release:

return (    <Placeholder        onPaintPost={onPaintPost}        onPointerEnter={onPointerEnter}        onPointerLeave={onPointerLeave}        onPointerPress={onPointerPress}        onPointerRelease={onPointerRelease}    />);

样式#

样式是完全可定制的:

interface IButtonStyle {    border: {        radius: number;    };    color: Vec4;    backgroundColor: Vec4;    font: {        size: number;        family: string[];    };}

API#

export interface IControl extends IControlExtension {    OnPointerEnter(        fn: (sender: IControl, mp: MessagePointer) => void,    ): IControl;    OnPointerLeave(        fn: (sender: IControl, mp: MessagePointer) => void,    ): IControl;    OnPointerPress(        fn: (sender: IControl, mp: MessagePointer) => void,    ): IControl;    OnPointerRelease(        fn: (sender: IControl, mp: MessagePointer) => void,    ): IControl;}
export interface IPainter {    SetFillColor(vColor: Vec4): void;    FillRoundedRectangle(        x: number,        y: number,        w: number,        h: number,        rx: number,        ry: number,    ): void;
    SetTextColor(vColor: Vec4): void;    DrawString(        pFont: Byo2Font,        rc: Rect,        s: string,        nFlag: DrawTextFlag,        nCharCount: number,    ): Rect;}