Skip to content

[TODO] 布局功能 #35

Description

@Gu-ZT

Matters to be added to TODO - 将要加入TODO的事项

添加布局功能,可通过 frontmatter 修改,并支持在模组配置中切换深色模式

Describe what this TODO will do - 描述这个TODO要做的事情

1. 功能概述

新增布局(Layout)机制:文档作者通过 Front Matter 指定一套界面布局,控制阅读界面的窗口形态、面板排布、目录树行为、控件纹理、控件显隐与颜色变量;玩家可通过模组配置切换深色模式,布局自动响应。

---
layout: ageratum:dark
---

格式 layout: <namespace>:<layout>,对应 assets/<namespace>/ageratum/layouts/<layout>.json;命名空间可省略(layout: dark → 当前文档命名空间)。布局文件与语言无关。

2. 作用域与继承规则

优先级从高到低:

优先级 声明位置 生效范围
1 文档自身 frontmatter 仅该文档
2 所在目录 index.md(namespace:*/index,逐级向上冒泡) 该目录子树全部文档
3 语言根 index.md(namespace:index) 该 namespace 全部文档
4 未声明 默认布局 ageratum:light

以实际加载(含 en_us 回退)的文档为准解析布局链。深色模式的变体映射作用于布局链解析结果之上(见第 3 节)。

3. 深色模式

模组客户端配置新增开关(修改后对之后打开的界面生效):

# config/ageratum-client.toml
darkMode = false   # true 时启用深色模式

布局文件可通过 dark_variant 声明其深色模式版本:

{
  "name": "paper",
  "dark_variant": "mymod:paper_dark"
}

解析规则:

  1. 深色模式关闭:使用布局链(第 2 节)的解析结果,无额外处理
  2. 深色模式开启:对布局链解析结果沿 dark_variant 指针逐级跳转,直到目标布局不再声明为止;dark_variant 成环时输出警告日志并停在环前最后一个有效布局
  3. 未声明 dark_variant 的布局在深色模式下保持原样
  4. 内置 ageratum:light 声明 dark_variant: ageratum:dark,因此未指定布局的文档在深色模式下自动切换为 ageratum:dark
  5. ageratum:dark 继承 ageratum:light;任何布局(含深色变体)未声明 extends 时均默认继承 ageratum:light,此规则不变
  6. 推荐做法:深色变体通过 extends 显式继承其浅色对应布局,只覆盖纹理与颜色(见第 6 节实例)

4. 内置布局

  • ageratum:light —— 现行居中书页样式;所有布局的最终兜底;声明 dark_variant: ageratum:dark
  • ageratum:dark —— 深色书页样式;extends: ageratum:light,仅覆盖纹理与颜色

5. layout.json 格式

5.1 总则
  • 命名规范:所有键统一使用 snake_case(this_is_a_key 形式)
  • 增量覆盖:JSON 是对父布局(extends,缺省为兜底布局 ageratum:light)的补丁;对象深度合并,标量覆盖,未写的键继承上级
  • 不静默失败:文件缺失、JSON 解析失败、纹理无效、extends / dark_variant 成环均输出警告日志并回退上级/兜底
{
  "name": "fullscreen",
  "extends": "ageratum:light",        // 可选,父布局;缺省继承 ageratum:light
  "dark_variant": "mymod:fullscreen_dark", // 可选,深色模式版本
  "screen":      { },   // 窗口形态
  "panels":      { },   // 面板与区域排布
  "tree":        { },   // 目录树行为
  "bookmarks":   { },   // 收藏列表行为
  "actions":     { },   // 操作按钮区
  "content":     { },   // 内容区
  "textures":    { },   // 纹理(含组件纹理)
  "colors":      { },   // 颜色变量
  "interaction": { }    // 交互参数
}
5.2 screen —— 窗口形态
键 类型 默认值 说明
mode centered / fullscreen centered centered:书页按比例缩放居中(现行行为);fullscreen:界面铺满屏幕
min_margin.horizontal / min_margin.vertical int 32 / 10 centered 模式下书页相对屏幕的最小边距
padding {left,top,right,bottom} 全 0 fullscreen 模式下界面相对屏幕边缘的留白
5.3 panels —— 面板排布

