跳转至

UIElement

自 2.2.1

UIElement 是 LDLib2 中最基础且最常用的 UI 组件。 所有 UI 组件都继承自它。

从概念上讲,它类似于 HTML 中的 <div/> 元素:一个通用的容器,可以进行样式化、布局,并通过行为进行扩展。

因此,本页介绍的所有内容同样适用于 LDLib2 中的所有其他 UI 组件——所以请务必仔细阅读。


用法

var element = new UIElement();
element.style(style -> style.background(MCSprites.RECT));
element.layout(layout -> layout.width(40).height(40));
element.setFocusable(true);
element.addEventListener(UIEvents.MOUSE_DOWN, e -> e.currentElement.focus());
element.addClass("add-class");
element.removeClass("add-class");
root.addChild(element);
val root = element root@{
    element({
        layout = { size(40.px) }
        style = { background(MCSprites.RECT) }
        focusable = true
        cls = {
            +"add-class"
            -"remove-class"
        }
    }) {
        events { UIEvents.MOUSE_DOWN += { it.currentElement.focus() } }
    }
}
let element = new UIElement();
element.style(style => style.background(MCSprites.RECT));
element.layout(layout => layout.width(40).height(40));
element.setFocusable(true);
element.addEventListener(UIEvents.MOUSE_DOWN, e => e.currentElement.focus());
element.addClass("add-class");
element.removeClass("add-class");
root.addChild(element);

Xml

<element id="my_id" class="class1 class2" focusable="false" visible="true" active="true" style="background: #fff; width: 50">
    <!-- 在此添加子元素 -->
    <button text="click me!"/>
    <inventory-slots/>
</element>

样式

布局

布局属性实际上也是样式。

UIElement 的样式(包括布局)可以通过以下方式访问:

element.style(style -> style.background(...));
element.layout(layout -> layout.width(...));
element.getStyle().background(...);
element.getLayout().width(...);
element({
    layout = { width(20.pct) }
    style = { background(MCSprites.RECT) }
}) { }

element.layoutDsl { 
    width(20.pct)
}
element.styleDsl { 
    background(MCSprites.RECT)
}
element.style(style => style.background(...));
element.layout(layout => layout.width(...));
element.getStyle().background(...);
element.getLayout().width(...);

布局属性

使用前最好先阅读 布局

display

控制元素是否参与布局。FLEX 启用 flex 布局,GRID 启用 grid 布局,NONE 将元素从布局计算中移除,CONTENTS 不影响布局但会渲染其子元素。

layout.display(TaffyDisplay.FLEX);
layout.display(TaffyDisplay.GRID); // enable grid layout
element.setDisplay(false); // equals to layout.display(TaffyDisplay.NONE);
layout {
    display(false)
    display(TaffyDisplay.GRID)
}
element {
    display: flex;
    display: grid;
}

layout-direction

设置布局方向。通常从父元素继承。

layout.layoutDirection(TaffyDirection.LTR);
layout {
    direction(TaffyDirection.LTR)
}
element {
    layout-direction: ltr;
}

flex-basis

设置在 flex 增长/收缩之前的主轴初始大小。支持 百分比自动 模式。

layout.flexBasis(1);
layout {
    flexBasis(1)
}
element {
    flex-basis: 1;
}

flex

使元素沿主轴灵活伸缩。

layout.flex(1);
layout {
    flex(1)
}
element {
    flex: 1;
}

flex-grow

控制当有额外空间时,元素的增长程度。

layout.flexGrow(1);
layout {
    flexGrow(1)
}
element {
    flex-grow: 1;
}

flex-shrink

控制当空间不足时,元素的收缩程度。

layout.flexShrink(1);
layout {
    flexShrink(1)
}
element {
    flex-shrink: 1;
}

flex-direction

定义主轴方向,例如 ROWCOLUMN

layout.flexDirection(FlexDirection.ROW);
layout {
    flexDirection(FlexDirection.ROW)
}
element {
    flex-direction: row;
}

