before:
const image = new ImageWidget();
image.setTintColor([1, 1, 1, 0.75]);
image.setUrl("http://somewhere.com/image.png");
const element = new UIElement();
element.widget = image;
refObject.addUI(element);
after:
const element = new UIElement();
element.widget = render(<image url={"http://somewhere.com/image.png"} color={[1, 1, 1, 0.75]} />);
refObject.addUI(element);
For now, you'll need to use Typescript, and I'll assume that you're familiar enough with the basics of typescript to know things like what your tsconfig.json file is. If someone wants to make something that'll work with esbuild or what not, I welcome it.
You can also use yarn create ttpg-package --template tsx
to get a new workspace with jsx-int-tpg set up already.
Add the "jsx" and "jsxFactory" fields to compilerOptions, and be sure to include "tsx" files to your tsc config. Files that use JSX need to have the file extension of "tsx".
{
"compilerOptions" {
"jsx": "react",
"jsxFactory": "jsxInTTPG",
"jsxFragmentFactory": "jsxFrag",
/* ... other compilerOptions ... */
},
"include": ["./src/**/*.tsx", /* other includes */]
},
additionally, you'll need to add this import at the top of any file that uses JSX
import { jsxInTTPG } from "jsx-in-ttpg";
use the provided render
function to ensure a JSX tag is a Widget. The top-level JSX tag must be a vanilla widget (or a string or array string, since jsxInTTPG will wrap a lone string in a <text>
widget automagically). This could also be a custom component, so long as any nested components resolve to a structure with a top-level widget.
import { render, jsxInTTPG } from "jsx-in-ttpg";
const ui = new UIElement();
/* do stuff to set up ui element */
ui.widget = render(<text>Hello World</text>);
refObject.addUI(ui);
in instances where you need to set a new widget, or add a child, or if you want to mix-n-match JSX with vanilla TTPG elements, you can make use of the render()
function in the same way:
const layoutBox = new LayoutBox();
layoutBox.setChild(render(<text>some text</text>));
a custom component should always return a single JSXNode (or a primitive, null, etc, etc). If it turns out that you need to return multiple elements, wrap the returned elements in a fragment <>{...}</>
. You will need to import {jsxFrag} from 'jsx-in-ttpg'
to do so
import { render, jsxInTTPG, jsxFrag } from "jsx-in-ttpg";
const MyComponent = () => {
return (
<>
<horizontalbox>
<text>Hello</text>
<text>World</text>
</horizontalbox>
<horizontalbox>
<richtext>Some fancy [b]bolded[/b] text</richtext>
</horizontalbox>
</>
);
};
const ui = new UIElement();
ui.widget = render(
<layout>
<verticalbox>
<MyComponent />
</verticalbox>
</layout>
);
refObject.addUI(ui);
Every Tabletop Playground widget is resprestented in a JSX intrinsic element with certain attributes that function as function calls or property setters for that widget.
- Canvas:
<canvas>
- ImageWidget:
<image>
- ImageButton:
<imagebutton>
- ContentButton:
<contentbutton>
- Border:
<border>
- HorizontalBox:
<horizontalbox>
- VerticalBox:
<verticalbox>
- LayoutBox:
<layout>
- Text:
<text>
- Button:
<button>
- CheckBox:
<checkbox>
- MultilineTextBox:
<textarea>
- ProgressBar:
<progressbar>
- RichText:
<richtext>
- SelectionBox:
<select>
- Slider:
<slider>
- TextBox:
<input>
several function calls, event handlers, or properties of TTPG widgets are available as attributes on the JSX element.
Note that boolean-type attributes don't require you to put the actual true/false value to have that value be "true" - for example <button disabled>Some Text</button>
is equivilent to <button disabled={true}>Some Text</button>
. (this is why certain properties are kind of the "inverse" of what you find in vanilla TTPG widget syntax, such as "disabled" vs "setEnabled" and "hidden" vs "setVisible")
disabled
-optional boolean
- equivilent to "setEnabled(!disabled)"hidden
-optional boolean
- equivilent to "setVisible(!hidden)";ref
-optional RefObject<T>
where T is the widget class being used - more on refs later.
<image onLoad={() => {}} color={[1, 1, 1, 1]} card={Card} url={"http://..."} src={"someimage.jpg"} package={"..."} />
-
src
-string
: Equivilent to callingimageWidget.setImage(src)
-
srcPackage
-string
: Equivilent to callingimageWidget.setImage(src, srcPackage)
- you must provide thesrc
attribute for thesrcPackage
attribute to have any effect. -
url
-string
: Equivilent to callingimagewidget.setUrl(...)
- mutually exclusive withsrc
andsrcPackage
as well ascard
. -
card
-Card
: Equivilent to callingimageWidget.setSourceCard(...)
- mutually exclusive withsrc
andsrcPackage
as well asurl
. -
onLoad
-(image: ImageWidget, filename: string, packageId: string) => void
: Equivilent to callingimagewidget.onImageLoaded.add(...)
. -
color
-[number, number, number, number]
orColor
: Equivilent to callingimagewidget.setTintColor(...)
. -
width
-number
: Equivilent to callingimagewidget.setWidth(...)
. -
height
-number
: Equivilent to callingimagewidget.setHeight(...)
. -
<image>
takes no children
<imagebutton onLoad={() => {}} color={[1, 1, 1, 1]} card={Card} url={"http://..."} src={"someimage.jpg"} package={"..."} onClick={() => {}} />
-
onClick
: Equivilent ofimageButton.onClicked.add(...)
-
src
-string
: Equivilent to callingimageWidget.setImage(src)
-
srcPackage
-string
: Equivilent to callingimageWidget.setImage(src, srcPackage)
- you must provide thesrc
attribute for thesrcPackage
attribute to have any effect. -
url
-string
: Equivilent to callingimagewidget.setUrl(...)
- mutually exclusive withsrc
andsrcPackage
as well ascard
. -
card
-Card
: Equivilent to callingimageWidget.setSourceCard(...)
- mutually exclusive withsrc
andsrcPackage
as well asurl
. -
onLoad
-(image: ImageWidget, filename: string, packageId: string) => void
: Equivilent to callingimagewidget.onImageLoaded.add(...)
. -
color
-[number, number, number, number]
orColor
: Equivilent to callingimagewidget.setTintColor(...)
. -
width
-number
: Equivilent to callingimagewidget.setWidth(...)
. -
height
-number
: Equivilent to callingimagewidget.setHeight(...)
. -
<imagebutton>
takes no children
<border ref={borderRef} color={[1, 0, 0, 1]}>
<text>"Hello, world!"</text>
</border>
-
color
-[number, number, number, number]
orColor
: Equivalent to callingborder.setColor(...)
. -
<border>
can take strings or a single widget as children.
The canvas has no additional attributes. however, canvases take an atypical child function. You can have more than one. This is so you can set the x, y, width, and height inline.
<canvas>
{canvasChild({ x: 0, y: 0, width: 100, height: 100 }, <button>SomeText</button>)}
{canvasChild({ x: 200, y: 0, width: 100, height: 100 }, <button>Some Other Text</button>)}
</canvas>
<canvas>
can take multiplecanvasChild
calls as children. the second argument of canvasChild can be a string or a single widget.
<layout halign={HorizontalAlignment.Center} valign={VerticalAlignment.Middle} padding={{ left: 10, right: 10, top: 5, bottom: 5 }}>
<image src="avatar.jpg" />
</layout>
-
valign
-VerticalAlignment
: Equivalent to callinglayoutBox.setVerticalAlignment(...)
. -
halign
-HorizontalAlignment
: Equivalent to callinglayoutBox.setHorizontalAlignment(...)
. -
padding
-number | { left?: number, right?: number, top?: number, bottom?: number }
: Equivalent to callinglayoutBox.setPadding(...)
if a single number is used, it is applied to all four directions. -
maxHeight
-number
: Equivalent to callinglayoutBox.setMaximumHeight(...)
. -
minHeight
-number
: Equivalent to callinglayoutBox.setMinimumHeight(...)
. -
height
-number
: Equivalent to callinglayoutBox.setOverrideHeight(...)
. -
maxWidth
-number
: Equivalent to callinglayoutBox.setMaximumWidth(...)
. -
minWidth
-number
: Equivalent to callinglayoutBox.setMinimumWidth(...)
. -
width
-number
: Equivalent to callinglayoutBox.setOverrideWidth(...)
. -
<layout>
can take strings or a single widget as children.
VerticalBox (as well as the HorizontalBox) can take a special function child, much like Canvas. Unlike Canvas, however, it's not required. the first parameter of boxChild is the "weight" and the second is the Widget (or string) that will be that child.
<verticalbox gap={10} halign={HorizontalAlignment.Left} valign={VerticalAlignment.Middle}>
<text>"Hello!"</text>
{boxChild(3, <button>Some Button</button>)}
<image src="image1.jpg" />
</verticalbox>
-
gap
-number
: Equivalent to callingverticalBox.setChildDistance(...)
. -
valign
-VerticalAlignment
: Equivalent to callingverticalBox.setVerticalAlignment(...)
. -
halign
-HorizontalAlignment
: Equivalent to callingverticalBox.setHorizontalAlignment(...)
. -
<verticalbox>
can take strings, any number of widgets, or the special {boxChild(1, <...>)} function.
<horizontalbox ref={hboxRef} disabled={false} hidden={false} gap={10} halign={HorizontalAlignment.Left} valign={VerticalAlignment.Middle}>
<image src="image1.jpg" />
<text>"Hello!"</text>
</horizontalbox>
-
gap
-number
: Equivalent to callingverticalBox.setChildDistance(...)
. -
valign
-VerticalAlignment
: Equivalent to callingverticalBox.setVerticalAlignment(...)
. -
halign
-HorizontalAlignment
: Equivalent to callingverticalBox.setHorizontalAlignment(...)
. -
<horizontalbox>
, like<verticalbox>
, can take strings, any number of widgets, or the special {boxChild(1, <...>)} function.
<contentbutton
ref={buttonRef}
disabled={false}
hidden={false}
onClick={(button, player) => {
/* Click handler */
}}
>
<image src="button_icon.png" />
</contentbutton>
-
onClick
-(button: ContentButton, player: Player) => void
: Equivalent to adding a click event handler usingonClicked.add(...)
. -
can take strings or a single widget as children.
<text ref={textRef} disabled={false} hidden={false} bold={true} italic={false} size={18} color={[1, 0, 0, 1]} wrap={true} justify={TextJustification.Center}>
Sample text content.
</text>
-
bold
-boolean
: Equivalent to callingtext.setBold(...)
. -
italic
-boolean
: Equivalent to callingtext.setItalic(...)
. -
size
-number
: Equivalent to callingtext.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingtext.setTextColor(...)
. -
wrap
-boolean
: Equivalent to callingtext.setAutoWrap(...)
. -
justify
-TextJustification
: Equivalent to callingtext.setJustification(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<text>
obviously takes a string as a child
<button
ref={buttonRef}
disabled={false}
hidden={false}
bold={true}
italic={false}
size={18}
color={[1, 0, 0, 1]}
onClick={(button, player) => {
/* Click handler */
}}
>
Click Me!
</button>
-
bold
-boolean
: Equivalent to callingbutton.setBold(...)
. -
italic
-boolean
: Equivalent to callingbutton.setItalic(...)
. -
size
-number
: Equivalent to callingbutton.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingbutton.setTextColor(...)
. -
onClick
-(button: Button, player: Player) => void
: Equivalent to adding a click event handler usingonClicked.add(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<button>
can take a string as a child
<checkbox
ref={checkboxRef}
disabled={false}
hidden={false}
bold={true}
italic={false}
size={18}
color={[1, 0, 0, 1]}
onChange={(checkbox, player, state) => {
/* Change handler */
}}
checked={true}
label="Check me!"
/>
-
bold
-boolean
: Equivalent to callingcheckbox.setBold(...)
. -
italic
-boolean
: Equivalent to callingcheckbox.setItalic(...)
. -
size
-number
: Equivalent to callingcheckbox.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingcheckbox.setTextColor(...)
. -
onChange
-(checkbox: CheckBox, player: Player, state: boolean) => void
: Equivalent to adding a change event handler usingonCheckStateChanged.add(...)
but will only trigger on user input, not on programmatic input. -
onChangeActual
-(checkbox: CheckBox, player: Player | undefined, state: boolean) => void
: Equivalent to adding a change event handler usingonCheckStateChanged.add(...)
. -
checked
-boolean
: Equivalent to callingcheckbox.setIsChecked(...)
. -
label
-string
orstring[]
: Equivalent to setting the checkbox label usingsetText(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<checkbox>
takes no children
<textarea
ref={textareaRef}
disabled={false}
hidden={false}
bold={true}
italic={false}
size={18}
color={[1, 0, 0, 1]}
onChange={(element, player, text) => {
/* Change handler */
}}
onCommit={(element, player, text) => {
/* Commit handler */
}}
maxLength={100}
transparent={false}
/>
-
bold
-boolean
: Equivalent to callingmultilineTextBox.setBold(...)
. -
italic
-boolean
: Equivalent to callingmultilineTextBox.setItalic(...)
. -
size
-number
: Equivalent to callingmultilineTextBox.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingmultilineTextBox.setTextColor(...)
. -
onChange
-(element: MultilineTextBox, player: Player, text: string) => void
: Equivalent to adding a change event handler usingonTextChanged.add(...)
but will only trigger on user interaction, not programatic interaction. -
onChangeActual
-(element: MultilineTextBox, player: Player | undefined, text: string) => void
: Equivalent to adding a change event handler usingonTextChanged.add(...)
. -
onCommit
-(element: MultilineTextBox, player: Player, text: string) => void
: Equivalent to adding a commit event handler usingonTextCommitted.add(...)
but will only trigger on user interaction, not programatic interaction. -
onCommitActual
-(element: MultilineTextBox, player: Player | undefined, text: string) => void
: Equivalent to adding a commit event handler usingonTextCommitted.add(...)
. -
maxLength
-number
: Equivalent to setting the maximum length of the text usingsetMaxLength(...)
. -
transparent
-boolean
: Equivalent to callingmultilineTextBox.setTransparent(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<textarea>
can take a string as a child
<progressbar ref={progressbarRef} disabled={false} hidden={false} bold={true} italic={false} wrap={true} size={18} color={[1, 0, 0, 1]} value={50} label="Loading..." />
-
bold
-boolean
: Equivalent to callingprogressBar.setBold(...)
. -
italic
-boolean
: Equivalent to callingprogressBar.setItalic(...)
. -
wrap
-boolean
: Equivalent to callingprogressBar.setWrap(...)
. -
size
-number
: Equivalent to callingprogressBar.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingprogressBar.setTextColor(...)
. -
value
-number
: Equivalent to callingprogressBar.setProgress(...)
. -
label
-string
orstring[]
: Equivalent to setting the progress bar label usingsetText(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<progressbar>
takes no children
<richtext ref={richtextRef} disabled={false} hidden={false} bold={true} italic={false} size={18} color={[1, 0, 0, 1]} wrap={true} justify={TextJustification.Center}>
This is a sample [b]rich[/b] text.
</richtext>
-
bold
-boolean
: Equivalent to callingrichText.setBold(...)
. -
italic
-boolean
: Equivalent to callingrichText.setItalic(...)
. -
size
-number
: Equivalent to callingrichText.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingrichText.setTextColor(...)
. -
wrap
-boolean
: Equivalent to callingrichText.setAutoWrap(...)
. -
justify
-TextJustification
: Equivalent to callingrichText.setJustification(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<richtext>
can take a string as it's child.
<select
ref={selectRef}
disabled={false}
hidden={false}
bold={true}
italic={false}
size={18}
color={[1, 0, 0, 1]}
onChange={(element, player, index, option) => {
/* Change handler */
}}
value="option2"
options={["option1", "option2", "option3"]}
/>
-
bold
-boolean
: Equivalent to callingselectionBox.setBold(...)
. -
italic
-boolean
: Equivalent to callingselectionBox.setItalic(...)
. -
size
-number
: Equivalent to callingselectionBox.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingselectionBox.setTextColor(...)
. -
onChange
-(element: SelectionBox, player: Player, index: number, option: string) => void
: Equivalent to adding a change event handler usingonSelectionChanged.add(...)
but will only trigger on user interaction, not programatic interaction. -
onChangeActual
-(element: SelectionBox, player: Player | undefined, index: number, option: string) => void
: Equivalent to adding a change event handler usingonSelectionChanged.add(...)
. -
value
-string
: Equivalent to setting the selected option usingsetSelectedOption(...)
. -
options
-string[]
: An array of available options for the selection box. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<select>
takes no children
<slider
ref={sliderRef}
disabled={false}
hidden={false}
bold={true}
italic={false}
size={18}
color={[1, 0, 0, 1]}
min={0}
value={50}
max={100}
step={1}
onChange={(element, player, value) => {
/* Change handler */
}}
inputWidth={50}
font="Arial"
fontPackage="main"
/>
-
bold
-boolean
: Equivalent to callingslider.setBold(...)
. -
italic
-boolean
: Equivalent to callingslider.setItalic(...)
. -
size
-number
: Equivalent to callingslider.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingslider.setTextColor(...)
. -
min
-number
: Equivalent to setting the minimum value usingsetMinValue(...)
. -
value
-number
: Equivalent to setting the current value usingsetValue(...)
. -
max
-number
: Equivalent to setting the maximum value usingsetMaxValue(...)
. -
step
-number
: Equivalent to setting the step size usingsetStepSize(...)
. -
onChange
-(element: Slider, player: Player, value: number) => void
: Equivalent to adding a change event handler usingonValueChanged.add(...)
but will only trigger on user interaction, not programatic interaction. -
onChangeActual
-(element: Slider, player: Player | undefined, value: number) => void
: Equivalent to adding a change event handler usingonValueChanged.add(...)
. -
inputWidth
-number
: Equivalent to setting the width of the input box usingsetTextBoxWidth(...)
. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<slider>
takes no children
<input
size={18}
color={[1, 0, 0, 1]}
onChange={(element, player, text) => {
/* Change handler */
}}
onCommit={(element, player, text, hardCommit) => {
/* Commit handler */
}}
maxLength={100}
transparent={false}
selectOnFocus={true}
value="Initial Value"
type="string"
font="Arial"
fontPackage="main"
/>
-
bold
-boolean
: Equivalent to callingtextBox.setBold(...)
. -
italic
-boolean
: Equivalent to callingtextBox.setItalic(...)
. -
size
-number
: Equivalent to callingtextBox.setFontSize(...)
. -
color
-[number, number, number, number]
orColor
: Equivalent to callingtextBox.setTextColor(...)
. -
onChange
-(element: TextBox, player: Player, text: string) => void
: Equivalent to adding a change event handler usingonTextChanged.add(...)
but will only trigger on user interaction, not programatic interaction. -
onChangeActual
-(element: TextBox, player: Player | undefined, text: string) => void
: Equivalent to adding a change event handler usingonTextChanged.add(...)
. -
onCommit
-(element: TextBox, player: Player, text: string, hardCommit: boolean) => void
: Equivalent to adding a commit event handler usingonTextCommitted.add(...)
but will only trigger on user interaction, not programatic interaction. -
onCommitActual
-(element: TextBox, player: Player | undefined, text: string, hardCommit: boolean) => void
: Equivalent to adding a commit event handler usingonTextCommitted.add(...)
. -
maxLength
-number
: Equivalent to setting the maximum length of the text usingsetMaxLength(...)
. -
transparent
-boolean
: Equivalent to callingtextBox.setBackgroundTransparent(...)
. -
selectOnFocus
-boolean
: Equivalent to callingtextBox.setSelectTextOnFocus(...)
. -
value
-string
: Equivalent to setting the initial value usingsetText(...)
. -
type
-"string" | "float" | "positive-float" | "integer" | "positive-integer"
: Equivalent to setting the input type usingsetInputType(...)
but with a string argument instead of numeric arguments. -
font
-string
: Equivalent to setting the font usingsetFont(...)
. -
fontPackage
-string
: Equivalent to setting the font package usingsetFont(...)
with a package specifier. -
<input>
takes no children
You can make your own "components" that you can re-use throughout your code. I've re-used the same convention that React/Typescript uses: built-in widgets are all lowercase. Custom components must start with an Uppercase letter. Note the below: "RobPanel" vs "verticalbox".
// RobPanel can be used anywhere I want this style of panel - with some forward thinking, it can be made more abstract and re-usable, if one wanted to.
const RobPanel = (props: { children?: SingleNode; title: TextNode; onClose?: () => void }) => {
return (
<border color={[0, 0, 0, 1]}>
<verticalbox>
<border color={[0.125, 0.125, 0.125, 1]}>
<horizontalbox valign={VerticalAlignment.Center}>
{boxChild(1, <text justify={TextJustification.Center}>{props.title}</text>)}
<button onClick={props.onClose}>Close</button>
</horizontalbox>
</border>
{boxChild(1, props.children)}
</verticalbox>
</border>
);
};
const element = new UIElement();
element.widget = render(<RobPanel title={"My Model"}>Hi There</RobPanel>);
refObject.addUI(element);
JSX is only used to instantiate a widget. It does not take into account any changes you make to the widget inline through some state. For those who are familiar with JSX in the context of React or simiar, this will be jarring, no doubt. In order to programmatically access and alter a deeply nested widget, you can pass in a ref object into the ref attribute of an element.
To do this, call the provided useRef()
function to generate a ref object. Once the widget has been instantiated, you can access the Widget directly using refObj.current
. see the below.
if the widget hasn't been instantiated yet, the ref.current
value will be null
.
There is a trap here, however. It's important that if you are done with a certain widget, you clear the ref, otherwise the widget may not get properly garbage collected. To do this, call refObj.clear()
. additionally, you can pass the same refObject to a new element to recyle it if you're no need a reference to the previous widget.
const imageRef = useRef<ImageWidget>();
const layoutRef = useRef<LayoutBox>();
const checkRef = () => {
console.log(imageRef.current);
if (imageRef.current) {
console.log(imageRef.current.getTintColor());
}
};
element.widget = render(
<border color={[0, 0, 0, 1]}>
<verticalbox gap={10}>
<layout maxWidth={240}>
<image ref={imageRef} url={"https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/ads/offworldcolonies_h.jpg"} />
</layout>
<horizontalbox>
<button onClick={checkRef}>Check</button>
</horizontalbox>
</verticalbox>
</border>
);
refObject.addUI(element);
you can also just keep a reference to the widget the old-fashioned way, too, but I find it easier to keep the widget in the right place in the hierarchy rather than scattering discreet widgets around the script.
const layoutRef = useRef<LayoutBox>();
const imageElement = render(<image url={"https://raw.githubusercontent.com/RobMayer/TTSLibrary/master/ads/offworldcolonies_h.jpg"} />);
const checkRef = () => {
console.log(imageElement);
console.log(imageElement.getTintColor());
};
element.widget = render(
<border color={[0, 0, 0, 1]}>
<verticalbox gap={10}>
<layout maxWidth={240}>{imageElement}</layout>
<horizontalbox>
<button onClick={checkRef}>Check</button>
</horizontalbox>
</verticalbox>
</border>
);
refObject.addUI(element);