panels 为命名面板表,面板即附着在界面左右边缘的竖向侧栏,内容区自动占据剩余空间。每个面板内由 sections 数组按从上到下的顺序声明分区:

"panels": {
  "sidebar": {
    "edge": "right",                    // left | right
    "width": 160,                       // 面板宽度(px)
    "spacing": 8,                       // 分区间距
    "padding": { "top": 8, "bottom": 8, "left": 6, "right": 6 },
    "background": {                     // 可选,面板背景纹理,支持九宫格
      "location": "ageratum:gui/guide/panel_dark",
      "nine_slice": { "border": 8 }
    },
    "sections": ["tree", "bookmarks", "actions"]
  }
}

可用分区:tree(目录树)、bookmarks(收藏列表)、actions(操作按钮)。同一分区最多出现一次;未列出的分区不渲染。兜底布局等价于:左面板 [tree],右面板 [actions, bookmarks](即现行 UI)。

控件显隐:每个面板及各分区行为段均支持 visible(bool,默认 true),置 false 即整体隐藏但保留配置;actions.buttons 数组同时承担按钮级显隐——未列出的按钮不渲染。

分区尺寸在对应行为段中配置(tree.height、bookmarks.height、actions.height),取值:

取值 含义
整数 固定高度(px)
"auto" 按内容高度(操作按钮区的默认行为)
"<n>%" 占面板可用高度的百分比
"fill" 瓜分剩余空间(多个 fill 按 weight 比例分配,默认 1)
5.4 tree —— 目录树行为
键 类型 默认值 说明
height 见 5.3 fill 分区高度
visible bool true 分区显隐(false 时整区不渲染,但保留配置)
indent_per_level int 10 每级子目录缩进(px),替代原 labelLevel2Indent,缩进可控
hover_shift int 5 悬停横向位移(px)。设为 0 即关闭滑出效果,标签默认全部完整显示在面板内
collapse_mode auto / expanded / collapsed auto 初始折叠状态;点击一级条目展开/收起子目录(现行点击行为保留)
pinned_parent bool true 滚动时是否固定当前父标签
scroll_hint bool true 目录树可滚动时是否显示上下箭头提示
marquee object 见下 空间不足时的滚动字幕

marquee 滚动字幕(目录树与收藏列表共用此结构):

键 类型 默认值 说明
enabled bool true 悬停在因宽度不足被截断的条目上时,文字横向滚动显示完整标题
speed float 30.0 滚动速度(px/s)
pause_ms int 800 滚动到两端时的停顿(ms)
gap int 24 循环滚动时首尾间隔(px)

未悬停或未截断时保持静态截断显示,不产生任何额外开销。

5.5 bookmarks —— 收藏列表行为
键 默认值 说明
height fill 分区高度
visible true 分区显隐
marquee 同 5.4 滚动字幕
show_add_button true 是否在分区顶部显示「添加书签」按钮(false 时可在 actions.buttons 中排入 add)
5.6 actions —— 操作按钮区
键 默认值 说明
height auto 分区高度
visible true 分区显隐
buttons ["close","share","add","return"] 按钮及其排布顺序;未列出的按钮不渲染(按钮级显隐)
direction vertical / horizontal 排列方向;horizontal 时按 spacing 横排并自动换行
spacing 10 按钮间距
align top / bottom 分区高于内容时按钮对齐方式(置于面板末尾的按钮区用 bottom 实现「下部操作按钮」)
5.7 content —— 内容区
键 默认值 说明
padding.left/top/right/bottom 15/18/15/18 内容区相对其边界的内边距(替代原 contentStartX/Y)
rows_margin 5 组件行间距
background — 可选,内容区背景纹理(支持九宫格),即原 textures.background 的全屏化表达
5.8 textures —— 控件纹理

字符串简写使用该键的默认规格;对象形式可覆盖 width/height/texture_size,且所有矩形纹理支持九宫格拉伸(nine_slice: {border} 或 {left,right,top,bottom}),这是全屏布局的基础能力:

"textures": {
  "label_primary": "ageratum:gui/guide/label_primary",
  "button_close": "ageratum:gui/guide/button_close",
  "components": {
    "item_slot": "ageratum:gui/component/slot",
    "recipe_crafting_table": { "location": "mymod:gui/component/crafting_dark", "width": 128, "height": 64 }
  }
}