flex-wrap

控制子元素是否换行。

layout.wrap(FlexWrap.WRAP);
layout {
    wrap(FlexWrap.WRAP)
}
element {
    flex-wrap: wrap;
}

position

设置定位模式。RELATIVE 参与布局,ABSOLUTE 不影响兄弟元素。

layout.positionType(TaffyPosition.ABSOLUTE);
layout {
    position(TaffyPosition.ABSOLUTE)
}
element {
    position: absolute;
}

top / right / bottom / left / start / end / horizontal / vertical / all

positionRELATIVEABSOLUTE 时使用的偏移量。

layout.top(10);
layout.leftPercent(30); // 30%
layout.allAuto()
layout = {
    pos {
        top(10.px)
        left(10.px)
        bottom(auto)
    }
}
element {
    top: 10;
    left: 30%;
    all: auto;
}

margin-*

*: top / right / bottom / left / start / end / horizontal / vertical / all

设置元素周围的外边距。

layout.marginTop(5);
layout.marginAll(3);
layout = {
    margin {
        top(5.px)
        all(3.px)
    }
}
element {
    margin-top: 5;
    margin-all: 3;
}

padding-*

*: top / right / bottom / left / start / end / horizontal / vertical / all

设置边框与内容之间的内边距。

layout.paddingLeft(8);
layout = {
    padding { left(8) }
}
element {
    padding-left: 8;
}

gap-*

*: row / column / all

设置 flex 布局中子元素之间的间距。

layout.rowGap(6);
layout = {
    gap { row(6) }
}
element {
    gap-row: 6;
}

width

设置元素宽度。支持 百分比自动 模式。

layout.width(100);
layout.widthPercent(20); // 20%
layout = {
    width(100)
    width(20.pct) // 20%
}
element {
    width: 100;
    width: 20%;
}

height

设置元素高度。支持 百分比自动 模式。

layout.height(50);
layout = {
    width(100)
    height(20.pct) // 20%
}
element {
    height: 50;
}

min-width / min-height

设置最小尺寸约束。

layout.minWidth(20);
layout = {
    minWidth(20);
}
element {
    min-width: 20;
}

max-width / max-height

设置最大尺寸约束。

layout.maxHeight(200);
layout = {
    maxHeight(200);
}
element {
    max-height: 200;
}

aspect-rate

锁定宽高比。对于方形或图标元素很有用。

layout.aspectRate(1);
layout = {
    aspectRate(1);
}
element {
    aspect-rate: 1;
}

align-items

沿交叉轴对齐子元素(容器属性)。

layout.alignItems(AlignItems.CENTER);
layout = {
    alignItems(AlignItems.CENTER)
}
element {
    align-items: center;
}

justify-content

沿主轴对齐子元素(容器属性)。

layout.justifyContent(AlignContent.CENTER);
layout = {
    justifyContent(AlignContent.CENTER)
}
element {
    justify-content: center;
}

align-self

覆盖单个元素的交叉轴对齐方式。

layout.alignSelf(AlignItems.CENTER);
layout = {
    alignSelf(AlignItems.CENTER)
}
element {
    align-self: center;
}

align-content

当启用 flex-wrap 时,对齐换行后的行。

layout.alignContent(AlignContent.CENTER);
layout = {
    alignContent(AlignContent.CENTER)
}
element {
    align-content: center;
}

Grid 属性

要使用 grid 布局,请在容器元素上设置 display(TaffyDisplay.GRID)。模板属性在容器上定义 grid 结构,而 grid-rowgrid-column 放在子元素上以控制其位置。

grid-template-rows

定义 grid 容器的显式行轨道。

支持的轨道大小:Npx(固定像素)、N%(百分比)、Nfr(分数单位)、automin-contentmax-contentminmax(min, max)fit-content(limit)repeat(count, size)[name](命名线)。多个轨道以空格分隔。

