Skip to content

Addon IPhonePage

HoshinoYumeka edited this page Sep 7, 2026 · 1 revision

IPhonePage / PhoneCanvas / PhoneStyle

首页附属接口文档 | 下一页:IAppSource / AppInfo

把界面画在手机屏幕内部所需的三个类型。

com.november.mcphone.api.client.ui.IPhonePage     一页界面
com.november.mcphone.api.client.ui.PhoneCanvas    一帧的绘制上下文
com.november.mcphone.api.client.ui.PhoneStyle     手机当前的配色
仅客户端
起于 1.2.13
入口 IPhoneApp.openPage() 返回一个 IPhonePage

不使用本接口时,App 被点开只能 Minecraft.getInstance().setScreen(...) 整个跳出手机:状态栏、导航栏、壁纸消失,返回键须自行实现,关闭后回到何处也须自行记录。内建的聊天、记事本、相册均为手机屏幕内的一页。

由 MCphone 负责、实现方不必处理的部分:状态栏、导航栏、壁纸、手机外壳;导航栏返回键 ◁ 的默认行为(退回主屏);ESC 关机;页面切走时调用 onClose()


IPhonePage

方法一览

方法 默认 说明
void render(PhoneCanvas canvas) 必须实现 每帧调用
boolean mouseClicked(double mouseX, double mouseY, int button) false 屏幕绝对坐标
boolean mouseScrolled(double mouseX, double mouseY, double amount) false 同上
boolean keyPressed(int keyCode, int scanCode, int modifiers) false ESC 不会走到这里
boolean charTyped(char codePoint, int modifiers) false 输入法提交的汉字与 Ctrl+V 粘贴走这里
boolean capturesKeyboard() false 有输入框时必须返回 true
boolean onBack() false 导航栏 ◁
void onOpen() 这一页刚被打开
void onClose() 这一页被切走,一定会被调到

只有 render 是必须实现的。 其余全为 default。按兼容承诺第一条,此后向本接口新增能力也只会新增 default 方法,实现类不会因 MCphone 升级而编译不过。

最小实现

public final class CalculatorApp implements IPhoneApp {

    @Override
    public IPhonePage openPage() { return new CalculatorPage(); }

    @Override
    public void onPress() { }        // 覆盖了 openPage,这里不会被调到

    // getId / getDisplayName / getIconTexture 略
}

public final class CalculatorPage implements IPhonePage {

    @Override
    public void render(PhoneCanvas c) {
        c.graphics().drawString(c.font(), "1 + 1 = 2",
                c.x() + 4, c.y() + 4, c.style().bodyColor(), false);
    }
}

render(PhoneCanvas)

每帧调用。在 canvas.x() / y() / width() / height() 给出的矩形内绘制,状态栏与导航栏已经扣除。

⚠ 不要保存 canvas 引用,它只在本次调用内有效。见坑 · PhoneCanvas 只活一帧

mouseClicked / mouseScrolled

坐标是屏幕绝对坐标,与 PhoneCanvas.x() 同一套。返回 true 表示已处理,不再向下传递。

⚠ 页面内的空点击应返回 true。返回 false 会落到 MCphone 的默认处理,而默认处理中"点手机外面 = 关机"。 见坑 · 空点击返回 false 会关机

keyPressed / charTyped

charTyped 接收输入法提交的字符与粘贴内容。

ESC 不会走到 keyPressed:它由 MCphone 统一处理为直接关机。该键不开放给页面拦截,也不允许页面改写其含义——玩家按了退出却什么都没发生是最糟的一种失败。退出前的收尾写在 onClose()

capturesKeyboard()

声明本页是否含输入框。

返回 效果
true 按键先给本页,不再落到原版按键判定上
false(默认) 按键照常参与原版判定

⚠ 有输入框却不返回 true:原版背包键默认为 E,玩家打拼音必然按到 e,手机当场关闭、内容全丢。 ⚠ 没有输入框却返回 true:背包键被吃掉,玩家无法用它关闭手机。 见坑 · capturesKeyboard 两个方向都会错

MCphone 自身的设备命名、聊天输入、笔记编辑三处均返回 true

onBack()

玩家按下导航栏 ◁ 时调用。

返回 效果
true 实现方自行处理(例如页面内还有一层要退),MCphone 不动
false(默认) 本页到此为止,退回主屏

onOpen() / onClose()

onOpen():本页刚被打开,用于拉取数据、重置状态。

onClose():本页被切走,用于释放资源与保存草稿。一定会被调用——从 ◁ 退出、按 ESC、关闭手机、断线,都会走到这里。