界面控件键:label_primary、label_secondary、label_bookmark、button_close、button_share、button_return、button_add、button_up、button_down。按钮纹理仍为竖向两帧(常态/悬停)。

组件纹理键(textures.components)——可替换各 Markdown 组件的背景/素材:

键 默认纹理 说明
item_slot component/slot.png <item> 物品槽
recipe_crafting_table component/crafting_table.png 工作台配方背景
recipe_furnace component/furnace.png 熔炉配方背景
recipe_smithing_table component/smithing_table.png 锻造台配方背景
recipe_stonecutter component/stonecutter.png 切石机配方背景
structure_button_projection guide/button_projection.png 结构预览投影按钮

组件纹理键由组件工厂在注册时声明;第三方模组组件的键以 namespace:key 全限定名参与覆盖,布局作者无需关心实现类。

5.9 colors —— 颜色变量

界面级变量:link、broken_link、label_text_active、label_text_clickable、label_text_disabled、bookmark_text、background_gradient_start、background_gradient_end。值接受 #RRGGBB / #AARRGGBB / MC 命名颜色。

另有:

键 默认值 说明
panel_text #5D4630 面板内分区标题等辅助文字
content_text — 内容区正文基色(未设置时沿用组件自身颜色逻辑)

组件级颜色(colors.components)——代码块的背景、行号栏与语义着色全部可配置:

"colors": {
  "link": "#66CCFF",
  "components": {
    "code_block": {
      "background": "#22AAAAAA",       // 代码块背景
      "border": "#88333333",           // 边框
      "gutter_background": "#22444444", // 行号栏背景
      "gutter_line": "#55333333",       // 行号栏分隔线
      "line_number": "#99555555",       // 行号颜色
      "highlight_line": "#29657585",    // 高亮行背景
      "text": "#444444",               // 默认代码色
      "syntax": {                      // 语义色(通用角色,映射到各语言 span)
        "keyword": "#C792EA",
        "type": "#FFCB6B",
        "literal": "#F78C6C",
        "comment": "#939393",
        "operator": "#007C1F",
        "separator": "#0021FF"
      },
      "syntax_spans": {                 // 可选,按语法高亮 span 类名直改(优先于通用角色)
        "cpp_preproc": "#800080",
        "xml_tag_name": "#0037FF"
      }
    }
  }
}
code_block 键 默认值 说明
background #22AAAAAA 代码块背景色
border #88333333 边框颜色
gutter_background #22444444 行号栏背景色
gutter_line #55333333 行号栏分隔线颜色
line_number #99555555 行号文字颜色
highlight_line #29657585 {1,3-5} 高亮行的背景色
text #444444 未命中语义着色时的默认代码色
syntax.keyword / type / literal / comment / operator / separator #C792EA / #FFCB6B / #F78C6C / #939393 / #007C1F / #0021FF 通用语义角色
syntax_spans.<span> — jhighlight span 类名直改(如 java_keyword、xml_tag_name、cpp_preproc),未覆盖的 span 回退到对应通用角色

行号栏的整体显隐仍由模组配置 showCodeBlockLineNumbers 控制;布局只负责配色。其它组件的配色键(提示框等)后续按同一 colors.components.<组件> 结构扩展。

5.10 interaction
键 默认值 说明
scroll_step 16.0 滚轮单步滚动距离

6. 实例

6.1 现行布局(ageratum:light 完整形式)

现行「居中书页、左侧目录树、右侧操作按钮与收藏列表」的布局用本 schema 等价表达如下,同时作为全部键的默认值参考:

