UIElement
自 2.2.1UIElement 是 LDLib2 中最基础且最常用的 UI 组件。 所有 UI 组件都继承自它。
从概念上讲,它类似于 HTML 中的 #!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);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(...);布局属性
使用前最好先阅读 布局。
INFO
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);INFO
position
设置定位模式。RELATIVE 参与布局,ABSOLUTE 不影响兄弟元素。
layout.positionType(TaffyPosition.ABSOLUTE);INFO
top / right / bottom / left / start / end / horizontal / vertical / all
当 position 为 RELATIVE 或 ABSOLUTE 时使用的偏移量。
layout.top(10);
layout.leftPercent(30); // 30%
layout.allAuto()INFO
margin-*
*: top / right / bottom / left / start / end / horizontal / vertical / all
设置元素周围的外边距。
layout.marginTop(5);
layout.marginAll(3);INFO
padding-*
*: top / right / bottom / left / start / end / horizontal / vertical / all
设置边框与内容之间的内边距。
layout.paddingLeft(8);Grid 属性
INFO
要使用 grid 布局,请在容器元素上设置 display(TaffyDisplay.GRID)。模板属性在容器上定义 grid 结构,而 grid-row 和 grid-column 放在子元素上以控制其位置。
INFO
grid-template-rows
定义 grid 容器的显式行轨道。
支持的轨道大小:Npx(固定像素)、N%(百分比)、Nfr(分数单位)、auto、min-content、max-content、minmax(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 linesINFO
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");INFO
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 nameINFO
grid-auto-rows
为隐式创建的行设置行轨道大小——即未被 grid-template-rows 覆盖的行。
layout.gridAutoRows("auto");
layout.gridAutoRows("minmax(50px, auto)");INFO
grid-auto-columns
为隐式创建的列设置列轨道大小。
layout.gridAutoColumns("auto");
layout.gridAutoColumns("100px");INFO
grid-auto-flow
控制自动放置的元素如何填充 grid。ROW 优先填充行(默认);COLUMN 优先填充列。ROW_DENSE / COLUMN_DENSE 会回填之前的空隙。
layout.gridAutoFlow(GridAutoFlow.ROW);
layout.gridAutoFlow(GridAutoFlow.COLUMN);
layout.gridAutoFlow(GridAutoFlow.ROW_DENSE);INFO
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 lineINFO
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"));基本属性
INFO
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 上的辅助方法INFO
clip
Since mc26.1控制元素子树是否被裁剪,以及以何种方式裁剪。clip 用于替代旧的 1.21 overflow / overflow-clip API。除 NONE 以外的所有模式也会阻止元素内容边界外的命中测试。
| 模式 | 描述 |
|---|---|
NONE | 不裁剪。这是默认值。 |
SCISSOR | 将渲染裁剪到元素的内容边界内。可作为 26.1+ 中 overflow: hidden 或 setOverflowVisible(false) 的替代方案。 |
MASK | 使用 mask 设置的纹理裁剪渲染。适用于静态遮罩纹理。 |
DYNAMIC_MASK | 与 MASK 相同,但每帧都会刷新遮罩。适用于动画遮罩或会动态变化的遮罩。 |
style.clip(Clip.SCISSOR);
style.clip(Clip.MASK).mask(MCSprites.BORDER);
style.clip(Clip.DYNAMIC_MASK).mask(animatedMask);INFO
mask
Since mc26.1定义 clip: mask 和 clip: dynamic-mask 使用的纹理。遮罩会绘制在元素边界上,并使用采样得到的遮罩系数乘到已渲染子树的颜色和 alpha 上。
不透明的灰度遮罩使用纹理亮度:白色保留内容可见,黑色隐藏内容。使用 alpha 编码的遮罩会使用 alpha 通道,这适合透明 PNG 遮罩和柔和边缘。除非 clip 为 MASK 或 DYNAMIC_MASK,否则 mask 属性不会产生可见效果。
style.clip(Clip.MASK);
style.mask(MCSprites.BORDER);INFO
tooltips
定义当鼠标悬停在元素上时显示的悬停提示内容。
style.tooltips("tips.0", "tips.1");
style.appendTooltipsString("tips.2");INFO
color
使用 ARGB 乘数对当前元素的 background 和 overlay 纹理进行着色。 此着色仅应用于当前元素,不会影响子元素。
style.color(0x80FF8080);INFO
overflow-clip
已过时,仅适用于 1.21 API。 在 26.1 及更新版本中请使用 clip: mask 和 mask。
在 1.21 API 中,当元素的 overflow 为隐藏时,overflow-clip 会使用给定纹理的红色通道作为遮罩来裁剪子元素渲染。在 26.1+ 中,请将 clip 设置为 MASK 或 DYNAMIC_MASK,然后使用 mask 设置遮罩纹理。
style.overflowClip(MCSprites.BORDER);INFO
transform-2d
应用 2D 变换,例如平移、缩放或旋转。
style.transform2D(Transform2D.identity().scale(0.5f));
element.transform(transform -> transform.translate(10, 0))INFO
transition
定义属性变化之间的动画过渡效果。
layout.transition(new Transition(Map.of(LayoutProperties.HEIGHT, new Animation(1, 0, Eases.LINEAR))));状态
isVisible
当 isVisible 设置为 false 时,该元素及其所有子元素将不再被渲染。 与 display: NONE 不同,这不会影响布局计算。 isVisible = false 的元素也会被排除在命中测试之外,因此许多 UI 事件(如点击)将不会被触发。
isActive
当 isActive 设置为 false 时,元素可能会失去其交互行为——例如,按钮无法再被点击——并且元素将不再接收 tick 事件。
INFO
当 isActive 设置为 false 时,会自动向元素添加一个 __disabled__ 类。 你可以使用以下 LSS 选择器来设置活动和非活动状态的样式:
selector.__disabled__ {
}
selector:disabled {
}
selector:not(.__disabled__) {
}
selector:not(:disabled) {
}focusable
元素默认是 focusable: false。有些组件(如 TextField)在设计上是可聚焦的,但你仍然可以手动更改元素的可聚焦状态。 只有当 focusable 设置为 true 时,元素才能通过 focus() 或鼠标交互获得焦点。
INFO
当元素处于 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 中,你可以使用 #!xml <internal index="..."/> 标签访问内部元素,其中 index 指定要引用的内部元素:
<button>
<!-- 在这里获取内部文本组件 -->
<internal index="0">
</internal>
</button>INFO
在 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();可用事件
| 事件 | 描述 |
|---|---|
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
});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
));渲染
| 方法 | 签名 | 描述 |
|---|---|---|
isDisplayed() | boolean | 如果 display 不是 NONE,则返回 true。 |