layout.display(TaffyDisplay.GRID);
layout.gridTemplateRows("1fr 1fr 1fr");             // three equal rows
layout.gridTemplateRows("50px 1fr auto");           // fixed, flexible, auto
layout.gridTemplateRows("repeat(3, 100px)");        // three 100px rows
layout.gridTemplateRows("[header] 50px [content] 1fr [footer] 50px"); // named lines
layout = {
    display(TaffyDisplay.GRID)
    grid {
        templateRows("1fr 1fr 1fr")
        templateRows("repeat(3, 100px)")
    }
}
element {
    display: grid;
    grid-template-rows: 1fr 1fr 1fr;
    grid-template-rows: repeat(3, 100px);
}

grid-template-columns

定义 grid 容器的显式列轨道。使用与 grid-template-rows 相同的轨道尺寸语法。

layout.display(TaffyDisplay.GRID);
layout.gridTemplateColumns("10px 1fr 10px");    // fixed margins + flexible center
layout.gridTemplateColumns("repeat(3, 1fr)");   // three equal columns
layout.gridTemplateColumns("minmax(100px, 1fr) 200px");
layout = {
    display(TaffyDisplay.GRID)
    grid {
        templateColumns("10px 1fr 10px")
        templateColumns("repeat(3, 1fr)")
    }
}
element {
    display: grid;
    grid-template-columns: 10px 1fr 10px;
    grid-template-columns: repeat(3, 1fr);
}

grid-template-areas

为 grid 单元格分配命名区域。每个引号字符串代表一行;其中的单词命名该行中的单元格。使用 . 表示空单元格。所有行必须具有相同数量的单元格。

layout.display(TaffyDisplay.GRID);
layout.gridTemplateColumns("1fr 1fr 1fr");
layout.gridTemplateRows("auto 1fr auto");
layout.gridTemplateAreas(
    "\"header header header\" \"sidebar content content\" \"footer footer footer\""
);
// Children reference areas via gridRow/gridColumn by area name
layout = {
    display(TaffyDisplay.GRID)
    grid {
        templateColumns("1fr 1fr 1fr")
        templateRows("auto 1fr auto")
        templateAreas("\"header header header\" \"sidebar content content\" \"footer footer footer\"")
    }
}
element {
    display: grid;
    grid-template-rows: auto 1fr auto;
    grid-template-columns: 1fr 1fr 1fr;
    grid-template-areas: "header header header" "sidebar content content" "footer footer footer";
}

grid-auto-rows

为隐式创建的行设置行轨道大小——即未被 grid-template-rows 覆盖的行。

layout.gridAutoRows("auto");
layout.gridAutoRows("minmax(50px, auto)");
layout = {
    display(TaffyDisplay.GRID)
    grid { autoRows("minmax(50px, auto)") }
}
element {
    display: grid;
    grid-auto-rows: minmax(50px, auto);
}

grid-auto-columns

为隐式创建的列设置列轨道大小。

layout.gridAutoColumns("auto");
layout.gridAutoColumns("100px");
layout = {
    display(TaffyDisplay.GRID)
    grid { autoColumns("100px") }
}
element {
    display: grid;
    grid-auto-columns: 100px;
}

grid-auto-flow

控制自动放置的元素如何填充 grid。ROW 优先填充行(默认);COLUMN 优先填充列。ROW_DENSE / COLUMN_DENSE 会回填之前的空隙。

layout.gridAutoFlow(GridAutoFlow.ROW);
layout.gridAutoFlow(GridAutoFlow.COLUMN);
layout.gridAutoFlow(GridAutoFlow.ROW_DENSE);
layout = {
    display(TaffyDisplay.GRID)
    grid { autoFlow(GridAutoFlow.COLUMN) }
}
element {
    display: grid;
    grid-auto-flow: column;
}

grid-row

控制子元素在 grid 容器中的行位置。请在子元素上设置,而不是容器上。

位置值:"1"(行号)、"1 / 3"(起始/结束行)、"span 2"(跨越 N 行)、"1 / span 2"(起始 + 跨越)、"header"(命名区域行)、"-1"(最后一行)。