{
  "name": "light",
  "dark_variant": "ageratum:dark",

  "screen": {
    "mode": "centered",
    "min_margin": { "horizontal": 32, "vertical": 10 }
  },

  "panels": {
    "chapters": {
      "edge": "left",
      "width": 34,
      "visible": true,
      "sections": ["tree"]
    },
    "tools": {
      "edge": "right",
      "width": 34,
      "visible": true,
      "spacing": 10,
      "sections": ["actions", "bookmarks"]
    }
  },

  "tree": {
    "height": "fill",
    "visible": true,
    "indent_per_level": 10,
    "hover_shift": 5,
    "collapse_mode": "auto",
    "pinned_parent": true,
    "scroll_hint": true,
    "marquee": { "enabled": true, "speed": 30.0, "pause_ms": 800, "gap": 24 }
  },

  "bookmarks": {
    "height": "fill",
    "visible": true,
    "show_add_button": true,
    "marquee": { "enabled": true }
  },

  "actions": {
    "height": "auto",
    "visible": true,
    "buttons": ["close", "share", "add", "return"],
    "direction": "vertical",
    "spacing": 10,
    "align": "top"
  },

  "content": {
    "padding": { "left": 15, "top": 18, "right": 15, "bottom": 18 },
    "rows_margin": 5,
    "background": {
      "location": "ageratum:gui/guide/guide",
      "width": 360,
      "height": 232,
      "texture_size": 512
    }
  },

  "textures": {
    "label_primary": "ageratum:gui/guide/label_primary",
    "label_secondary": "ageratum:gui/guide/label_secondary",
    "label_bookmark": "ageratum:gui/guide/label_bookmark",
    "button_close": "ageratum:gui/guide/button_close",
    "button_share": "ageratum:gui/guide/button_share",
    "button_return": "ageratum:gui/guide/button_back",
    "button_add": "ageratum:gui/guide/button_add",
    "button_up": "ageratum:gui/guide/button_up",
    "button_down": "ageratum:gui/guide/button_down",
    "components": {
      "item_slot": "ageratum:gui/component/slot",
      "recipe_crafting_table": "ageratum:gui/component/crafting_table",
      "recipe_furnace": "ageratum:gui/component/furnace",
      "recipe_smithing_table": "ageratum:gui/component/smithing_table",
      "recipe_stonecutter": "ageratum:gui/component/stonecutter",
      "structure_button_projection": "ageratum:gui/guide/button_projection"
    }
  },

  "colors": {
    "link": "#66CCFF",
    "broken_link": "#FF5555",
    "label_text_active": "#8B5A2B",
    "label_text_clickable": "#5D4630",
    "label_text_disabled": "#3F3F3F",
    "bookmark_text": "#5D4630",
    "panel_text": "#5D4630",
    "background_gradient_start": "#C0101010",
    "background_gradient_end": "#D0101010",
    "components": {
      "code_block": {
        "background": "#22AAAAAA",
        "border": "#88333333",
        "gutter_background": "#22444444",
        "gutter_line": "#55333333",
        "line_number": "#99555555",
        "highlight_line": "#29657585",
        "text": "#444444",
        "syntax": {
          "keyword": "#C792EA",
          "type": "#FFCB6B",
          "literal": "#F78C6C",
          "comment": "#939393",
          "operator": "#007C1F",
          "separator": "#0021FF"
        }
      }
    }
  },

  "interaction": {
    "scroll_step": 16.0
  }
}

显隐控制示例:隐藏分享按钮 → 从 actions.buttons 中移除 "share";隐藏收藏列表 → "bookmarks": { "visible": false };隐藏整个右侧面板 → "panels": { "tools": { "visible": false } }。

6.2 全屏布局及其深色变体

实现「全屏显示;右侧边栏上半目录树、中间收藏列表、下部操作按钮;目录树无滑出、截断条目悬停跑马灯;子目录缩进 14px;配方背景替换;深色模式自动切换」。

assets/mymod/ageratum/layouts/fullscreen.json(浅色版,声明深色变体):