⚠ 收尾逻辑写在 onClose(),不是 onBack()。见坑 · 收尾写在 onClose 而非 onBack

异常处理

每个回调均被兜住:本页抛出异常只会使该页被关闭并记录一条日志,不会拖垮手机界面,更不会崩溃游戏。

这是兜底,不是设计。被兜掉的异常对玩家而言就是"点开这个 App 自己弹回去了"。


PhoneCanvas

一帧的绘制上下文,由 MCphone 构造并传入 render

访问器

方法 返回
GuiGraphics graphics() 原版绘制句柄
Font font() 手机界面用字体
int x() 内容区左边界(屏幕绝对坐标)
int y() 内容区上边界(已扣掉状态栏)
int width() 内容区宽度
int height() 内容区高度(已扣掉状态栏与导航栏)
int mouseX() / int mouseY() 鼠标位置,与上面同一套坐标
float partialTick() 本帧插值系数
PhoneStyle style() 手机当前配色
boolean hovered(int rx, int ry, int rw, int rh) 鼠标是否在该矩形内
boolean hoveredContent() 鼠标是否在整个内容区内
void clipped(int x, int y, int w, int h, Runnable body) 带裁剪地绘制,见下

坐标是屏幕绝对坐标,可直接传给 GuiGraphics,无须再加偏移。

hovered 的参数名带 r 是历史遗留,它收的仍是绝对坐标,不是相对内容区的偏移。

生命周期

每帧新建一个,只在该帧内有效。

⚠ 不要保存它。保存下来的对象里,鼠标位置与 GuiGraphics 下一帧即过期,用它绘制的后果是画在错误的位置,或对着已经关闭的渲染状态操作。

clipped(...)

void clipped(int x, int y, int w, int h, Runnable body)

在一个矩形内绘制,越界部分裁掉。用于可滚动的列表与长文本。

canvas.clipped(canvas.x(), canvas.y(), canvas.width(), canvas.height(), () -> {
    int y = canvas.y() - scrollPx;
    for (String line : lines) {
        canvas.graphics().drawString(canvas.font(), line, canvas.x(), y, color, false);
        y += canvas.font().lineHeight;
    }
});
行为 说明
坐标 屏幕绝对坐标,与 x() 同一套
嵌套 可以,内外两层取交集,与原版裁剪栈一致
矩形退化(宽或高 ≤ 0) 什么都不画,但 body 照样执行——其中顺手做的测量不会停摆
body 抛异常 裁剪照样收回,异常继续上抛,由 MCphone 关闭该页

不要自行调用 graphics().enableScissor(...) 原版该方法收窗口坐标且不看 PoseStack,而玩家可在「设置 → 界面大小」把整个手机缩放到 75%–300%。见坑 · 裁剪必须走 clipped

本方法只提供作用域写法、没有配对的 enable / disable,是刻意的:裁剪是全局状态,enable 之后未能走到 disable,该帧其余全部内容都会被切在那个框里。

界面缩放(1.9.3 起)

玩家可把整个手机缩放到 75%–300%。页面无须为此做任何改动:缩放是渲染时套的一层 pose,PhoneCanvas 给出的 x / y / 宽高与送入 mouseClicked / mouseScrolled 的鼠标坐标位于同一套未缩放坐标系中,两边一起换算过了。

两种写法会出错:

  • 自行从 Minecraft.getInstance().mouseHandler 取鼠标位置 —— 那是屏幕原始坐标,未经换算。
  • 自行调用 enableScissor —— 见上。

PhoneStyle

手机当前的配色。全部为 ARGB(0xAARRGGBB),可直接传给 filldrawString

方法 用途
int titleColor() 标题、当前选中项。最亮的一档
int bodyColor() 正文。绝大多数文字
int subtleColor() 次要信息:说明、时间戳、占位提示
int accentColor() 强调色:价格、数字、需要一眼看到的东西
int screenBackground() 屏幕底色。整页铺底时用它
int pressedOverlay() 悬停或按下时垫在下面的半透明层
int buttonColor() 可点按钮底色
int buttonHoverColor() 按钮悬停底色
int buttonDisabledColor() 点不动的按钮底色
int buttonDisabledTextColor() 点不动的按钮上的文字颜色

为什么是接口而不是一组 public static final int:常量会被编译器内联进调用方的 class 文件。附属编译时的配色会被固定下来,MCphone 之后更换配色,附属拿到的仍是编译当天的值,且没有任何迹象表明何处不对。接口是一次真实调用,永远拿到当下的值,也为将来的主题切换留了门。


首页附属接口文档 | 下一页:IAppSource / AppInfo

Clone this wiki locally