child.layout(layout -> layout.gridRow("1"));          // row 1
child.layout(layout -> layout.gridRow("1 / 3"));      // rows 1-3
child.layout(layout -> layout.gridRow("span 2"));     // span 2 rows
child.layout(layout -> layout.gridRow("header"));     // named area row
child.layout(layout -> layout.gridRow("-1"));         // last row line
element({
    layout = {
        grid { row("1 / span 2") }
    }
}) { }
child {
    grid-row: 1;
    grid-row: 1 / 3;
    grid-row: span 2;
    grid-row: header;
}

grid-column

控制子元素在 grid 容器中的列位置。使用与 grid-row 相同的位置语法。

child.layout(layout -> layout.gridColumn("2"));
child.layout(layout -> layout.gridColumn("1 / span 3"));
child.layout(layout -> layout.gridColumn("sidebar"));
element({
    layout = {
        grid { column("1 / span 2") }
    }
}) { }
child {
    grid-column: 2;
    grid-column: 1 / span 3;
    grid-column: sidebar;
}

基本属性

background

设置绘制在元素后方的纹理,例如纯色、矩形精灵或图片。

style.background(MCSprites.BORDER);
style = {
    background(MCSprites.BORDER)
}

查看 LSS 中的纹理 了解 lss 支持。

element {
    background: #FFF;
    background: rect(#2ff, 3);
    background: sprite(ldlib2:textures/gui/icon.png);
}

overflow

已过时,仅适用于 1.21 API。 在 26.1 及更新版本中请使用 clip

在 1.21 API 中,overflow 用于控制如何处理溢出内容。overflow 本身是布局属性,同时也有 overflowVisible(...)setOverflowVisible(...) 等便捷辅助方法。在 26.1+ 中,UIElement#setOverflowVisible(false) 会映射为 Clip.SCISSOR

layout.overflow(YogaOverflow.HIDDEN);
style.overflowVisible(false);
element.setOverflowVisible(false); // UIElement 上的辅助方法
style = {
    overflowVisible(false)
}
element {
    overflow: hidden;
}

clip

Since mc26.1

控制元素子树是否被裁剪,以及以何种方式裁剪。clip 用于替代旧的 1.21 overflow / overflow-clip API。除 NONE 以外的所有模式也会阻止元素内容边界外的命中测试。

模式 描述
NONE 不裁剪。这是默认值。
SCISSOR 将渲染裁剪到元素的内容边界内。可作为 26.1+ 中 overflow: hiddensetOverflowVisible(false) 的替代方案。
MASK 使用 mask 设置的纹理裁剪渲染。适用于静态遮罩纹理。
DYNAMIC_MASK MASK 相同,但每帧都会刷新遮罩。适用于动画遮罩或会动态变化的遮罩。
style.clip(Clip.SCISSOR);
style.clip(Clip.MASK).mask(MCSprites.BORDER);
style.clip(Clip.DYNAMIC_MASK).mask(animatedMask);
style = {
    clip(Clip.SCISSOR)
    clip(Clip.MASK)
    mask(MCSprites.BORDER)
}
element {
    clip: scissor;
}

element.masked {
    clip: mask;
    mask: sprite(ldlib2:textures/gui/icon.png);
}

element.animated-mask {
    clip: dynamic-mask;
    mask: sprite(ldlib2:textures/gui/icon.png);
}

mask

Since mc26.1

定义 clip: maskclip: dynamic-mask 使用的纹理。遮罩会绘制在元素边界上,并使用采样得到的遮罩系数乘到已渲染子树的颜色和 alpha 上。

不透明的灰度遮罩使用纹理亮度:白色保留内容可见,黑色隐藏内容。使用 alpha 编码的遮罩会使用 alpha 通道,这适合透明 PNG 遮罩和柔和边缘。除非 clipMASKDYNAMIC_MASK,否则 mask 属性不会产生可见效果。

style.clip(Clip.MASK);
style.mask(MCSprites.BORDER);
style = {
    clip(Clip.MASK)
    mask(MCSprites.BORDER)
}

查看 LSS 中的纹理 了解 lss 支持。

element {
    clip: mask;
    mask: sprite(ldlib2:textures/gui/icon.png);
}

overlay

控制在元素内容上方绘制的覆盖层渲染。

style.overlay(...);
style = {
    overlay(MCSprites.BORDER)
}

查看 LSS 中的纹理 了解 lss 支持。

element {
    overlay: #FFF;
    overlay: rect(#2ff, 3);
    overlay: sprite(ldlib2:textures/gui/icon.png);
}

tooltips

定义当鼠标悬停在元素上时显示的悬停提示内容。

style.tooltips("tips.0", "tips.1");
style.appendTooltipsString("tips.2");
style = {
    tooltips("tips.0", "tips.1")
}
element {
    tooltips: this is my tooltips;
}

z-index

控制元素的堆叠顺序。数值较大的元素显示在数值较小的元素之上。

style.zIndex(1);
style = {
    zIndex(1)
}
element {
    z-index: 1;
}

opacity

设置元素的透明度等级。0 为完全透明,1 为完全不透明。

style.opacity(0.8f);
style = {
    opacity(0.8)
}
element {
    opacity: 0.8;
}

color

使用 ARGB 乘数对当前元素的 backgroundoverlay 纹理进行着色。 此着色仅应用于当前元素,不会影响子元素。

style.color(0x80FF8080);
style = {
    color(0x80FF8080.toInt())
}
element {
    color: #80FF8080;
}

overflow-clip

已过时,仅适用于 1.21 API。 在 26.1 及更新版本中请使用 clip: maskmask

在 1.21 API 中,当元素的 overflow 为隐藏时,overflow-clip 会使用给定纹理的红色通道作为遮罩来裁剪子元素渲染。在 26.1+ 中,请将 clip 设置为 MASKDYNAMIC_MASK,然后使用 mask 设置遮罩纹理。

style.overflowClip(MCSprites.BORDER);
style = {
    overflowClip(MCSprites.BORDER)
}

查看 LSS 中的纹理 了解 lss 支持。

element {
    overflow-clip: sprite(ldlib2:textures/gui/icon.png);
}

transform-2d

应用 2D 变换,例如平移、缩放或旋转。

style.transform2D(Transform2D.identity().scale(0.5f));
element.transform(transform -> transform.translate(10, 0))
style = {
    transform2D(Transform2D.identity().scale(0.5f))
}
element {
    transform: translate(10, 20) rotate(45) scale(2, 2) pivot(0.5, 0.5);
    transform: translateX(10) scale(0.5);
}

transition

定义属性变化之间的动画过渡效果。

layout.transition(new Transition(Map.of(LayoutProperties.HEIGHT, new Animation(1, 0, Eases.LINEAR))));
style = {
    transition(Transition(mapOf(LayoutProperties.HEIGHT to Animation(1f, 0f, Eases.LINEAR))))
}
element {
    transition: width 1;
    transition: background 0.8 quad_in_out,
                transform 0.3;
}

状态

isVisible

isVisible 设置为 false 时,该元素及其所有子元素将不再被渲染。 与 display: NONE 不同,这不会影响布局计算。 isVisible = false 的元素也会被排除在命中测试之外,因此许多 UI 事件(如点击)将不会被触发。

isActive

isActive 设置为 false 时,元素可能会失去其交互行为——例如,按钮无法再被点击——并且元素将不再接收 tick 事件。

Note

isActive 设置为 false 时,会自动向元素添加一个 __disabled__ 类。 你可以使用以下 LSS 选择器来设置活动和非活动状态的样式:

selector.__disabled__ {
}

selector:disabled {
}

selector:not(.__disabled__) {
}

selector:not(:disabled) {
}

focusable

元素默认是 focusable: false。有些组件(如 TextField)在设计上是可聚焦的,但你仍然可以手动更改元素的可聚焦状态。 只有当 focusable 设置为 true 时,元素才能通过 focus() 或鼠标交互获得焦点。

Note

当元素处于 focused(已聚焦)状态时,会自动添加一个 __focused__ 类。 你可以使用以下 LSS 选择器来设置已聚焦和未聚焦状态的样式:

selector.__focused__ {
}

selector:focused {
}

selector:not(.__focused__) {
}

selector:not(:focused) {
}

hover state

当元素被悬停时,会自动添加一个 __hovered__ 类。 为了兼容 CSS,你可以使用 :hover 作为选择器简写,它等价于 .__hovered__

selector.__hovered__ {
}

selector:hover {
}

isInternalUI

这是一个特殊状态,表示一个元素是否是组件的内部部分。 例如,一个 button 包含一个用于渲染其标签的内部 text 元素。

从语义上讲,不允许直接添加、移除或重新排序内部元素。 但是,你仍然可以通过编辑器或 XML 编辑它们的样式并管理它们的子元素。 在编辑器中,内部元素在层级视图中显示为灰色。

在 XML 中,你可以使用 <internal index="..."/> 标签访问内部元素,其中 index 指定要引用的内部元素:

<button>
    <!-- 在这里获取内部文本组件 -->
    <internal index="0">
    </internal>
</button>

在 LSS 中,你可以使用 :host 和 :internal 来明确指定宿主元素或内部元素。默认情况下,选择器会匹配两者,除非加以限制。

button > text {
}

button > text:internal {
}

button > text:host {
}


字段

仅列出外部可观察或可配置的公共或受保护字段。

名称 类型 访问权限 描述
taffyStyle TaffyLayoutStyle protected (getter) 用于布局计算的底层 Taffy 样式桥接对象。
nodeId NodeId protected (getter) 注册在 TaffyTree 中的节点句柄。
modularUI ModularUI private (getter) 此元素所属的 ModularUI 实例。
id String private (getter/setter) 元素 ID,用于选择器和查询。
classes Set<String> private (getter) 应用于此元素的类似 CSS 的类列表。
styleBag StyleBag private (getter) 存储已解析的样式候选值和计算后的样式。
styles List<Style> private (getter) 附加到此元素的内联样式。
layoutStyle LayoutStyle private (getter) 布局属性对应的样式包装器。
style BasicStyle private (getter) 基础视觉样式(background、overlay 着色、opacity、zIndex 等)。
isVisible boolean private (getter/setter) 元素是否可见。
isActive boolean private (getter/setter) 元素是否参与逻辑和事件。
focusable boolean private (getter/setter) 元素是否可以获得焦点。
isInternalUI boolean private (getter) 标记内部(组件拥有的)元素。

方法

布局与几何

方法 签名 描述
getLayout() LayoutStyle 返回布局样式控制器。
layout(...) UIElement layout(Consumer<LayoutStyle>) 以流式方式修改布局属性。
getTaffyLayout() Layout 返回该元素解析后的 Taffy 布局结果。
getPositionX() float 屏幕上的绝对 X 坐标。
getPositionY() float 屏幕上的绝对 Y 坐标。
getSizeWidth() float 元素的计算宽度。
getSizeHeight() float 元素的计算高度。
getContentX() float 内容区域的 X 坐标(不包括边框和内边距)。
getContentY() float 内容区域的 Y 坐标。
getContentWidth() float 内容区域的宽度。
getContentHeight() float 内容区域的高度。
adaptPositionToScreen() void 调整位置以保持在屏幕边界内。
adaptPositionToElement(...) void 调整位置以保持在另一个元素内部。

树结构

方法 签名 描述
getParent() UIElement 返回父元素,或 null
getChildren() List<UIElement> 返回一个不可修改的子元素列表。
addChild(...) UIElement addChild(UIElement) 添加一个子元素。
addChildren(...) UIElement addChildren(UIElement...) 添加多个子元素。
removeChild(...) boolean removeChild(UIElement) 移除一个子元素。
removeSelf() boolean 从其父元素中移除此元素。
clearAllChildren() void 移除所有子元素。
isAncestorOf(...) boolean 检查此元素是否是另一个元素的祖先。
getStructurePath() ImmutableList<UIElement> 从根元素到此元素的路径。

样式与类

方法 签名 描述
style(...) UIElement style(Consumer<BasicStyle>) 修改内联视觉样式。
lss(...) UIElement lss(String, Object) 以编程方式应用样式表风格的属性。
addClass(...) UIElement addClass(String) 添加一个类似 CSS 的类。
removeClass(...) UIElement removeClass(String) 移除一个类。
hasClass(...) boolean 检查类是否存在。
getLocalStylesheets() List<Stylesheet> 返回附加到此元素的本地样式表。
addLocalStylesheet(...) UIElement addLocalStylesheet(Stylesheet) 添加本地样式表(仅自身 + 后代)。
addLocalStylesheet(...) UIElement addLocalStylesheet(String) 从 LSS 文本解析并添加本地样式表。
removeLocalStylesheet(...) UIElement removeLocalStylesheet(Stylesheet) 从此元素作用域中移除本地样式表。
clearLocalStylesheets() UIElement 移除附加到此元素的所有本地样式表。
transform(...) UIElement transform(Consumer<Transform2D>) 应用 2D 变换。
animation() StyleAnimation 创建一个以此元素为目标的样式动画。查看 StyleAnimation
animation(a -> {}) StyleAnimation 如果 ModularUI 有效则立即运行动画设置,或在变为有效时通过 MUI_CHANGED 运行一次。

焦点与交互

方法 签名 描述
focus() void 请求此元素获得焦点。
blur() void 如果此元素已聚焦,则清除其焦点。
isFocused() boolean 如果此元素已聚焦,则返回 true。
isHover() boolean 如果鼠标直接悬停在此元素上,则返回 true。
isSelfOrChildHover() boolean 如果自身或子元素被悬停,则返回 true。
startDrag(...) DragHandler 开始一个拖拽操作。

事件

方法 签名 描述
addEventListener(...) UIElement addEventListener(String, UIEventListener) 注册一个冒泡阶段的事件监听器。
addEventListener(..., true) UIElement addEventListener(String, UIEventListener, boolean) 注册一个捕获阶段的监听器。
removeEventListener(...) void 移除一个事件监听器。
stopInteractionEventsPropagation() UIElement 停止鼠标和拖拽事件的传播。

用法

// Bubble-phase listener (default): fires after children handle the event
element.addEventListener(UIEvents.MOUSE_DOWN, event -> {
    event.currentElement.focus();
});

// Capture-phase listener: fires before children handle the event
element.addEventListener(UIEvents.CLICK, event -> {
    event.stopPropagation(); // prevent children from seeing this event
}, true);

// Removing a specific listener
UIEventListener listener = event -> { /* ... */ };
element.addEventListener(UIEvents.CLICK, listener);
element.removeEventListener(UIEvents.CLICK, listener);

// Stop all mouse/drag events from bubbling to parent elements
element.stopInteractionEventsPropagation();
element {
    // Bubble events (default)
    events {
        UIEvents.MOUSE_DOWN += UIEventListener { it.currentElement.focus() }
        UIEvents.CLICK on { event -> /* handle click */ }
    }
    // Capture events
    events(capture = true) {
        UIEvents.CLICK on { it.stopPropagation() }
    }
}
element.addEventListener(UIEvents.MOUSE_DOWN, event => {
    event.currentElement.focus();
});

可用事件

事件 描述
UIEvents.MOUSE_DOWN 鼠标按钮在元素上按下
UIEvents.MOUSE_UP 鼠标按钮释放
UIEvents.CLICK 鼠标点击(在同一元素上按下并释放)
UIEvents.DOUBLE_CLICK 鼠标双击
UIEvents.MOUSE_MOVE 鼠标在元素上移动
UIEvents.MOUSE_ENTER 鼠标指针进入元素边界
UIEvents.MOUSE_LEAVE 鼠标指针离开元素边界
UIEvents.MOUSE_WHEEL 鼠标滚轮滚动
UIEvents.DRAG_ENTER 拖拽操作进入此元素
UIEvents.DRAG_LEAVE 拖拽操作离开此元素
UIEvents.DRAG_UPDATE 拖拽目标位置已更新
UIEvents.DRAG_SOURCE_UPDATE 拖拽源位置已更新
UIEvents.DRAG_PERFORM 物品被放置到此元素上
UIEvents.DRAG_END 拖拽操作结束
UIEvents.FOCUS 元素获得键盘焦点
UIEvents.BLUR 元素失去键盘焦点
UIEvents.FOCUS_IN 焦点移入此元素的子树
UIEvents.FOCUS_OUT 焦点移出此元素的子树
UIEvents.KEY_DOWN 键盘按键按下
UIEvents.KEY_UP 键盘按键释放
UIEvents.CHAR_TYPED 输入了一个可打印字符
UIEvents.HOVER_TOOLTIPS 悬停时收集悬停提示
UIEvents.VALIDATE_COMMAND 斜杠命令验证
UIEvents.EXECUTE_COMMAND 斜杠命令执行
UIEvents.LAYOUT_CHANGED 布局已重新计算
UIEvents.STYLE_CHANGED 样式已重新计算
UIEvents.REMOVED 元素已从其父元素中移除
UIEvents.ADDED 元素已添加到父元素
UIEvents.MUI_CHANGED ModularUI 关联已更改
UIEvents.TICK 周期性的客户端 tick

客户端-服务端同步与 RPC

方法 签名 描述
addSyncValue(...) UIElement 注册一个同步值。
removeSyncValue(...) UIElement 注销一个同步值。
addRPCEvent(...) RPCEmitter 注册一个 RPC 事件。
sendEvent(...) void 向服务端发送一个 RPC 事件。
sendEvent(..., callback) <T> void 发送一个带有响应回调的 RPC 事件。

服务端事件

服务端事件监听器在服务端而不是客户端上运行。它们使用相同的 UIEvents 类型常量,并支持冒泡和捕获阶段。它们通过内部 RPC 机制自动同步。

// Runs on the server when UIEvents.TICK fires
element.addServerEventListener(UIEvents.TICK, event -> {
    // server-side tick logic
});
element {
    serverEvents {
        UIEvents.TICK on { event ->
            // server-side logic
        }
    }
    // Capture phase on server
    serverEvents(capture = true) {
        UIEvents.CLICK on { it.stopPropagation() }
    }
}

RPC 事件

RPC(远程过程调用)事件允许客户端显式调用服务端逻辑,并可选择接收响应。

// Register an RPC event during element initialization
RPCEmitter emitter = element.addRPCEvent(ele ->
    RPCEventBuilder.simple(UIEvents.CLICK, (e, args) -> {
        // This runs on the server
        ServerPlayer player = e.modularUI.player;
        player.sendSystemMessage(Component.literal("Hello from server!"));
    })
);

// Trigger the RPC from client (e.g., inside a client event listener)
element.addEventListener(UIEvents.CLICK, event ->
    element.sendEvent(emitter.event())
);

数据绑定

数据绑定自动在服务端和客户端之间同步数值。在 Java 中使用 addSyncValue,或在 Kotlin 中使用 bind* DSL 辅助函数。

// Bidirectional: synced server <-> client
element.addSyncValue(new SyncValue<>(Integer.class,
    () -> myData.count,
    v  -> myData.count = v
));
element({}) {
    // Bidirectional (server <-> client)
    bind({ myData.count }, { myData.count = it })

    // Server -> client only
    bindS2C({ myData.count })

    // Client -> server only
    bindC2S({ v -> myData.count = v })

    // Bind a mutable property directly (bidirectional)
    bind(myData::count)
}

渲染

方法 签名 描述
isDisplayed() boolean 如果 display 不是 NONE,则返回 true。