{
  "name": "fullscreen",
  "extends": "ageratum:light",
  "dark_variant": "mymod:fullscreen_dark",

  "screen": {
    "mode": "fullscreen",
    "padding": { "left": 12, "top": 12, "right": 12, "bottom": 12 }
  },

  "panels": {
    "sidebar": {
      "edge": "right",
      "width": 170,
      "spacing": 10,
      "padding": { "top": 10, "bottom": 10, "left": 8, "right": 8 },
      "background": {
        "location": "mymod:gui/guide/sidebar",
        "nine_slice": { "border": 8 }
      },
      "sections": ["tree", "bookmarks", "actions"]
    }
  },

  "tree": {
    "height": "45%",
    "indent_per_level": 14,
    "hover_shift": 0,
    "collapse_mode": "collapsed",
    "pinned_parent": false,
    "marquee": {
      "enabled": true,
      "speed": 30.0,
      "pause_ms": 800,
      "gap": 24
    }
  },

  "bookmarks": {
    "height": "fill",
    "marquee": { "enabled": true },
    "show_add_button": false
  },

  "actions": {
    "height": "auto",
    "align": "bottom",
    "direction": "horizontal",
    "spacing": 8,
    "buttons": ["add", "return", "share", "close"]
  },

  "content": {
    "padding": { "left": 24, "top": 24, "right": 24, "bottom": 24 },
    "rows_margin": 6,
    "background": {
      "location": "mymod:gui/guide/content",
      "nine_slice": { "left": 12, "right": 12, "top": 12, "bottom": 12 }
    }
  },

  "textures": {
    "label_primary": {
      "location": "mymod:gui/guide/label_row",
      "width": 154,
      "height": 18,
      "nine_slice": { "left": 6, "right": 6 }
    },
    "label_secondary": "mymod:gui/guide/label_row",
    "label_bookmark": "mymod:gui/guide/label_row",
    "components": {
      "item_slot": "mymod:gui/component/slot",
      "recipe_crafting_table": "mymod:gui/component/crafting_table",
      "recipe_furnace": "mymod:gui/component/furnace",
      "recipe_smithing_table": "mymod:gui/component/smithing_table",
      "recipe_stonecutter": "mymod:gui/component/stonecutter"
    }
  },

  "interaction": {
    "scroll_step": 24.0
  }
}

assets/mymod/ageratum/layouts/fullscreen_dark.json(深色变体,显式继承浅色版,仅覆盖纹理与颜色;未写 extends 时默认继承 ageratum:light):

{
  "name": "fullscreen_dark",
  "extends": "mymod:fullscreen",

  "panels": {
    "sidebar": {
      "background": {
        "location": "mymod:gui/guide/sidebar_dark",
        "nine_slice": { "border": 8 }
      }
    }
  },

  "content": {
    "background": {
      "location": "mymod:gui/guide/content_dark",
      "nine_slice": { "left": 12, "right": 12, "top": 12, "bottom": 12 }
    }
  },

  "textures": {
    "label_primary": "mymod:gui/guide/label_row_dark",
    "label_secondary": "mymod:gui/guide/label_row_dark",
    "label_bookmark": "mymod:gui/guide/label_row_dark",
    "button_close": "mymod:gui/guide/button_close_dark",
    "button_share": "mymod:gui/guide/button_share_dark",
    "button_return": "mymod:gui/guide/button_back_dark",
    "button_add": "mymod:gui/guide/button_add_dark",
    "button_up": "mymod:gui/guide/button_up_dark",
    "button_down": "mymod:gui/guide/button_down_dark",
    "components": {
      "item_slot": "mymod:gui/component/slot_dark",
      "recipe_crafting_table": "mymod:gui/component/crafting_table_dark",
      "recipe_furnace": "mymod:gui/component/furnace_dark",
      "recipe_smithing_table": "mymod:gui/component/smithing_table_dark",
      "recipe_stonecutter": "mymod:gui/component/stonecutter_dark"
    }
  },

  "colors": {
    "link": "#8AD4FF",
    "broken_link": "#FF6E6E",
    "label_text_active": "#FFD75F",
    "label_text_clickable": "#E8E0D0",
    "label_text_disabled": "#7A7A7A",
    "bookmark_text": "#E8E0D0",
    "panel_text": "#B0A894",
    "background_gradient_start": "#F0080808",
    "background_gradient_end": "#F0080808",
    "components": {
      "code_block": {
        "background": "#40202830",
        "border": "#88555566",
        "gutter_background": "#30202828",
        "gutter_line": "#55444455",
        "line_number": "#99AAAAAA",
        "highlight_line": "#40657585",
        "text": "#D8DEE9",
        "syntax": {
          "keyword": "#C792EA",
          "type": "#FFCB6B",
          "literal": "#F78C6C",
          "comment": "#7A7A7A",
          "operator": "#89DDFF",
          "separator": "#B0BEC5"
        }
      }
    }
  }
}

配套文档只需:

---
layout: mymod:fullscreen
---

玩家在模组配置中将 darkMode 设为 true 后,该文档自动以 mymod:fullscreen_dark 渲染;未声明 dark_variant 的布局保持原样。

相关

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions