diff --git a/.gitignore b/.gitignore
index bacbd88..1f7b077 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,3 +180,4 @@ thumbnails
 
 # version files for pyinstaller build
 versionfile_*.txt
+/src/ScriptFlowEditor
diff --git a/src/ScriptFlowEditor/__init__.py b/src/ScriptFlowEditor/__init__.py
new file mode 100644
index 0000000..460ddf3
--- /dev/null
+++ b/src/ScriptFlowEditor/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""
+文字 ADV 脚本流程编辑器的数据模型与（后续）视图层入口。
+
+当前仅实现数据模型层：
+- 剧情段落（节点）
+- 段落间连接条件
+- 段落连接条件相关变量（flag）
+"""
+
+from ScriptFlowEditor.models import (
+    FlagType,
+    FlagVariable,
+    StorySegment,
+    SegmentPath,
+    GameScriptFlow,
+)
+
+__all__ = [
+    "FlagType",
+    "FlagVariable",
+    "StorySegment",
+    "SegmentPath",
+    "GameScriptFlow",
+]
diff --git a/src/ScriptFlowEditor/basicflow_deserialization_test.py b/src/ScriptFlowEditor/basicflow_deserialization_test.py
new file mode 100644
index 0000000..442b758
--- /dev/null
+++ b/src/ScriptFlowEditor/basicflow_deserialization_test.py
@@ -0,0 +1,16 @@
+from pathlib import Path
+from ScriptFlowEditor import GameScriptFlow
+
+# 项目根目录（本文件在 src/ScriptFlowEditor/basicflow_deserialization_test.py）
+base = Path(__file__).resolve().parent.parent.parent
+path = base / "src" / "ScriptFlowEditor" / "generated" / "Demo Flow.json"
+flow = GameScriptFlow.load_from_json(path)
+
+print("已加载流程")
+print(f"名称: {flow.name}")
+print(f"id: {flow.id}")
+print(f"标题: {flow.title}")
+print(f"注释: {flow.comment}")
+print(f"各段落: {flow.segments}")
+print(f"各路径: {flow.paths}")
+print(f"flag变量: {flow.flags}")
\ No newline at end of file
diff --git a/src/ScriptFlowEditor/basicflow_serialization_test.py b/src/ScriptFlowEditor/basicflow_serialization_test.py
new file mode 100644
index 0000000..d9ae53d
--- /dev/null
+++ b/src/ScriptFlowEditor/basicflow_serialization_test.py
@@ -0,0 +1,37 @@
+# 基础流程及序列化保存测试
+
+from ScriptFlowEditor import *
+
+# flag变量
+first_choice = FlagVariable(name='choice_1', flag_type=FlagType.BOOL, initial_value=True, comment='The 1st choice flag.')
+second_choice = FlagVariable(name='choice_2', flag_type=FlagType.BOOL, initial_value=False, comment='The 2nd choice flag.')
+
+# start，整个流程入口
+start_node = StorySegment(name='start', content='This is start label.', is_ending_segment=False, id=None)
+
+# 分支段落1
+branch_seg_1 = StorySegment(name='branch_seg_1', content='This is 1st branch segment label.', is_ending_segment=False, id=None)
+# 分支段落2
+branch_seg_2 = StorySegment(name='branch_seg_2', content='This is 2nd branch segment label.', is_ending_segment=False, id=None)
+
+# 结局1。从branch_seg_1跳转到该结局。
+ending_1 = StorySegment(name='ending_1', content='This is ending 1.', is_ending_segment=True)
+# 结局2。在second_choice为True时，从branch_seg_2跳转到该结局。
+ending_2 = StorySegment(name='ending_2', content='This is ending 2.', is_ending_segment=True)
+# 结局3。在first_choice为False时，从branch_seg_2跳转到该结局。
+ending_3 = StorySegment(name='ending_3', content='This is ending 3.', is_ending_segment=True)
+
+# 路径1。在start_node结尾，若first_choice为True则进入branch_seg_1。
+path_1 = SegmentPath(prev_segment_id=start_node.id, next_segment_id=branch_seg_1.id, condition_expression="choice_1 == True")
+# 路径2。在start_node结尾，若first_choice为False则进入branch_seg_2。
+path_2 = SegmentPath(prev_segment_id=start_node.id, next_segment_id=branch_seg_2.id, condition_expression="choice_1 == False")
+# 路径3。在branch_seg_1结尾，无条件进入ending_1。
+path_3 = SegmentPath(prev_segment_id=branch_seg_1.id, next_segment_id=ending_1.id, condition_expression=None)
+# 路径4。在branch_seg_2结尾，若second_choice为True则进入ending_2。
+path_4 = SegmentPath(prev_segment_id=branch_seg_2.id, next_segment_id=ending_2.id, condition_expression="choice_2 == True")
+# 路径5。在branch_seg_2结尾，若second_choice为False则进入ending_3。
+path_5 = SegmentPath(prev_segment_id=branch_seg_2.id, next_segment_id=ending_3.id, condition_expression="choice_2 == False")
+
+flow = GameScriptFlow(name="Demo Flow", segments=[start_node, branch_seg_1, branch_seg_2, ending_1, ending_2, ending_3], paths=[path_1, path_2, path_3, path_4, path_5], flags=[first_choice, second_choice])
+
+flow.save_as_json()
diff --git a/src/ScriptFlowEditor/models/__init__.py b/src/ScriptFlowEditor/models/__init__.py
new file mode 100644
index 0000000..9de18ac
--- /dev/null
+++ b/src/ScriptFlowEditor/models/__init__.py
@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+"""脚本流程编辑器数据模型。"""
+
+from ScriptFlowEditor.models.flag import FlagType
+from ScriptFlowEditor.models.flag import FlagVariable
+from ScriptFlowEditor.models.segment import StorySegment
+from ScriptFlowEditor.models.path import SegmentPath
+from ScriptFlowEditor.models.gamescriptflow import GameScriptFlow
+
+
+__all__ = [
+    "FlagType",
+    "FlagVariable",
+    "StorySegment",
+    "SegmentPath",
+    "GameScriptFlow",
+]
diff --git a/src/ScriptFlowEditor/models/flag.py b/src/ScriptFlowEditor/models/flag.py
new file mode 100644
index 0000000..b4942de
--- /dev/null
+++ b/src/ScriptFlowEditor/models/flag.py
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+"""段落连接条件相关变量(flag)模型。"""
+
+from __future__ import annotations
+
+import hashlib
+from dataclasses import dataclass, field
+
+import enum
+
+
+class FlagType(enum.Enum):
+
+    """
+    flag变量类型。
+    
+    bool: 布尔值
+    int: 整数
+    float: 浮点数
+    """
+    BOOL = "bool"
+    INT = "int"
+    FLOAT = "float"
+
+
+@dataclass
+class FlagVariable:
+    """
+    控制分支走向相关变量(flag）。
+    用于在段落间连接条件中引用，具备类型与初始值。
+    """
+
+    # 变量名称，全局范围内应唯一。
+    name: str
+
+    # 变量类型：bool / int / float。入参可为 str（如 "bool"）或 FlagType，字符串会转换为枚举。
+    flag_type: str | FlagType
+
+    # 初始值，类型需与 flag_type 一致。
+    initial_value: bool | int | float
+
+    # 注释。
+    comment: str | None = None
+
+    # 唯一标识，由 name 哈希生成 8 位；不传则自动生成。
+    id: str | None = None
+
+    def __post_init__(self) -> None:
+        if self.id is None:
+            self.id = hashlib.sha256(self.name.encode()).hexdigest()[:8]
+        self._normalize_flag_type()
+        self._validate_initial_value()
+
+    # 将入参转换为 FlagType，无效则抛错。
+    def _normalize_flag_type(self) -> None:
+        if isinstance(self.flag_type, str):
+            try:
+                self.flag_type = FlagType(self.flag_type)
+            except ValueError:
+                valid = [e.value for e in FlagType]
+                raise ValueError(
+                    f"FlagVariable {self.name!r}: flag_type value is invalid {self.flag_type!r}, "
+                    f"it should be one of {valid}"
+                ) from None
+        elif not isinstance(self.flag_type, FlagType):
+            raise TypeError(
+                f"FlagVariable {self.name!r}: flag_type should be str or FlagType, "
+                f"got {type(self.flag_type).__name__}"
+            )
+
+    # 校验初始值类型是否与 flag_type 一致。
+    def _validate_initial_value(self) -> None:
+        
+        if self.flag_type == FlagType.BOOL and not isinstance(self.initial_value, bool):
+            raise TypeError(
+                f"FlagVariable {self.name!r}: flag_type is bool, "
+                f"initial_value should be bool, got {type(self.initial_value).__name__}"
+            )
+        if self.flag_type == FlagType.INT and not isinstance(self.initial_value, int):
+            raise TypeError(
+                f"FlagVariable {self.name!r}: flag_type is int, "
+                f"initial_value should be int, got {type(self.initial_value).__name__}"
+            )
+        if self.flag_type == FlagType.FLOAT and not isinstance(self.initial_value, (int, float)):
+            raise TypeError(
+                f"FlagVariable {self.name!r}: flag_type is float, "
+                f"initial_value should be int or float, got {type(self.initial_value).__name__}"
+            )
diff --git a/src/ScriptFlowEditor/models/gamescriptflow.py b/src/ScriptFlowEditor/models/gamescriptflow.py
new file mode 100644
index 0000000..dca4e37
--- /dev/null
+++ b/src/ScriptFlowEditor/models/gamescriptflow.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+"""脚本文档聚合模型：段落、连接、变量。"""
+
+from __future__ import annotations
+
+import hashlib
+import json
+from pathlib import Path
+from dataclasses import asdict, dataclass, field
+from ScriptFlowEditor.models.flag import FlagType, FlagVariable
+from ScriptFlowEditor.models.segment import StorySegment
+from ScriptFlowEditor.models.path import SegmentPath
+
+
+@dataclass
+class GameScriptFlow:
+    """
+    游戏脚本流程。
+
+    聚合所有剧情段落、段落间分支路径、以及分支路径条件相关变量(flag)，
+    构成一份完整的游戏脚本流程数据。
+    """
+    # 脚本名称，用于在编辑器中显示与引用。
+    name: str = ""
+
+    # 剧情段落（节点）列表。
+    segments: list[StorySegment] = field(default_factory=list)
+
+    # 段落间分支路径列表。
+    paths: list[SegmentPath] = field(default_factory=list)
+
+    # 分支路径条件相关变量列表。
+    flags: list[FlagVariable] = field(default_factory=list)
+
+    # 脚本流程标题（可选，用于显示）。
+    title: str = ""
+
+    # 注释。
+    comment: str | None = None
+
+    # 唯一标识，由 name 哈希生成 8 位；不传则自动生成。
+    id: str | None = None
+
+    def __post_init__(self) -> None:
+        if self.id is None:
+            self.id = hashlib.sha256(self.name.encode()).hexdigest()[:8]
+
+    # 按 id 查找剧情段落。
+    def get_segment_by_id(self, segment_id: str) -> StorySegment | None:
+        for s in self.segments:
+            if s.id == segment_id:
+                return s
+        return None
+
+    # 按 id 查找变量。
+    def get_flag_by_id(self, flag_id: str) -> FlagVariable | None:
+        for f in self.flags:
+            if f.id == flag_id:
+                return f
+        return None
+
+    # 按名称查找变量。
+    def get_flag_by_name(self, name: str) -> FlagVariable | None:
+        for f in self.flags:
+            if f.name == name:
+                return f
+        return None
+
+    # 按前置段落 id 查找分支路径。
+    def get_path_prev(self, prev_segment_id: str) -> list[SegmentPath]:
+        return [c for c in self.paths if c.prev_segment_id == prev_segment_id]
+
+    # 按后续段落 id 查找分支路径。
+    def get_path_next(self, next_segment_id: str) -> list[SegmentPath]:
+        return [c for c in self.paths if c.next_segment_id == next_segment_id]
+
+    # 序列化为可 JSON 序列化的字典（JSON 对象）。
+    def to_dict(self) -> dict:
+        return {
+            "name": self.name,
+            "id": self.id,
+            "title": self.title,
+            "comment": self.comment,
+            "segments": [asdict(s) for s in self.segments],
+            "paths": [asdict(p) for p in self.paths],
+            "flags": [self._flag_to_dict(f) for f in self.flags],
+        }
+
+    # 将 FlagVariable 转为 dict，flag_type 枚举转为字符串。
+    @staticmethod
+    def _flag_to_dict(f: FlagVariable) -> dict:
+        d = asdict(f)
+        if hasattr(f.flag_type, "value"):
+            d["flag_type"] = f.flag_type.value
+        return d
+
+    # 序列化为 JSON 字符串。
+    def to_json(self, *, indent: int | None = None, ensure_ascii: bool = False) -> str:
+        return json.dumps(self.to_dict(), indent=indent, ensure_ascii=ensure_ascii)
+
+    def _get_generated_dir(self) -> Path:
+        """返回 ScriptFlowEditor/generated 目录路径，不存在则创建。"""
+        # 本文件位于 ScriptFlowEditor/models/gamescriptflow.py
+        generated = Path(__file__).resolve().parent.parent / "generated"
+        generated.mkdir(parents=True, exist_ok=True)
+        return generated
+
+    @classmethod
+    def load_from_json(cls, path: str | Path) -> GameScriptFlow:
+        """
+        从 JSON 文件反序列化为 GameScriptFlow 对象。
+
+        :param path: JSON 文件路径，例如 src/ScriptFlowEditor/generated/Demo Flow.json
+        :return: 反序列化得到的 GameScriptFlow 实例
+        """
+        path = Path(path)
+        data = json.loads(path.read_text(encoding="utf-8"))
+        return cls.load_from_dict(data)
+
+    @classmethod
+    def load_from_dict(cls, data: dict) -> GameScriptFlow:
+        """
+        从字典反序列化为 GameScriptFlow 对象（可含额外键如 node_positions，会被忽略）。
+
+        :param data: 含 segments、paths、flags 等键的字典
+        :return: 反序列化得到的 GameScriptFlow 实例
+        """
+        segments = [cls._segment_from_dict(d) for d in data.get("segments", [])]
+        paths = [cls._path_from_dict(d) for d in data.get("paths", [])]
+        flags = [cls._flag_from_dict(d) for d in data.get("flags", [])]
+        return cls(
+            name=data.get("name", ""),
+            id=data.get("id"),
+            title=data.get("title", ""),
+            comment=data.get("comment"),
+            segments=segments,
+            paths=paths,
+            flags=flags,
+        )
+
+    @staticmethod
+    def _segment_from_dict(d: dict) -> StorySegment:
+        return StorySegment(
+            name=d["name"],
+            content=d["content"],
+            is_ending_segment=d.get("is_ending_segment", True),
+            id=d.get("id"),
+            comment=d.get("comment", ""),
+            paths_segment_ids=d.get("paths_segment_ids", {}),
+        )
+
+    @staticmethod
+    def _path_from_dict(d: dict) -> SegmentPath:
+        return SegmentPath(
+            prev_segment_id=d["prev_segment_id"],
+            next_segment_id=d["next_segment_id"],
+            condition_expression=d.get("condition_expression"),
+            name=d.get("name"),
+            comment=d.get("comment"),
+            id=d.get("id"),
+        )
+
+    @staticmethod
+    def _flag_from_dict(d: dict) -> FlagVariable:
+        return FlagVariable(
+            name=d["name"],
+            flag_type=d["flag_type"],
+            initial_value=d["initial_value"],
+            comment=d.get("comment"),
+            id=d.get("id"),
+        )
+
+    # 序列化为 JSON 并保存到 ScriptFlowEditor/generated 目录下的 .json 文件。
+    def save_as_json(
+        self,
+        filename: str | None = None,
+        *,
+        indent: int | None = 2,
+        ensure_ascii: bool = False,
+    ) -> Path:
+        """
+        将当前对象序列化为 JSON 并保存到 src/ScriptFlowEditor/generated 目录。
+
+        :param filename: 文件名（可含 .json 后缀）；为 None 时用 name 或 id，不含则自动加 .json。
+        :param indent: 传给 json.dumps 的缩进，默认 2。
+        :param ensure_ascii: 是否转义非 ASCII，默认 False 以保留中文。
+        :return: 保存后的文件路径。
+        """
+        out_dir = self._get_generated_dir()
+        if not filename:
+            base = (self.name or self.id or "gamescriptflow").replace("/", "_").replace("\\", "_").strip(". ") or "flow"
+            filename = f"{base}.json" if not base.lower().endswith(".json") else base
+        elif not filename.lower().endswith(".json"):
+            filename = f"{filename}.json"
+        path = out_dir / filename
+        path.write_text(self.to_json(indent=indent, ensure_ascii=ensure_ascii), encoding="utf-8")
+        return path
diff --git a/src/ScriptFlowEditor/models/path.py b/src/ScriptFlowEditor/models/path.py
new file mode 100644
index 0000000..b5e9d05
--- /dev/null
+++ b/src/ScriptFlowEditor/models/path.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+"""段落间连接路径模型。"""
+
+from __future__ import annotations
+
+import hashlib
+from dataclasses import dataclass, field
+
+@dataclass
+class SegmentPath:
+    """
+    段落间分支路径。
+    满足条件表达式时，可以通往目标段落。
+    """
+
+    # 前置剧情段落id。
+    prev_segment_id: str
+
+    # 后续剧情段落id。
+    next_segment_id: str
+
+    # 条件表达式或模式字符串，为空表示无条件。
+    condition_expression: str|None = None
+
+    # 路径名称，用于在编辑器中显示与引用。为空时按源与目标段落id拼接。
+    name: str | None = None
+
+    # 注释。
+    comment: str | None = None
+
+    # 唯一标识，由 name 哈希生成 8 位；不传则自动生成。
+    id: str | None = None
+
+    def __post_init__(self) -> None:
+        if self.name is None or self.name == "":
+            self.name = f"{self.prev_segment_id}_to_{self.next_segment_id}_path"
+        if self.id is None or self.id == "":
+            self.id = hashlib.sha256(self.name.encode()).hexdigest()[:8]
+
diff --git a/src/ScriptFlowEditor/models/segment.py b/src/ScriptFlowEditor/models/segment.py
new file mode 100644
index 0000000..abca548
--- /dev/null
+++ b/src/ScriptFlowEditor/models/segment.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+"""剧情段落(节点)模型。"""
+
+from __future__ import annotations
+
+import hashlib
+from dataclasses import dataclass, field
+
+
+@dataclass
+class StorySegment:
+    """
+    剧情段落(节点)。
+    表示一段可播放的剧情内容，具备名称、正文以及是否为终点段落的属性。
+    """
+
+    # 段落名称，用于在编辑器中显示。
+    name: str
+
+    # 段落正文内容(脚本文本)。
+    content: str
+
+    # 是否为终点段落；默认为 True。如果为 False，则有后继连接。 如果为 True，则没有后继连接。
+    is_ending_segment: bool = True
+
+    # 唯一标识，由 name 哈希生成 8 位；不传则自动生成。
+    id: str | None = None
+
+    # 注释。
+    comment: str = ""
+
+    # 分支路径id与后续段落id字典。
+    paths_segment_ids: dict[str, str] = field(default_factory=dict)
+
+    def __post_init__(self) -> None:
+        if self.id is None:
+            self.id = hashlib.sha256(self.name.encode()).hexdigest()[:8]
+
+    def add_path_segment_id(self, path_id: str, segment_id: str) -> None:
+        self.paths_segment_ids[path_id] = segment_id
diff --git a/src/fgui_converter/FguiAssetsParseLib.py b/src/fgui_converter/FguiAssetsParseLib.py
index 248d12a..cf41658 100644
--- a/src/fgui_converter/FguiAssetsParseLib.py
+++ b/src/fgui_converter/FguiAssetsParseLib.py
@@ -8,19 +8,20 @@
 class FguiPackage():
     """
     FairyGUI资源包中的Package描述内容。
-    包含component列表、image列表和atlas列表。
+    包含component列表、image列表、atlas列表和音频文件列表。
     """
     id = ''
     name = ''
     component_list = []
     image_list = []
     atlas_list = []
+    sound_list = []
 
     class brief_component:
         id = ''
         name = ''
         path = ''
-        size = ()
+        size = None # tuple(width, height)
         exported = True
         def __init__(self, id, name, path, size, exported=True):
             self.id = id
@@ -34,7 +35,7 @@ class brief_image:
         id = ''
         name = ''
         path = ''
-        size = ()
+        size = None # tuple(width, height)
         scale = ''  # 九宫格：9grid；平铺：tile
         scale9grid = []
         exported = True
@@ -59,6 +60,17 @@ def __init__(self, id, size, file):
             self.size = (int(size_list[0]), int(size_list[1]))
             self.file = file
 
+    class brief_sound:
+        id = ''
+        name = ''
+        path = ''
+        file = ''
+        def __init__(self, id, name, path, file):
+            self.id = id
+            self.name = name
+            self.path = path
+            self.file = file
+
     def __init__(self, package_etree):
         self.package_etree = package_etree
         self.id = package_etree.get("id")
@@ -79,6 +91,7 @@ def __init__(self, package_etree):
                                                             exported=TransStrToBoolean(child.get('exported'))))
                 self.id_name_mapping[child.get('id')] = child.get('name')
                 self.id_size_mapping[child.get('id')] = tuple(map(int, child.get('size').split(",")))
+                continue
             # image类 name, path, size, scale=None, scale9grid=[], exported=True
             if (child.tag == 'image'):
                 self.image_list.append(self.brief_image(
@@ -91,12 +104,22 @@ def __init__(self, package_etree):
                                                     exported=TransStrToBoolean(child.get('exported'))))
                 self.id_name_mapping[child.get('id')] = child.get('name')
                 self.id_size_mapping[child.get('id')] = tuple(map(int, child.get('size').split(",")))
+                continue
             # atlas类 id, size, file
             if (child.tag == 'atlas'):
                 self.atlas_list.append(self.brief_atlas(
                                                 child.get('id'),
                                                 child.get('size'),
                                                 child.get('file')))
+                continue
+            # sound类 id, name, path, file
+            if (child.tag == 'sound'):
+                self.sound_list.append(self.brief_sound(
+                                                child.get('id'),
+                                                child.get('name'),
+                                                child.get('path'),
+                                                child.get('file')))
+                continue
 
     def clear(self):
         self.component_list.clear()
@@ -235,11 +258,15 @@ class FguiController:
     """
      FairyGUI控制器类
      name：控制器名称，字符串，样例"button"
-     page：索引、索引名，字符串，样例"0,up,1,down,2,over,3,selectedOver"
-     selected：初始索引号，字符串 ，样例"0"
+     homepage_type：首页类型，None或字符串。None-根据selected确定首页；"specific"-指定索引；"variable"-根据变量名查找指定索引。
+     homepage：首页索引，None或字符串，可能是数字字符串或文本字符串。
+     page：索引、索引名，字符串，样例"0,up,1,down,2,over,3,selectedOver"。
+     selected：初始索引号，字符串 ，样例"0"。
     """
-    def __init__(self, name, page, selected):
+    def __init__(self, name, homepage_type, homepage, page, selected):
         self.name = name
+        self.homepage_type = homepage_type
+        self.homepage = homepage
         self.page_index_dict = {}
         page_list = page.split(',')
         page_num = len(page_list)
@@ -277,6 +304,9 @@ def __init__(self, component_etree, id, name, package_desc=None):
         self.package_desc = package_desc
         size = component_etree.get("size")
         self.size = tuple(map(int, size.split(","))) if size else (0,0)
+        # 限制尺寸，4元组，分别为最小宽度、最大宽度、最小高度、最大高度。默认值为0，表示与当前实际尺寸一致。
+        restrict_size = component_etree.get("restrictSize", "0,0,0,0")
+        self.min_width, self.max_width, self.min_height, self.max_height = tuple(map(int, restrict_size.split(",")))
         self.overflow = component_etree.get("overflow", "visible")
         self.scroll = component_etree.get("scroll", "vertical")
         self.extention = component_etree.get("extention")
@@ -304,7 +334,7 @@ def __init__(self, component_etree, id, name, package_desc=None):
         for i in range(self.child_num):
             # 控制器
             if (self.component_etree[i].tag == "controller"):
-                self.controller_list.append(FguiController(self.component_etree[i].get("name"), self.component_etree[i].get("pages"), self.component_etree[i].get("selected")))
+                self.controller_list.append(FguiController(self.component_etree[i].get("name"), self.component_etree[i].get("homePageType"), self.component_etree[i].get("homePage"), self.component_etree[i].get("pages"), self.component_etree[i].get("selected")))
             # 显示内容
             elif (self.component_etree[i].tag == "displayList"):
                 self.display_list = FguiDisplayList(self.component_etree[i], self.package_desc)
@@ -323,24 +353,35 @@ def __repr__(self):
 class FguiButton(FguiComponent):
     """
     FairyGUI中的按钮button。
-    相比其他component，多一个Button标签
+    相比其他component，多一个Button标签。
+    mode：模式。Radio-单选按钮；Check-复选按钮；None-普通按钮。
+    sound：点击音效。格式为“ui://”+“packageDescription id” + “sound id”。
+    downEffect：按下效果。scale-缩放，dark-变暗。
+    downEffectValue：按下效果值。缩放值，变暗值。
     """
     def __init__(self, component_etree, id, name, package_desc=None):
         super().__init__(component_etree, id, name, package_desc=package_desc)
         button = component_etree.find("Button")
         self.button_mode = button.get("mode")
+        sound = button.get("sound")
+        if sound:
+            # 格式为“ui://”+“packageDescription id” + “sound id”。
+            self.button_sound = sound[sound.find(package_desc.id)+len(package_desc.id):]
+        else:
+            self.button_sound = None
         self.button_down_effect = button.get("downEffect")
         self.button_down_effect_value = button.get("downEffectValue")
 
 class FguiScrollBar(FguiComponent):
     """
     FairyGUI中的滚动条scrollbar。
-    相比其他component，多一个ScrollBar标签，大部分情况为空。
+    相比其他component，多一个ScrollBar标签，只可能有一个属性fixedGripSize，表示滑块是否固定尺寸。
     通常滚动条都会有以个对应的其他组件，为同名带后缀“_grip”的按钮。
     """
     def __init__(self, component_etree, id, name, package_desc=None):
         super().__init__(component_etree, id, name, package_desc=package_desc)
         scrollbar = component_etree.find("ScrollBar")
+        self.fixed_grip_size = scrollbar.get("fixedGripSize")
 
 class FguiLabel(FguiComponent):
     """
@@ -365,16 +406,17 @@ def __init__(self, component_etree, id, name, package_desc=None):
 class FguiProgressBar(FguiComponent):
     """
     FairyGUI中的进度条。
-    相比其他component，多一个ProgressBar标签，大部分情况为空。
+    被其他组件引用时，相比其他component，多一个ProgressBar标签，包含最小值、最大值与当前值。
     """
     def __init__(self, component_etree, id, name, package_desc=None):
         super().__init__(component_etree, id, name, package_desc=package_desc)
         self.progressbar = component_etree.find("ProgressBar")
+        self.title_type = self.progressbar.get("titleType")
 
 class FguiSlider(FguiComponent):
     """
     FairyGUI中的滑动条。
-    相比其他component，多一个Slider标签，大部分情况为空。
+    被其他组件引用时，相比其他component，多一个Slider标签，包含最小值、最大值与当前值。
     滑动条会有一个相应的其他组件，同名带后缀“_grip”的按钮。
     """
     def __init__(self, component_etree, id, name, package_desc=None):
@@ -522,7 +564,7 @@ def __init__(self, display_item_tree, package_desc=None):
         # 轴心。默认值为(0.0, 0.0)。
         pivot = self.display_item_tree.get("pivot", "0.0,0.0")
         self.pivot = tuple(map(float, pivot.split(",")))
-        # 是否将轴心作为锚点。否认为False。
+        # 是否将轴心作为锚点。默认为False。
         self.pivot_is_anchor = (self.display_item_tree.get("anchor") == "true")
         # 不透明度。默认为1.0。
         alpha = self.display_item_tree.get("alpha", "1.0")
@@ -554,6 +596,9 @@ def __init__(self, display_item_tree, package_desc=None):
         # Button，按钮专有属性
         self.button_property = None
 
+        # ProgressBar，进度条专有属性
+        self.progressbar_property = None
+
         # Slider，滑动条专有属性
         self.slider_property = None
 
@@ -596,6 +641,10 @@ def __init__(self, display_item_tree, package_desc=None):
                 self.relations = FguiRelation(self.display_item_tree[i])
             elif self.display_item_tree[i].tag == "Slider" :
                 self.slider_property = FguiSliderProperty(self.display_item_tree[i])
+            elif self.display_item_tree[i].tag == "ProgressBar" :
+                self.progressbar_property = FguiProgressBarProperty(self.display_item_tree[i])
+            elif self.display_item_tree[i].tag == "Label" :
+                self.label_property = FguiLabelProperty(self.display_item_tree[i])
             else:
                 print(f"Tag not parse: {self.display_item_tree[i].tag}.")
 
@@ -631,6 +680,18 @@ def __init__(self, component_property_tree):
         self.controller_index = self.component_property_tree.get("page")
         # TODO 待添加点击音效
 
+class FguiLabelProperty(FguiComponentPropertyBase):
+    """
+    组件子属性中的Label信息。
+    """
+    def __init__(self, component_property_tree):
+        super().__init__(component_property_tree)
+        if self.property_name != "Label" :
+            raise ValueError("xml tag is not Label")
+        self.title = self.component_property_tree.get("title")
+        self.text_color = self.component_property_tree.get("titleColor")
+        self.font_size = int(self.component_property_tree.get("titleFontSize", 24))
+
 class FguiGraph(FguiDisplayable):
     """
     FairyGUI中的图形。包括空白、矩形(圆边矩形)、圆形(椭圆)、多边形等。
@@ -646,8 +707,18 @@ def __init__(self, display_item_tree, package_desc=None):
         self.stroke_color  = hex_aarrggbb_to_rgba(stroke_color)
         fill_color = self.display_item_tree.get("fillColor", "#ffffffff") # 描边默认为白色
         self.fill_color = hex_aarrggbb_to_rgba(fill_color)
-        # 矩形可能存在圆角
-        self.corner_radius = int(self.display_item_tree.get("corner", "0"))
+        # 矩形可能存在圆角。corner 可能为逗号分隔的多个整数，转为 4 元组（不足补 0，多于 4 个舍弃）。
+        corner_raw = self.display_item_tree.get("corner")
+        if corner_raw is None:
+            self.corner_radius = (0, 0, 0, 0)
+        else:
+            segments = corner_raw.split(",")
+            # 仅当逗号分隔后真的只有一段（如 "25"）才复制为四元组；若有多段（如 "25," 得 ["25",""]）则按多段用 0 填充
+            if len(segments) == 1 and segments[0].strip():
+                self.corner_radius = (int(segments[0].strip()),) * 4
+            else:
+                parts = [int(x.strip()) if x.strip() else 0 for x in segments]
+                self.corner_radius = tuple((parts + [0] * 4)[:4])
         # 正多边形需要记录边数和顶点位置。
         # 顶点位置使用一个数组表示，数组长度等于顶点数(边数)。
         # 顶点只能存在于标准正多边形顶点到图形中心的连线上。
@@ -756,6 +827,14 @@ def __init__(self, list_item_tree, package_description_id=None):
         self.item_title = self.list_item_tree.get("title")
         self.item_icon = self.list_item_tree.get("icon")
         self.item_name = self.list_item_tree.get("name")
+        self.item_id = None
+        self.get_item_id_by_url()
+
+    def get_item_id_by_url(self):
+        if self.item_url and self.package_description_id:
+            self.item_id = self.item_url[self.item_url.find(self.package_description_id)+len(self.package_description_id):]
+        return
+
     def __repr__(self):
         return f"FguiListItem({self.item_url}, {self.item_title}, {self.item_icon}, {self.item_name})"
 
@@ -771,17 +850,17 @@ class FguiList(FguiDisplayable):
     scrollBar-显示滚动条：visible-可见，hidden-隐藏，auto-滚动时显示
     scrollBarRes-滚动条资源url，格式为“ui://”+“packageDescription id” + “component id”。
     scrollBarFlags：一系列滚动条标识位，可能是一个12bit整数。从低位到高位分别为：
-        0-bit：垂直滚动条显示在左边
-        1-bit：滚动位置自动贴近元件
-        2-bit：仅在内容溢出时才显示滚动条
-        3-bit：页面模式
-        4-bit 5-bit：触摸滚动效果，00默认、01启用、10关闭
-        6-bit 7-bit：边缘回弹效果，00默认、01启用、10关闭
-        8-bit：禁用惯性
-        9-bit：禁用剪裁
-        10-bit：浮动显示
-        11-bit：禁用剪裁边缘
-    当列表允许滚动时，有一大堆特性暂不处理，例如“边缘回弹”、指定滚动条组件、自动贴近元件等。
+        0-bit：垂直滚动条显示在左边，1-是，0-否。
+        1-bit：滚动位置自动贴近元件，1-是，0-否。
+        2-bit：仅在内容溢出时才显示滚动条，1-是，0-否。
+        3-bit：页面模式，1-是，0-否。
+        4-bit 5-bit：触摸滚动效果，00-默认、01-启用、10-关闭。
+        6-bit 7-bit：边缘回弹效果，00-默认、01-启用、10-关闭。
+        8-bit：禁用惯性，1-是，0-否。
+        9-bit：禁用剪裁，1-是，0-否。
+        10-bit：浮动显示，1-是，0-否。
+        11-bit：禁用剪裁边缘，1-是，0-否。
+    当列表允许滚动时，有一大堆特性暂不处理，例如“边缘回弹”、自动贴近元件等。
     margin：边缘留空，4个整数，分别对应上下左右。
     clipSoftness：边缘虚化，xy分辨对应水平与垂直方向的虚化程度。
     lineItemCount：列表布局为横向流动或分页时，表示列数。列表布局为竖向流动时，表示行数。其他布局中，该参数无效果。
@@ -799,7 +878,8 @@ def __init__(self, display_item_tree, package_desc=None):
         self.scroll = self.display_item_tree.get("scroll", "vertical")
         scrollbar_res = self.display_item_tree.get("scrollBarRes")
         self.scrollbar_res = self.get_scrollbar_res(scrollbar_res, package_desc.id)
-        self.scroll_bar_flags = int(self.display_item_tree.get("scrollBarFlags", "256"))
+        self.scroll_bar_flags = int(self.display_item_tree.get("scrollBarFlags", "0"))
+        self.scrollbar_flags_dict = self.get_scrollbar_flags_dict()
         margin = self.display_item_tree.get("margin")
         self.margin = tuple(map(int, margin.split(","))) if margin else (0,0,0,0)
         self.clip_softness = self.display_item_tree.get("clipSoftness", "0,0")
@@ -819,7 +899,9 @@ def __init__(self, display_item_tree, package_desc=None):
         self.item_list_length = 0
 
     def get_default_item(self, packageDescription_id):
-        self.default_item_id = self.default_item_url[self.default_item_url.find(packageDescription_id)+len(packageDescription_id):]
+        if self.default_item_url:
+            self.default_item_id = self.default_item_url[self.default_item_url.find(packageDescription_id)+len(packageDescription_id):]
+        return
 
     def set_item_list_length(self, item_list_length):
         self.item_list_length = item_list_length
@@ -840,11 +922,52 @@ def get_scrollbar_res(self, scrollbar_res, packageDescription_id):
         else:
             return None
 
+    def get_scrollbar_flags_dict(self):
+        if self.scroll_bar_flags:
+            scrollbar_flags_list = []
+            for i in range(12):
+                scrollbar_flags_list.append((self.scroll_bar_flags & (1 << i)) >> i)
+            """
+            0-bit：垂直滚动条显示在左边，1-是，0-否。
+            1-bit：滚动位置自动贴近元件，1-是，0-否。
+            2-bit：仅在内容溢出时才显示滚动条，1-是，0-否。
+            3-bit：页面模式，1-是，0-否。
+            4-bit 5-bit：触摸滚动效果，00-默认、01-启用、10-关闭。
+            6-bit 7-bit：边缘回弹效果，00-默认、01-启用、10-关闭。
+            8-bit：禁用惯性，1-是，0-否。
+            9-bit：禁用剪裁，1-是，0-否。
+            10-bit：浮动显示，1-是，0-否。
+            11-bit：禁用剪裁边缘，1-是，0-否。
+            """
+            scrollbar_flags_dict = {
+            "display_on_left": scrollbar_flags_list[0],
+            "snapping_to_item": scrollbar_flags_list[1],
+            "hide_when_not_overflow": scrollbar_flags_list[2],
+            "page_mode": scrollbar_flags_list[3],
+            "touch_scroll": (scrollbar_flags_list[4] << 1) | (scrollbar_flags_list[5]),
+            "bounds_back": (scrollbar_flags_list[6] << 1) | (scrollbar_flags_list[7]),
+            "inertia_disabled": scrollbar_flags_list[8],
+            "mask_disabled": scrollbar_flags_list[9],
+            "floating_display": scrollbar_flags_list[10],
+            "dont_clip_margin": scrollbar_flags_list[11],
+        }
+            return scrollbar_flags_dict
+        return None
 
 class FguiLoader(FguiDisplayable):
     """
     FairyGUI中的装载器。
     包含属性url：表示引用的组件url，通常是一个资源url，格式为“ui://”+“packageDescription id” + “component id”。
+    autoSize：是否自动调整尺寸。
+    Fill：填充方式。None-(无)，"scale"-等比缩放(显示全部)，"scaleNoBorder"-等比缩放(无边框)，"scaleMatchHeight"-等比缩放(适应高度)，"scaleMatchWidth"-等比缩放(适应宽度)， "scaleFree"-自由缩放。
+    shrinkOnly：是否仅允许缩小。
+    align：水平对齐方式。"left"-左对齐，"center"-居中对齐，"right"-右对齐。
+    vAlign：垂直对齐方式。"top"-上对齐，"middle"-居中对齐，"bottom"-下对齐。
+    frame：动画当前帧。若使用图片则无用处。
+    color-颜色：一个6位Hex字符串，表示显示时所有像素的RGA都要乘以该值。
+    fillMethod-填充方式："hz"-水平、"vt"-垂直、"radial90"-90度、"radial180"-180度、"radial360"-360度。
+    fillOrigin-填充原点：0(默认值)、1、2、3。该值根据不同的填充方式有不同的含义。
+    fillAmount-填充比例：100(默认值)，一个介于0到100之间的整数。
     """
     def __init__(self, display_item_tree, package_desc=None):
         if display_item_tree.tag != "loader" :
@@ -855,6 +978,16 @@ def __init__(self, display_item_tree, package_desc=None):
         if package_desc and self.url:
             self.package_description_id = package_desc.id
             self.get_item_id(package_desc.id)
+        self.auto_size = self.display_item_tree.get("autoSize", False)
+        self.fill_type = self.display_item_tree.get("fill")
+        self.shrink_only = self.display_item_tree.get("shrinkOnly", False)
+        self.align = self.display_item_tree.get("align", "left")
+        self.v_align = self.display_item_tree.get("vAlign", "top")
+        self.frame = int(self.display_item_tree.get("frame", "0"))
+        self.color = self.display_item_tree.get("color")
+        self.fill_method = self.display_item_tree.get("fillMethod")
+        self.fill_origin = self.display_item_tree.get("fillOrigin")
+        self.fill_amount = self.display_item_tree.get("fillAmount")
 
     def get_item_id(self, packageDescription_id):
         self.item_url = self.url[self.url.find(packageDescription_id)+len(packageDescription_id):]
@@ -872,7 +1005,19 @@ def __init__(self, relation_item_tree):
         key = self.relation_item_tree.get("target")
         value = self.relation_item_tree.get("sidePair")
         self.relation_dict[key] = value
-        # print(self.relation_dict)
+
+class FguiProgressBarProperty:
+    """
+    进度条的数值属性。分别包含最小值、最大值与当前值。
+    如果某一项属性未出现则等于默认值0。
+    """
+    def __init__(self, progressbar_property_tree):
+        if progressbar_property_tree.tag != "ProgressBar":
+            raise ValueError("xml tag is not ProgressBar.")
+        self.progressbar_property_tree = progressbar_property_tree
+        self.current_value = self.progressbar_property_tree.get("value", 0)
+        self.min_value = self.progressbar_property_tree.get("min", 0)
+        self.max_value = self.progressbar_property_tree.get("max", 0)
 
 class FguiSliderProperty:
     """
@@ -1095,7 +1240,8 @@ def __init__(self, fgui_project_path):
         self.fgui_atlas_dicts = {}
         # 组件信息
         self.fgui_component_set = []
-
+        # 音频文件信息
+        self.fgui_sound_dicts = {}
         # 先找到packageDescription，解析出component、image和atlas列表
         package_key = 'package'
         if (not self.object_dict.__contains__(package_key)):
@@ -1118,6 +1264,8 @@ def __init__(self, fgui_project_path):
                 component = FguiLabel(self.object_dict[component.id], component.id, component.name, package_desc=self.package_desc)
             elif extention_type == "Slider":
                 component = FguiSlider(self.object_dict[component.id], component.id, component.name, package_desc=self.package_desc)
+            elif extention_type == "ProgressBar":
+                component = FguiProgressBar(self.object_dict[component.id], component.id, component.name, package_desc=self.package_desc)
             else:
                 component = FguiComponent(self.object_dict[component.id], component.id, component.name, package_desc=self.package_desc)
             self.fgui_component_set.append(component)
@@ -1127,6 +1275,10 @@ def __init__(self, fgui_project_path):
             self.fgui_atlas_dicts[atlas.id] = atlas_file_name
         # 若不需要从atlas切割出单个图片，可以结合 *@sprites.bytes文件，获取每个image对象
         self.fgui_image_set = ParseFguiSpriteDescFile(self.sprite_desc_file)
+        # 根据sound_list建立sound_id与实际音频文件间的映射关系
+        for sound in self.package_desc.sound_list:
+            sound_file_name = self.fgui_project_name + '@' + sound.file
+            self.fgui_sound_dicts[sound.id] = sound_file_name
 
     def clear(self):
         self.fgui_project_name = ''
@@ -1140,7 +1292,10 @@ def clear(self):
         self.fgui_atlas_dicts.clear()
 
     def get_componentname_by_id(self, id : str) -> str:
-        return self.package_desc.id_name_mapping[id]
+        if id in self.package_desc.id_name_mapping:
+            return self.package_desc.id_name_mapping[id]
+        else:
+            return None
 
     def get_component_by_id(self, id : str) -> FguiComponent:
         for component in self.fgui_component_set:
@@ -1149,9 +1304,6 @@ def get_component_by_id(self, id : str) -> FguiComponent:
         return None
 
     def get_image_size_by_id(self, id : str):
-        # for image in self.fgui_image_set:
-        #     if image.image_id == id:
-        #         return (image.width, image.height)
         return self.package_desc.id_size_mapping[id]
 
     def __del__(self):
diff --git a/src/fgui_converter/utils/renpy/Fgui2RenpyConverter.py b/src/fgui_converter/utils/renpy/Fgui2RenpyConverter.py
index 8be067f..191b702 100644
--- a/src/fgui_converter/utils/renpy/Fgui2RenpyConverter.py
+++ b/src/fgui_converter/utils/renpy/Fgui2RenpyConverter.py
@@ -25,6 +25,81 @@ class BarOrientationType(IntEnum):
     HORIZONTAL = 0
     VERTICAL = 1
 
+
+class MainMenuTitle:
+    """主菜单标题：由 --main-menu-title 的键值对解析得到。"""
+    __slots__ = ('text_str', 'text_color')
+
+    def __init__(self, text_str: str | None = None, text_color: str | None = None):
+        self.text_str = text_str
+        self.text_color = text_color
+
+
+# 颜色名称到十六进制值的映射，用于 --main-menu-title text_color=名称
+COLOR_NAME_TO_HEX = {
+    'red': '#ff0000',
+    'green': '#00ff00',
+    'blue': '#0000ff',
+    'white': '#ffffff',
+    'black': '#000000',
+    'yellow': '#ffff00',
+    'cyan': '#00ffff',
+    'magenta': '#ff00ff',
+    'orange': '#ffa500',
+    'gray': '#808080',
+    'grey': '#808080',
+    'silver': '#c0c0c0',
+    'maroon': '#800000',
+    'lime': '#00ff00',
+    'olive': '#808000',
+    'navy': '#000080',
+    'purple': '#800080',
+    'teal': '#008080',
+}
+
+
+def is_valid_hex_color(color: str | None) -> bool:
+    """
+    校验颜色值是否合法：支持以下两种形式
+    1) 以 # 开头，后跟 3 位或 6 位十六进制数字，如 #f00、#ff0000；
+    2) 预定义颜色名称，如 red、green、blue（见 COLOR_NAME_TO_HEX）。
+    空字符串或 None 视为不合法。
+    """
+    if not color or not isinstance(color, str):
+        return False
+    color = color.strip()
+    if re.fullmatch(r'#[0-9A-Fa-f]{3}(?:[0-9A-Fa-f]{3})?', color):
+        return True
+    return color.lower() in COLOR_NAME_TO_HEX
+
+
+def _parse_main_menu_title_kv(pairs):
+    """
+    将 --main-menu-title key1=val1 key2=val2 解析为 MainMenuTitle。
+    pairs: 由 argparse 解析得到的字符串列表，如 ['text_str=标题', 'text_color=#ff0000']。
+    返回 MainMenuTitle 或 None（当 pairs 为空或 None 时）。
+    """
+    if not pairs:
+        return None
+    d = {}
+    for s in pairs:
+        if '=' in s:
+            k, _, v = s.partition('=')
+            d[k.strip()] = v.strip()
+    if not d:
+        return None
+    # 未提供的键为 None，赋值时保持 displayable 原值不变
+    text_str = d.get('text_str') if 'text_str' in d else d.get('text')
+    text_color = d.get('text_color') if 'text_color' in d else d.get('color')
+    if text_color is not None and not is_valid_hex_color(text_color):
+        print(f"警告: text_color 格式无效（需 # 加 3/6 位十六进制或预定义颜色名），已忽略: {text_color!r}")
+        text_color = None
+    elif text_color is not None:
+        # 颜色名解析为十六进制，便于后续使用
+        text_color = COLOR_NAME_TO_HEX.get(text_color.strip().lower(), text_color)
+    return MainMenuTitle(text_str=text_str, text_color=text_color)
+
+
 # 添加当前目录到Python路径，以便导入FguiAssetsParseLib
 sys.path.append(os.path.dirname(os.path.abspath(__file__)))
 
@@ -76,6 +151,10 @@ class FguiToRenpyConverter:
     # 主管线提供的gallery数据文件
     ui_helper_file_name = '01_ui_helper.rpy'
 
+    # 自定义参数，用于替换Fgui发布资源中的值。
+    main_menu_title = None
+    main_menu_logo = None
+
     def __init__(self, fgui_assets):
         self.fgui_assets = fgui_assets
         self.renpy_code = []
@@ -87,6 +166,7 @@ def __init__(self, fgui_assets):
         self.style_code = []
         self.image_definition_code = []
         self.graph_definition_code = []
+        self.sound_definition_code = []
         self.game_global_variables_code = []
         self.gallery_screen_code = []
         self.music_room_screen_code = []
@@ -123,11 +203,13 @@ def __init__(self, fgui_assets):
         self.style_preset_template = 'renpy_style_preset_template.txt'
         self.style_preset = self.get_template_content(self.style_preset_template)
         self.list_screen_template = self.get_template_content('renpy_list_screen_template.txt')
+        self.loader_screen_template = self.get_template_content('renpy_loader_screen_template.txt')
 
         # 输出游戏目录
         self.game_dir = None
         self.scripts_dir = None
         self.images_dir = None
+        self.audio_dir = None
 
         # gallery相关数据
         self.gallery_data = {}
@@ -135,11 +217,30 @@ def __init__(self, fgui_assets):
         # 一些样式预设，用于覆盖Ren'Py默认样式。
         self.style_code.append(self.style_preset)
 
-    def set_game_global_variables(self, variable_name, variable_value):
+    def set_game_global_variables(self, variable_name : str, variable_value : any):
+        """
+        设置游戏全局变量。
+        使用define语句修改内置变量。
+        variable_name: 变量名。
+        variable_value: 变量值。
+        """
+        variable_value = str(variable_value)
         variable_str = f"define {variable_name} = {variable_value}"
         self.game_global_variables_code.append(variable_str)
         self.game_global_variables_code.append('')
 
+    def add_game_global_variables(self, variable_name : str, variable_value : any):
+        """
+        添加游戏全局变量。
+        使用default语句添加自定义变量。
+        variable_name: 变量名。
+        variable_value: 变量值。
+        """
+        variable_value = str(variable_value)
+        variable_str = f"default {variable_name} = {variable_value}"
+        self.game_global_variables_code.append(variable_str)
+        self.game_global_variables_code.append('')
+
     def calculate_indent(self):
         self.indent_str = self.indent_unit * self.root_indent_level
         return self.indent_str
@@ -289,6 +390,112 @@ def generate_graph_definitions(self, fgui_graph : FguiGraph, component_name : st
 
         return graph_code
 
+    def generate_sound_definitions(self):
+        """生成音频定义"""
+        sound_definitions = []
+        sound_definitions.append("# 音频定义")
+        sound_definitions.append("# 根据FairyGUI发布资源中音频文件列表定义音频变量")
+        for id, file in self.fgui_assets.fgui_sound_dicts.items():
+            sound_definitions.append(f"define audio.{id} = '{file}'")
+        sound_definitions.append("")
+        self.sound_definition_code.extend(sound_definitions)
+
+    def generate_progressbar_style(self, fgui_progressbar : FguiProgressBar) -> None:
+        """
+        生成进度条样式。
+        目标样例：
+        image horizontal_idle_bar_image:
+            Frame("horizontal_progressbar_base")
+
+        image horizontal_hover_bar_image:
+            Frame("horizontal_progressbar_active")
+        style horizontal_progressbar:
+            xsize 100
+            ysize 10
+            left_bar "horizontal_hover_bar_image"
+            right_bar "horizontal_idle_bar_image"
+        """
+        bar_image_definition_code = []
+        style_definition_code = []
+        progressbar_style_code = []
+        if not isinstance(fgui_progressbar, FguiProgressBar):
+            print("It is not a progressbar.")
+            return
+        
+        # 默认为水平进度条。
+        progressbar_type = BarOrientationType.HORIZONTAL
+        # bar的第一段，水平方向为right_bar。
+        first_bar_name = ''
+        # bar的第二段，水平方向为left_bar。
+        second_bar_name = ''
+
+        # progressbar 组件固定由两张图片(图形)构成。
+        # 图片(图形)固定名称为n0和bar。
+        # 其他组件暂不处理。
+        for displayable in fgui_progressbar.display_list.displayable_list:
+            # FGUI中进度条的背景
+            if displayable.name == 'n0':
+                if isinstance(displayable, FguiImage):
+                    second_bar_name = self.fgui_assets.get_componentname_by_id(displayable.src)
+                elif isinstance(displayable, FguiGraph):
+                    bar_image_definition_code.extend(self.generate_graph_definitions(displayable, fgui_progressbar.name))
+                    second_bar_name = f"{fgui_progressbar.name}_{displayable.id}"
+                else:
+                    print("Slider base is neither image nor graph.")
+                    return
+            # FGUI中水平滑动条的可变bar部分
+            if displayable.name == 'bar':
+                if isinstance(displayable, FguiImage):
+                    first_bar_name = self.fgui_assets.get_componentname_by_id(displayable.src)
+                elif isinstance(displayable, FguiGraph):
+                    bar_image_definition_code.extend(self.generate_graph_definitions(displayable, fgui_progressbar.name))
+                    first_bar_name = f"{fgui_progressbar.name}_{displayable.id}"
+                else:
+                    print("Slider bar is neither image nor graph.")
+                    return
+                bar_id = displayable.id
+                continue
+            # FGUI中滑动条的标题类型文本
+            if displayable.name == 'title':
+                if isinstance(displayable, FguiText):
+                    style_name = f"slider_{fgui_progressbar.id}_title"
+                    self.generate_text_style(displayable, style_name)
+                    # 暂时不生成标题文本
+                    continue
+                else:
+                    print("ProgressBar title is not text.")
+                    return
+
+        # 生成bar的image
+        bar_image_definition_code.append(f"image {fgui_progressbar.name}_base_bar_image:")
+        bar_image_definition_code.append(f"{self.indent_unit}Frame('{second_bar_name}')")
+        bar_image_definition_code.append(f"image {fgui_progressbar.name}_active_bar_image:")
+        bar_image_definition_code.append(f"{self.indent_unit}Frame('{first_bar_name}')")
+        bar_image_definition_code.append("")
+
+        # 生成progressbar样式
+        is_vertical = (progressbar_type==BarOrientationType.VERTICAL)
+        style_definition_code.append(f"style {fgui_progressbar.name}:")
+        style_definition_code.append(f"{self.indent_unit}bar_vertical {is_vertical}")
+        style_definition_code.append(f"{self.indent_unit}xysize {fgui_progressbar.size}")
+        # FairyGUI中前景图片使用缩放来改变进度，Ren'Py中使用bar_resizing来实现。
+        style_definition_code.append(f"{self.indent_unit}bar_resizing True")
+        if is_vertical:
+            style_definition_code.append(f"{self.indent_unit}top_bar '{fgui_progressbar.name}_base_bar_image'")
+            style_definition_code.append(f"{self.indent_unit}bottom_bar '{fgui_progressbar.name}_active_bar_image'")
+            thumb_ypos = 0
+        else:
+            style_definition_code.append(f"{self.indent_unit}left_bar '{fgui_progressbar.name}_active_bar_image'")
+            style_definition_code.append(f"{self.indent_unit}right_bar '{fgui_progressbar.name}_base_bar_image'")
+        style_definition_code.append("")
+
+        # 添加头部注释
+        progressbar_style_code.extend(bar_image_definition_code)
+        progressbar_style_code.append("# 进度条样式定义")
+        progressbar_style_code.extend(style_definition_code)
+
+        self.style_code.extend(progressbar_style_code)
+
     def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
         """
         生成滑块样式。
@@ -302,10 +509,10 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
             "horizontal_thumb_active"
 
         image horizontal_idle_bar_image:
-            "horizontal_bar_base"
+            Frame("horizontal_bar_base")
 
         image horizontal_hover_bar_image:
-            "horizontal_bar_active"
+            Frame("horizontal_bar_active")
 
         style horizontal_slider:
             bar_vertical False
@@ -314,6 +521,7 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
             thumb_offset 24
             left_bar "horizontal_hover_bar_image"
             right_bar "horizontal_idle_bar_image"
+            bar_resizing True
             thumb Fixed(Frame("horizontal_[prefix_]thumb_image",xsize=48,ysize=54,ypos=-22))
         """
         bar_image_definition_code = []
@@ -353,7 +561,7 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
                 else:
                     print("Slider base is neither image nor graph.")
                     return
-            # FGUI中滑动条的可变bar部分
+            # FGUI中水平滑动条的可变bar部分
             if displayable.name == 'bar':
                 if isinstance(displayable, FguiImage):
                     first_bar_name = self.fgui_assets.get_componentname_by_id(displayable.src)
@@ -364,11 +572,27 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
                     print("Slider bar is neither image nor graph.")
                     return
                 bar_id = displayable.id
+                continue
+            # FGUI中垂直滑动条的可变bar部分
+            if displayable.name == 'bar_v':
+                if isinstance(displayable, FguiImage):
+                    first_bar_name = self.fgui_assets.get_componentname_by_id(displayable.src)
+                elif isinstance(displayable, FguiGraph):
+                    bar_image_definition_code.extend(self.generate_graph_definitions(displayable, fgui_slider.name))
+                    first_bar_name = f"{fgui_slider.name}_{displayable.id}"
+                else:
+                    print("Slider bar_v is neither image nor graph.")
+                    return
+                bar_id = displayable.id
+                slider_type = BarOrientationType.VERTICAL
+                continue
             # FGUI中滑动条的标题类型文本
             if displayable.name == 'title':
                 if isinstance(displayable, FguiText):
                     style_name = f"slider_{fgui_slider.id}_title"
                     self.generate_text_style(displayable, style_name)
+                    # 暂时不生成标题文本
+                    continue
                 else:
                     print("Slider title is not text.")
                     return
@@ -383,17 +607,17 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
                     # 根据grip中relation的sidePair属性来确定方向
                     side_pair_str = displayable.relations.relation_dict[bar_id]
                     # 只有该值表示垂直滑动条
-                    if side_pair_str == "bottom-bottom":
-                        slider_type = BarOrientationType.VERTICAL
+                    # if side_pair_str == "bottom-bottom":
+                    #     slider_type = BarOrientationType.VERTICAL
                 else:
                     print("Slider grp is not button.")
                     return
 
         # 生成bar和thumb的image
         bar_image_definition_code.append(f"image {fgui_slider.name}_base_bar_image:")
-        bar_image_definition_code.append(f"{self.indent_unit}'{second_bar_name}'")
+        bar_image_definition_code.append(f"{self.indent_unit}Frame('{second_bar_name}')")
         bar_image_definition_code.append(f"image {fgui_slider.name}_active_bar_image:")
-        bar_image_definition_code.append(f"{self.indent_unit}'{first_bar_name}'")
+        bar_image_definition_code.append(f"{self.indent_unit}Frame('{first_bar_name}')")
         bar_image_definition_code.append(f"image {fgui_slider.name}_idle_thumb_image:")
         bar_image_definition_code.append(f"{self.indent_unit}'{thumb_idle_name}'")
         bar_image_definition_code.append(f"image {fgui_slider.name}_hover_thumb_image:")
@@ -405,14 +629,16 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
         style_definition_code.append(f"style {fgui_slider.name}:")
         style_definition_code.append(f"{self.indent_unit}bar_vertical {is_vertical}")
         style_definition_code.append(f"{self.indent_unit}xysize {fgui_slider.size}")
+        # FairyGUI中前景图片使用缩放来改变进度，Ren'Py中使用bar_resizing来实现。
+        style_definition_code.append(f"{self.indent_unit}bar_resizing True")
         style_definition_code.append(f"{self.indent_unit}thumb_offset {thumb_offset}")
         if is_vertical:
-            style_definition_code.append(f"{self.indent_unit}top_bar '{second_bar_name}'")
-            style_definition_code.append(f"{self.indent_unit}bottom_bar '{first_bar_name}'")
+            style_definition_code.append(f"{self.indent_unit}top_bar '{fgui_slider.name}_base_bar_image'")
+            style_definition_code.append(f"{self.indent_unit}bottom_bar '{fgui_slider.name}_active_bar_image'")
             thumb_ypos = 0
         else:
-            style_definition_code.append(f"{self.indent_unit}left_bar '{first_bar_name}'")
-            style_definition_code.append(f"{self.indent_unit}right_bar '{second_bar_name}'")
+            style_definition_code.append(f"{self.indent_unit}left_bar '{fgui_slider.name}_active_bar_image'")
+            style_definition_code.append(f"{self.indent_unit}right_bar '{fgui_slider.name}_base_bar_image'")
             thumb_xpos = 0
         style_definition_code.append(f"{self.indent_unit}thumb Fixed(Frame('{fgui_slider.name}_[prefix_]thumb_image',xysize={grip_com.size},pos=({thumb_xpos},{thumb_ypos})))")
         style_definition_code.append("")
@@ -424,7 +650,7 @@ def generate_slider_style(self, fgui_slider : FguiSlider) -> None:
 
         self.style_code.extend(slider_style_code)
 
-    def generate_scroll_bar_style(self, fgui_scrollbar : FguiScrollBar) -> None:
+    def generate_scrollbar_style(self, fgui_scrollbar : FguiScrollBar) -> None:
         """
         生成滚动条样式。
         目标样例：
@@ -435,7 +661,6 @@ def generate_scroll_bar_style(self, fgui_scrollbar : FguiScrollBar) -> None:
             base_bar Frame("vertical_scrollbar_bar")
             thumb Frame("vertical_scrollbar_grip_[prefix_]background")
         """
-        # scrollbar_image_definition_code = []
         style_definition_code = []
         scrollbar_style_code = []
         if not isinstance(fgui_scrollbar, FguiScrollBar):
@@ -495,15 +720,20 @@ def generate_scroll_bar_style(self, fgui_scrollbar : FguiScrollBar) -> None:
         else:
             print("Scrollbar grip is not button.")
             return
-            
+
+        # 滑块尺寸是否固定
+        thumb_size = (None, None)
+        if fgui_scrollbar.fixed_grip_size:
+            thumb_size = grip_com.size
+
         # 生成scrollbar样式
-        scrollbar_style_code.append(f"style {fgui_scrollbar.name}:")
-        scrollbar_style_code.append(f"{self.indent_unit}bar_vertical {is_vertical}")
-        scrollbar_style_code.append(f"{self.indent_unit}bar_invert {is_vertical}")
-        scrollbar_style_code.append(f"{self.indent_unit}xysize {fgui_scrollbar.size}")
-        scrollbar_style_code.append(f"{self.indent_unit}base_bar Frame('{fgui_scrollbar.name}_[prefix_]bar')")
-        scrollbar_style_code.append(f"{self.indent_unit}thumb Frame('{thumb_image_name}')")
-        scrollbar_style_code.append("")
+        style_definition_code.append(f"style {fgui_scrollbar.name}:")
+        style_definition_code.append(f"{self.indent_unit}bar_vertical {is_vertical}")
+        style_definition_code.append(f"{self.indent_unit}bar_invert {is_vertical}")
+        style_definition_code.append(f"{self.indent_unit}xysize {fgui_scrollbar.size}")
+        style_definition_code.append(f"{self.indent_unit}base_bar Frame('{fgui_scrollbar.name}_[prefix_]bar')")
+        style_definition_code.append(f"{self.indent_unit}thumb Frame('{thumb_image_name}', xysize={thumb_size})")
+        style_definition_code.append("")
 
         # 添加头部注释
         # scrollbar_style_code.extend(scrollbar_image_definition_code)
@@ -512,6 +742,10 @@ def generate_scroll_bar_style(self, fgui_scrollbar : FguiScrollBar) -> None:
 
         self.style_code.extend(scrollbar_style_code)
 
+    @staticmethod
+    def is_main_menu_screen(screen_name : str):
+        return screen_name == 'main_menu'
+
     @staticmethod
     def is_menu_screen(screen_name : str):
         return screen_name in FguiToRenpyConverter.menu_screen_name_list
@@ -568,6 +802,8 @@ def convert_component_display_list(self, component: FguiComponent, list_begin_in
                 screen_ui_code.extend(self.generate_list_displayable(displayable))
             # 装载器
             elif isinstance(displayable, FguiLoader):
+                screen_ui_code.extend(self.generate_loader_displayable(displayable))
+                # 目前装载器只支持图片。
                 pass
             # 其他组件
             else:
@@ -578,15 +814,25 @@ def convert_component_display_list(self, component: FguiComponent, list_begin_in
                     condition_str = f"showif {displayable.gear_display.controller_name} in {displayable.gear_display.controller_index}:"
                     screen_ui_code.append(f"{self.indent_str}{condition_str}")
                     self.indent_level_up()
-                    end_indent_level = 2
-                
+                    end_indent_level = end_indent_level + 1
+
+                screen_ui_code.append(f"{self.indent_str}fixed:")
+                self.indent_level_up()
+                screen_ui_code.append(f"{self.indent_str}pos {displayable.xypos}")
+                screen_ui_code.append(f"{self.indent_str}xysize {displayable.size}")
+                # 非默认锚点或轴心，以及有旋转的情况添加一个transform。
+                if displayable.pivot_is_anchor or displayable.rotation or displayable.pivot != (0.0, 0.0):
+                    screen_ui_code.append(f"{self.indent_str}at transform:")
+                    self.indent_level_up()
+                    screen_ui_code.append(f"{self.indent_str}anchor {displayable.pivot}")
+                    screen_ui_code.append(f"{self.indent_str}transform_anchor True")
+                    if displayable.rotation:
+                        screen_ui_code.append(f"{self.indent_str}rotate {displayable.rotation}")
+                    self.indent_level_down()
                 # 根据引用源id查找组件
                 ref_com = self.fgui_assets.get_component_by_id(displayable.src)
                 # 按钮。可设置标题，并根据自定义数据字段设置action。
                 if ref_com.extention == "Button" and ref_com.name != None:
-                    screen_ui_code.append(f"{self.indent_str}fixed:")
-                    self.indent_level_up()
-                    screen_ui_code.append(f"{self.indent_str}pos {displayable.xypos}")
                     # 取FguiComponent和FguiDisplayable对象的自定义数据作为action。FguiDisplayable对象中的自定义数据优先。
                     actions = displayable.custom_data if displayable.custom_data else ref_com.custom_data
                     action_list = []
@@ -606,9 +852,6 @@ def convert_component_display_list(self, component: FguiComponent, list_begin_in
                     continue
                 # 滑动条
                 if ref_com.extention == "Slider" and ref_com.name != None:
-                    screen_ui_code.append(f"{self.indent_str}fixed:")
-                    self.indent_level_up()
-                    screen_ui_code.append(f"{self.indent_str}pos {displayable.xypos}")
                     # 若在自定义数据中指定了关联数据对象，则直接使用。
                     if displayable.custom_data:
                         bar_value = displayable.custom_data
@@ -623,41 +866,122 @@ def convert_component_display_list(self, component: FguiComponent, list_begin_in
                     screen_ui_code.append(f"{self.indent_str}bar value {bar_value} style '{ref_com.name}' id '{component.name}_{displayable.id}'")
                     self.indent_level_down(end_indent_level)
                     continue
+                # 进度条
+                if ref_com.extention == "ProgressBar" and ref_com.name != None:
+                    # 若在自定义数据中指定了关联数据对象，则直接使用。
+                    if displayable.custom_data:
+                        variable_name = displayable.custom_data
+                    # 否则再查找引用源对象的自定义数据
+                    elif ref_com.custom_data:
+                        variable_name = ref_com.custom_data
+                    # 若未指定则在screen中生成一个临时变量
+                    else:
+                        variable_name = f"{component.name}_{displayable.name}_value"
+                        range_value = (int)(displayable.progressbar_property.max_value) - (int)(displayable.progressbar_property.min_value)
+                        screen_ui_code.append(f"{self.indent_str}{self.generate_uiadjustment_definition_str(name=variable_name, range=range_value, value=(int)(displayable.progressbar_property.current_value))}")
+                    screen_ui_code.append(f"{self.indent_str}bar adjustment {variable_name} style '{ref_com.name}' id '{component.name}_{displayable.id}'")
+                    self.indent_level_down(end_indent_level)
+                    continue
                 # 其他组件
-                screen_ui_code.append(f"{self.indent_str}fixed:")
-                self.indent_level_up()
-                screen_ui_code.append(f"{self.indent_str}pos {displayable.xypos}")
-                screen_ui_code.append(f"{self.indent_str}use {ref_com.name} id '{component.name}_{displayable.id}'")
+                parameter_str=''
+                parameter_str = self.generate_label_screen_parameter(displayable.label_property)
+
+
+                screen_ui_code.append(f"{self.indent_str}use {ref_com.name}({parameter_str}) id '{component.name}_{displayable.id}'")
                 self.indent_level_down(end_indent_level)
 
         return screen_ui_code
 
-    def generate_screen(self, component : FguiComponent):
+    def generate_label_screen(self, component : FguiComponent):
         """
-        生成screen定义。目标样例：
+        生成FGUI标签组件对应screen。目标样例：
+        screen label_screen(title='', xysize=(100, 100)):
+            fixed:
+                xysize xysize
+                text title:
+                    align (0.5, 0.5)
+                    font "hyzjhj.ttf"
+                    color "#FEDAAA"
+                    size 32
+        """
+        self.screen_definition_head.clear()
+        self.screen_variable_code.clear()
+        self.screen_function_code.clear()
+        self.screen_ui_code.clear()
+        self.screen_has_dismiss = False
+        self.dismiss_action_list.clear()
+        self.reset_indent_level()
+ 
+        # 生成标签组件screen
+        self.screen_definition_head.append("# 标签screen定义")
+        self.screen_definition_head.append(f"# 从FairyGUI标签组件{component.name}转换而来")
+        id = component.id
+        label_name = component.name
+        xysize = component.size
 
-        screen test_main_menu():
-            add 'menu_bg':
-                pos (0, 0)
+        title_index = -1
+        default_title = ''
+        title_displayable = None
+        text_xalign, text_yalign = 0, 0
+        title_pos = (0, 0)
+        title_anchor = (0, 0)
+        title_xysize = (0, 0)
+        title_style = None
+        title_color = '#FFFFFF'
+        title_size = 32
+
+        # 查找title文本组件
+        for index, displayable in enumerate(component.display_list.displayable_list):
+            if isinstance(displayable, FguiText) and displayable.name == 'title':
+                title_index = index
+                title_displayable = displayable
+                default_title = title_displayable.text
+                if displayable.single_line:
+                    default_title = default_title.replace("\n", "").replace("\r", "")
+                title_xysize = title_displayable.size
+                text_xalign, text_yalign = self.trans_text_align(title_displayable.align, title_displayable.v_align)
+                title_pos = title_displayable.xypos
+                title_anchor = title_displayable.pivot
+                self.generate_text_style(title_displayable, f"{label_name}_title_text")
+                title_style = f"{label_name}_title_text"
+                title_color = title_displayable.text_color
+                title_size = title_displayable.font_size
+                break
 
-            fixed:
-                pos (1007, 178)
-                use main_menu_button(title='开坑', actions=ShowMenu("save"))
-            fixed:
-                pos (1007, 239)
-                use main_menu_button(title='填坑', actions=ShowMenu("load"))
-            fixed:
-                pos (1007, 300)
-                use main_menu_button(title='设置', actions=ShowMenu("preferences"))
-            fixed:
-                pos (1007, 361)
-                use main_menu_button(title='关于', actions=ShowMenu("about"))
-            fixed:
-                pos (1007, 422)
-                use main_menu_button(title='帮助', actions=ShowMenu("help"))
-            fixed:
-                pos (1007, 483)
-                use main_menu_button(title='放弃', actions=Quit())
+        # 界面定义
+        self.screen_definition_head.append(f"screen {label_name}(title_text='{default_title}', title_color='{title_color}', title_size={title_size}):")
+        self.indent_level_up()
+
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
+        # 生成title组件前的组件
+        self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=0, list_end_index=title_index))
+
+        if title_displayable:
+            # title组件
+            self.screen_ui_code.append(f"{self.indent_str}text title_text style '{title_style}':")
+            self.indent_level_up()
+            self.screen_ui_code.append(f"{self.indent_str}xysize {title_xysize}")
+            self.screen_ui_code.append(f"{self.indent_str}color title_color")
+            self.screen_ui_code.append(f"{self.indent_str}size title_size")
+
+            self.indent_level_down()
+
+            # 生成title组件后的组件
+            self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=title_index + 1, list_end_index=-1))
+
+        self.screen_code.extend(self.screen_definition_head)
+
+        self.screen_ui_code.append("")
+        self.screen_code.extend(self.screen_ui_code)
+
+
+    def generate_screen(self, component : FguiComponent):
+        """
+        生成screen定义。
         """
         # self.screen_code.clear()
         self.screen_definition_head.clear()
@@ -666,7 +990,6 @@ def generate_screen(self, component : FguiComponent):
         self.screen_ui_code.clear()
         self.screen_has_dismiss = False
         self.dismiss_action_list.clear()
-        choice_screen_code = []
 
         self.screen_definition_head.append("# 界面定义")
         self.screen_definition_head.append(f"# 从FairyGUI组件{component.name}转换而来")
@@ -678,6 +1001,11 @@ def generate_screen(self, component : FguiComponent):
         # 界面入参列表
         screen_params = ''
 
+        # main_menu界面的特殊处理
+        if self.is_main_menu_screen(screen_name):
+            self.generate_main_menu_screen(component)
+            return
+
         # choice界面的特殊处理
         if self.is_choice_screen(screen_name):
             self.generate_choice_screen(component)
@@ -720,11 +1048,100 @@ def generate_screen(self, component : FguiComponent):
             self.screen_ui_code.append(f"{self.indent_str}tag menu\n")
             # 若自定义了game_menu，则修改默认游戏内菜单显示的控制变量。
             if screen_name == "game_menu":
-                self.set_game_global_variables('_game_menu_screen', str("\"game_menu\""))
+                self.set_game_global_variables('_game_menu_screen', "'game_menu'")
         if self.is_modal_screen(screen_name):
             self.screen_ui_code.append(f"{self.indent_str}modal True")
             self.screen_ui_code.append(f"{self.indent_str}zorder 200\n")
 
+        # 根据控制器列表定义界面内变量
+        if component.controller_list:
+            self.screen_variable_code.append(f"{self.indent_str}# 由组件控制器生成的界面内控制变量：") 
+        for controller in component.controller_list:
+            if not isinstance(controller, FguiController):
+                print("Component controller object type is wrong.")
+                break
+            if controller.homepage_type == "specific":
+                selected_index = int(controller.homepage)
+            else:
+                selected_index = controller.selected
+            self.screen_variable_code.append(f"{self.indent_str}default {controller.name} = {selected_index}")            
+
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
+        # 根据组件的可见区域性质，决定是否加一层viewport。
+        if component.overflow == "hidden":
+            self.screen_ui_code.append(f"{self.indent_str}viewport:")
+            self.indent_level_up()
+            self.screen_ui_code.append(f"{self.indent_str}xysize {component.size}")
+            self.screen_ui_code.append(f"{self.indent_str}draggable False")
+            self.screen_ui_code.append(f"{self.indent_str}mousewheel False")
+            self.screen_ui_code.append(f"{self.indent_str}fixed:")
+            self.indent_level_up()
+        elif component.overflow == "scroll":
+            self.screen_ui_code.append(f"{self.indent_str}viewport:")
+            self.indent_level_up()
+            self.screen_ui_code.append(f"{self.indent_str}xysize {component.size}")
+            self.screen_ui_code.append(f"{self.indent_str}draggable True") 
+            # 在Ren'Py中实际可能无法滚动。
+            # 需要添加一个fixed组件，并设置一个合适的xysize。该xysize应为容纳所有子组件的包围框。
+            self.screen_ui_code.append(f"{self.indent_str}fixed:")
+            self.indent_level_up()
+            self.screen_ui_code.append(f"{self.indent_str}xysize ({component.bbox_width}, {component.bbox_height})")
+
+        self.screen_ui_code.extend(self.convert_component_display_list(component))
+
+        self.screen_code.extend(self.screen_definition_head)
+        if self.screen_variable_code:
+            self.screen_code.extend(self.screen_variable_code)
+            self.screen_code.append("")
+        if self.screen_function_code:
+            self.screen_code.extend(self.screen_function_code)
+            self.screen_code.append("")
+        # 添加只有1个生效的dismiss
+        if self.screen_has_dismiss:
+            self.screen_ui_code.append(f"{self.indent_str}dismiss:")
+            self.indent_level_up()
+            self.screen_ui_code.append(f"{self.indent_str}modal False")
+            dismiss_action_list = ', '.join(self.dismiss_action_list)
+            self.screen_ui_code.append(f"{self.indent_str}action [{dismiss_action_list}]")
+
+        self.screen_ui_code.append("")
+        self.screen_code.extend(self.screen_ui_code)
+
+    def generate_main_menu_screen(self, component : FguiComponent):
+        print("This is main menu screen.")
+        self.screen_definition_head.clear()
+        self.screen_variable_code.clear()
+        self.screen_function_code.clear()
+        self.screen_ui_code.clear()
+        self.screen_has_dismiss = False
+        self.dismiss_action_list.clear()
+
+        self.reset_indent_level()
+        self.screen_definition_head.append(f"screen main_menu():")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}tag menu\n")
+
+        # 自定义的组件
+        # 标题文本。若main_menu_title不为空，则先遍历一遍所有组件并修改标题文本内容。
+        if self.main_menu_title:
+            for displayable in component.display_list.displayable_list:
+                if isinstance(displayable, FguiText) and displayable.name == 'title':
+                    if self.main_menu_title.text_str:
+                        displayable.text = self.main_menu_title.text_str
+                    if self.main_menu_title.text_color:
+                        displayable.text_color = self.main_menu_title.text_color
+                    break
+
+        # 游戏logo图片。若main_menu_logo不为空，则先遍历一遍所有组件并修改logo图片内容。
+        # if self.main_menu_logo:
+        #     for displayable in component.display_list.displayable_list:
+        #         if isinstance(displayable, FguiImage) and displayable.name == 'logo':
+        #             displayable.image = self.main_menu_logo
+
         # 根据控制器列表定义界面内变量
         if component.controller_list:
             self.screen_variable_code.append(f"{self.indent_str}# 由组件控制器生成的界面内控制变量：") 
@@ -734,6 +1151,11 @@ def generate_screen(self, component : FguiComponent):
                 break
             self.screen_variable_code.append(f"{self.indent_str}default {controller.name} = {controller.selected}")            
 
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
         # 根据组件的可见区域性质，决定是否加一层viewport。
         if component.overflow == "hidden":
             self.screen_ui_code.append(f"{self.indent_str}viewport:")
@@ -770,6 +1192,7 @@ def generate_screen(self, component : FguiComponent):
         self.screen_ui_code.append("")
         self.screen_code.extend(self.screen_ui_code)
 
+
     def generate_choice_screen(self, component : FguiComponent):
         print("This is choice screen.")
         self.screen_definition_head.clear()
@@ -805,6 +1228,11 @@ def generate_choice_screen(self, component : FguiComponent):
         choice_screen_code.append(f"# 选项界面")
         choice_screen_code.append(f"screen {component.name}({screen_params}):")
 
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
         # vbox的行距
         vbox_spacing = choice_list.line_gap if choice_list else 0
         self.reset_indent_level()
@@ -874,6 +1302,11 @@ def generate_save_load_screen(self, component : FguiComponent):
 
         # 菜单标签
         self.screen_definition_head.append(f"{self.indent_str}tag menu")
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
         # save_slot_list 之前的组件
         self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=0, list_end_index=slot_list_index))
 
@@ -1063,6 +1496,10 @@ def generate_history_screen(self, component :FguiComponent):
         self.indent_level_up()
         self.screen_definition_head.append(f"{self.indent_str}tag menu")
         self.screen_definition_head.append(f"{self.indent_str}predict False")
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
         
         # history_list 之前的组件
         self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=0, list_end_index=history_list_index))
@@ -1147,6 +1584,11 @@ def generate_history_item(self, component : FguiComponent):
         self.screen_definition_head.append(f"screen {component.name}({screen_params}):")
         self.indent_level_up()
 
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
         # 不包含who和what的displayList部分按顺序放在前面
         index_min = min(what_index, who_index)
         index_max = max(what_index, who_index)
@@ -1218,7 +1660,10 @@ def generate_gallery_screen(self, component : FguiComponent):
         self.screen_definition_head.append(f"screen {component.name}():")
         self.indent_level_up()
         self.screen_definition_head.append(f"{self.indent_str}tag menu")
-        self.screen_definition_head.append(f"{self.indent_str}")
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
 
         # gallery_button_list 之前的组件
         self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=0, list_end_index=gallery_button_list_index))
@@ -1328,7 +1773,10 @@ def generate_music_room_screen(self, component : FguiComponent):
         self.screen_definition_head.append(f"screen {component.name}():")
         self.indent_level_up()
         self.screen_definition_head.append(f"{self.indent_str}tag menu")
-        self.screen_definition_head.append(f"{self.indent_str}")
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
 
         # musicroom_button_list 之前的组件
         self.screen_ui_code.extend(self.convert_component_display_list(component, list_begin_index=0, list_end_index=musicroom_button_list_index))
@@ -1441,6 +1889,11 @@ def generate_say_screen(self, component : FguiComponent):
         self.screen_definition_head.append("style say_dialogue is say_what_text_style")
         self.screen_definition_head.append(f"screen {component.name}({screen_params}):")
         self.indent_level_up()
+        # 显式添加一个fixed，设置界面尺寸。
+        self.screen_ui_code.append(f"{self.indent_str}fixed:")
+        self.indent_level_up()
+        self.screen_ui_code.append(f"{self.indent_str}{self.generate_screen_fixed_size_str(component)}")
+
         self.screen_ui_code.append(f"{self.indent_str}if who is not None:")
         self.indent_level_up()
         self.screen_ui_code.append(f"{self.indent_str}add {namebox_image}:")
@@ -1478,6 +1931,11 @@ def get_image_name(self, fgui_image):
     def generate_variable_definition_str(variable_name, current_value=None):
         return f"default {variable_name} = {current_value}"
 
+    @staticmethod
+    def generate_uiadjustment_definition_str(name, range=1, value=0):
+        # return f"default {uiadjustment_name} = {adjustment_value}"
+        return f"default {name} = ui.adjustment(range={range}, value={value}, adjustable=False)"
+
     @staticmethod
     def generate_barvalue_definition_str(barvalue_name, min_value=0, max_value=100, current_value=0, scope='local'):
         barvalue_str = ''
@@ -1488,19 +1946,58 @@ def generate_barvalue_definition_str(barvalue_name, min_value=0, max_value=100,
         return barvalue_str
 
     @staticmethod
-    def generate_button_parameter(button_title=None, original_actions_str=None, icon=None):
+    def generate_label_screen_parameter(label_property):
+        parameter_list = []
+        parameter_str = ""
+        if label_property.title:
+            parameter_list.append(f"title_text='{label_property.title}'")
+        if label_property.text_color:
+            parameter_list.append(f"title_color='{label_property.text_color}'")
+        if label_property.font_size:
+            parameter_list.append(f"title_size={label_property.font_size}")
+        parameter_str = ",".join(parameter_list)
+        return parameter_str
+
+    @staticmethod
+    def generate_button_default_parameter(button_title=None, original_actions_str=None, icon=None, activate_sound=None):
         parameter_str = ""
         title_str = "title=''"
         actions_str = "actions=NullAction()"
         icon_str = "icon=Null()"
+        activate_sound_str = "activate_sound=None"
         if button_title :
             title_str = f"title=\"{button_title}\"".replace("\n", "\\n").replace("\r", "\\n")
         if original_actions_str :
             actions_str = f"actions={original_actions_str}"
         if icon:
             icon_str = f"icon={icon}"
+        if activate_sound:
+            activate_sound_str = f"activate_sound=audio.{activate_sound}"
+        else:
+            activate_sound_str = "activate_sound=None"
+        parameter_str = f"{title_str}, {actions_str}, {icon_str}, {activate_sound_str}"
+        return parameter_str
 
-        parameter_str = f"{title_str}, {actions_str}, {icon_str}"
+    @staticmethod
+    def generate_button_parameter(button_title=None, original_actions_str=None, icon=None, activate_sound=None):
+        parameter_str = ""
+        title_str = None
+        actions_str = None
+        icon_str = None
+        activate_sound_str = None
+        if button_title :
+            title_str = f"title=\"{button_title}\"".replace("\n", "\\n").replace("\r", "\\n")
+        if original_actions_str :
+            actions_str = f"actions={original_actions_str}"
+        if icon:
+            icon_str = f"icon={icon}"
+        if activate_sound:
+            activate_sound_str = f"activate_sound=audio.{activate_sound}"
+
+        # 仅生成非None的参数
+        parameter_str = ",".join([param for param in [title_str, actions_str, icon_str, activate_sound_str] if param is not None])
+        if parameter_str:
+            parameter_str = f"{parameter_str}"
         return parameter_str
 
     @staticmethod
@@ -1519,6 +2016,21 @@ def generate_button_callable_parameter(button_title=None, actions=None, icon=Non
         parameter_str = f"{title_str}, {actions_str}, {icon_str}"
         return parameter_str
 
+    @staticmethod
+    def generate_screen_fixed_size_str(component : FguiComponent):
+        size_str = ""
+
+        if component.max_width == 0 and component.min_width == 0 and component.max_height == 0 and component.min_height == 0:
+            size_str = f"xysize {component.size}"
+            return size_str
+
+        max_width = component.max_width if component.max_width > 0 else component.size[0]
+        min_width = component.min_width if component.min_width > 0 else component.size[0]
+        max_height = component.max_height if component.max_height > 0 else component.size[1]
+        min_height = component.min_height if component.min_height > 0 else component.size[1]
+        size_str = f"maximum ({max_width}, {max_height}) minimum ({min_width}, {min_height})"
+        return size_str
+
     def generate_text_style(self, fgui_text : FguiText, style_name : str):
         """
         生成文本样式，专用于按钮标题，因为按钮中通常只有一个文本组件。
@@ -1554,8 +2066,6 @@ def generate_text_style(self, fgui_text : FguiText, style_name : str):
         self.style_code.append(f"{style_indent}font '{text_font}'")
         self.style_code.append(f"{style_indent}size {fgui_text.font_size}")
         self.style_code.append(f"{style_indent}color '{fgui_text.text_color}'")
-        # xalign, yalign = self.trans_text_align(fgui_text.align, fgui_text.v_align)
-        # self.style_code.append(f"{style_indent}align ({xalign}, {yalign})")
 
         text_outline_string = self.generate_text_outline_string(fgui_text)
         self.style_code.append(f"{style_indent}outlines {text_outline_string}")
@@ -1564,8 +2074,10 @@ def generate_text_style(self, fgui_text : FguiText, style_name : str):
         #设置最小宽度才能使text_align生效
         self.style_code.append(f"{style_indent}min_width {fgui_text.size[0]}")
         xalign, yalign = self.trans_text_align(fgui_text.align, fgui_text.v_align)
-        # 默认两侧居中对齐
+        # 默认文本居中，如果文本长度超出文本框，效果可能与FairyGUI不一致。
         self.style_code.append(f"{style_indent}textalign {xalign}")
+        # 垂直方向对齐方式使用yalign，可能有问题。
+        self.style_code.append(f"{style_indent}yalign {yalign}")
         # 粗体、斜体、下划线、删除线
         if fgui_text.bold:
             self.style_code.append(f"{style_indent}bold {fgui_text.bold}")
@@ -1574,7 +2086,9 @@ def generate_text_style(self, fgui_text : FguiText, style_name : str):
         if fgui_text.underline:
             self.style_code.append(f"{style_indent}underline {fgui_text.underline}")
         if fgui_text.strike:
-            self.style_code.append(f"{style_indent}strikethrough {fgui_text.strike}")
+            self.style_code.append(f"{style_indent}strikethrough {fgui_text.strike}")        # 不自动换行
+        if fgui_text.single_line:
+            self.style_code.append(f"{style_indent}layout 'nobreak'")
         self.style_code.append("")
 
     @staticmethod
@@ -1602,6 +2116,7 @@ def generate_button_children(self, fgui_button : FguiButton):
         selected_hover_child_list = []
         # 非激活状体子组件列表
         insensitive_child_list = []
+        selected_insensitive_child_list = []
         # 始终显示的子组件列表
         always_show_child_list = []
         # 5种类型的子组件字典
@@ -1611,6 +2126,7 @@ def generate_button_children(self, fgui_button : FguiButton):
             'selected': selected_child_list,
             'selected_hover': selected_hover_child_list,
             'insensitive': insensitive_child_list,
+            'selected_insensitive': selected_insensitive_child_list,
             'always_show': always_show_child_list
         }
         state_index_name_dict = {
@@ -1619,6 +2135,7 @@ def generate_button_children(self, fgui_button : FguiButton):
             2: 'selected',
             3: 'selected_hover',
             4: 'insensitive',
+            5: 'selected_insensitive',
             None: 'always_show'
         }
         # 默认按钮控制器为button，并且必定有4种状态，顺序分别为idle、hover、selected、selected_hover。
@@ -1628,7 +2145,7 @@ def generate_button_children(self, fgui_button : FguiButton):
             return state_children_dict
         # 检查按钮控制器的状态列表
         state_list = fgui_button.controller_list[0].page_index_dict.keys()
-        state_number = min(len(state_list), 5) #暂时不处理5种以上的控制器状态
+        state_number = min(len(state_list), 6) #暂时不处理6种以上的控制器状态
         if state_number < 4:
             print("按钮控制器状态总数小于4。")
             return state_children_dict
@@ -1688,7 +2205,6 @@ def generate_button_screen(self, component : FguiComponent):
             textalign 0.5
         """
 
-        # self.screen_code.clear()
         self.screen_definition_head.clear()
         self.screen_variable_code.clear()
         self.screen_function_code.clear()
@@ -1703,15 +2219,17 @@ def generate_button_screen(self, component : FguiComponent):
         selected_hover_child_list = []
         # 非激活状体子组件列表
         insensitive_child_list = []
+        selected_insensitive_child_list = []
         # 始终显示的子组件列表
         always_show_child_list = []
-        # 5种类型的子组件字典
+        # 6种类型的子组件字典
         state_children_dict = {
             'idle': idle_child_list,
             'selected': selected_child_list,
             'hover': hover_child_list,
             'selected_hover': selected_hover_child_list,
             'insensitive': insensitive_child_list,
+            'selected_insensitive': selected_insensitive_child_list,
             'always_show': always_show_child_list
         }
 
@@ -1721,6 +2239,7 @@ def generate_button_screen(self, component : FguiComponent):
             2: 'hover',
             3: 'selected_hover',
             4: 'insensitive',
+            5: 'selected_insensitive',
             None: 'always_show'
         }
         
@@ -1738,7 +2257,7 @@ def generate_button_screen(self, component : FguiComponent):
             return
         # 检查按钮控制器的状态列表
         state_list = component.controller_list[0].page_index_dict.keys()
-        state_number = min(len(state_list), 5) #暂时不处理5种以上的控制器状态
+        state_number = min(len(state_list), 6) #暂时不处理6种以上的控制器状态
         if state_number < 4:
             print("按钮控制器状态总数小于4。")
             return
@@ -1762,6 +2281,8 @@ def generate_button_screen(self, component : FguiComponent):
         icon_size = (0, 0)
         icon_image = None
 
+        default_activate_sound = None
+
         for displayable in component.display_list.displayable_list:
             # 不带显示控制器表示始终显示
             if displayable.gear_display is None:
@@ -1807,26 +2328,25 @@ def generate_button_screen(self, component : FguiComponent):
 
         # 根据state_children_dict生成各种对应状态的background
         is_bar_grip = self.is_bar_grip(component)
-        # self.generate_image_object(f"{button_name}_idle_background", state_children_dict['idle'], button_name)
         self.generate_composite_image_object(f"{button_name}_idle_background", state_children_dict['idle'], button_name, xysize, is_frame=is_bar_grip)
         # selected_background
-        # self.generate_image_object(f"{button_name}_selected_background", state_children_dict['selected'], button_name)
         self.generate_composite_image_object(f"{button_name}_selected_background", state_children_dict['selected'], button_name, xysize, is_frame=is_bar_grip)
         # hover_background
-        # self.generate_image_object(f"{button_name}_hover_background", state_children_dict['hover'], button_name)
         self.generate_composite_image_object(f"{button_name}_hover_background", state_children_dict['hover'], button_name, xysize, is_frame=is_bar_grip)
         # selected_hover_background
-        # self.generate_image_object(f"{button_name}_selected_hover_background", state_children_dict['selected_hover'], button_name)
         self.generate_composite_image_object(f"{button_name}_selected_hover_background", state_children_dict['selected_hover'], button_name, xysize, is_frame=is_bar_grip)
         # insensitive_background
-        # self.generate_image_object(f"{button_name}_insensitive_background", state_children_dict['insensitive'], button_name)
         self.generate_composite_image_object(f"{button_name}_insensitive_background", state_children_dict['insensitive'], button_name, xysize, is_frame=is_bar_grip)
-
+        # selected_insensitive_background
+        self.generate_composite_image_object(f"{button_name}_selected_insensitive_background", state_children_dict['selected_insensitive'], button_name, xysize, is_frame=is_bar_grip)
+        # 按钮默认点击音效
+        if component.button_sound:
+            default_activate_sound = component.button_sound
 
         # 重置缩进级别
         self.reset_indent_level()
         default_actions = component.custom_data if component.custom_data else 'NullAction()'
-        param_str = self.generate_button_parameter(default_title, default_actions, icon_image)
+        param_str = self.generate_button_default_parameter(default_title, default_actions, icon_image, default_activate_sound)
         self.screen_definition_head.append(f"screen {button_name}({param_str}):")
         # self.screen_definition_head.append(f"screen {button_name}(title='{default_title}', actions={default_actions}, icon=Null()):")
 
@@ -1868,6 +2388,8 @@ def generate_button_screen(self, component : FguiComponent):
         self.screen_ui_code.append(f"{self.indent_str}size {icon_size}")
         self.indent_level_down()
         # 一些按钮特性
+        # 点击音效
+        self.screen_ui_code.append(f"{self.indent_str}activate_sound activate_sound")
         # focus_mask，对应FGUI中的点击“测试”。
         if component.hit_test:
             # 根据引用src查找image名
@@ -1878,7 +2400,6 @@ def generate_button_screen(self, component : FguiComponent):
 
         self.screen_code.extend(self.screen_definition_head)
         self.screen_code.extend(self.screen_ui_code)
-        # self.renpy_code.extend(self.screen_code)
 
     def trans_text_align(self, text_horizontal_align="left", text_vertical_align="top"):
         return self.align_dict.get(text_horizontal_align, 0.5), self.align_dict.get(text_vertical_align, 0.5)
@@ -2102,7 +2623,6 @@ def generate_text_displayable(self, fgui_text):
             print("It is not a text displayable.")
             return text_code
 
-
         end_indent_level = 1
 
         # 根据显示控制器gearDisplay设置显示条件
@@ -2114,15 +2634,24 @@ def generate_text_displayable(self, fgui_text):
 
         # 直接定义text组件。
         # 处理换行符
-        text_str = fgui_text.text.replace("\n", "\\n").replace("\r", "\\n")
+        if fgui_text.single_line:
+            # 设置为单行，删除所有换行符
+            text_str = fgui_text.text.replace("\n", "").replace("\r", "")
+        else:
+            # 默认转义换行符
+            text_str = fgui_text.text.replace("\n", "\\n").replace("\r", "\\n")
         # 需要根据is_input区分文本组件与输入框
         if fgui_text.is_input:
             # Ren'Py中的直接使用input组件无法在多个输入框的情况下切换焦点，也无法点击空白区域让所有输入框失去焦点。
             # 需要使用button作为父组件，与InputValue关联。整个界面添加一个dismiss，空白区域点击事件让输入框失去焦点。
-            # pass
+
             # 添加InputValue变量。
-            self.screen_variable_code.append(f"{self.indent_str}default {fgui_text.name} = '{fgui_text.text}'")
-            self.screen_variable_code.append(f"{self.indent_str}default {fgui_text.name}_input_value = ScreenVariableInputValue('{fgui_text.name}', default=False)")
+            if fgui_text.custom_data:
+                self.add_game_global_variables(fgui_text.custom_data, fgui_text.text)
+                self.screen_variable_code.append(f"{self.indent_str}default {fgui_text.name}_input_value = VariableInputValue('{fgui_text.custom_data}', default=False)")
+            else:
+                self.screen_variable_code.append(f"{self.indent_str}default {fgui_text.name} = '{fgui_text.text}'")
+                self.screen_variable_code.append(f"{self.indent_str}default {fgui_text.name}_input_value = ScreenVariableInputValue('{fgui_text.name}', default=False)")
             if self.screen_has_dismiss == False:
                 self.screen_has_dismiss = True
                 # 若prompt不为空，需要在screen中添加一个输入检测函数
@@ -2146,7 +2675,7 @@ def generate_text_displayable(self, fgui_text):
         text_code.append(f"{self.indent_str}at transform:")
         self.indent_level_up()
         text_code.append(f"{self.indent_str}anchor {fgui_text.pivot}")
-        if not fgui_text.pivot_is_anchor:
+        if fgui_text.pivot_is_anchor:
             size = fgui_text.size
             xoffset = int(fgui_text.pivot[0] * size[0])
             yoffset = int(fgui_text.pivot[1] * size[1])
@@ -2227,8 +2756,8 @@ def generate_text_displayable(self, fgui_text):
         xalign, yalign = self.trans_text_align(fgui_text.align, fgui_text.v_align)
         text_code.append(f"{self.indent_str}textalign {xalign}")
         # 不自动换行
-        text_code.append(f"{self.indent_str}layout 'nobreak'")
-            
+        if fgui_text.single_line:
+            text_code.append(f"{self.indent_str}layout 'nobreak'")
 
         # 粗体、斜体、下划线、删除线
         if fgui_text.bold:
@@ -2249,7 +2778,11 @@ def generate_text_displayable(self, fgui_text):
             if fgui_text.is_password:
                 text_code.append(f"{self.indent_str}mask '*'")
             # FGUI的输入限制使用正则表达式。在Ren'Py中使用字符串。此处仅为占位，无效果。
-            text_code.append(f"{self.indent_str}allow {{}}")
+            # text_code.append(f"{self.indent_str}allow {{}}")
+            if fgui_text.restrict:
+                text_code.append(f"{self.indent_str}allow '{fgui_text.restrict}'")
+            else:
+                text_code.append(f"{self.indent_str}allow {{}}")
             text_code.append(f"{self.indent_str}exclude {{}}")
         if fgui_text.is_input:
             self.indent_level_down()
@@ -2267,8 +2800,11 @@ def generate_list_displayable(self, fgui_list : FguiList) -> list[str]:
             return list_code
         component_name = self.current_component_name
 
-        # 默认引用组件可能是图片或其他组件，后续处理方式不同。
-        default_item = self.fgui_assets.get_component_by_id(fgui_list.default_item_id)
+        # 默认引用组件可能是None、图片或其他组件，后续处理方式不同。
+        if fgui_list.default_item_id:
+            default_item = self.fgui_assets.get_component_by_id(fgui_list.default_item_id)
+        else:
+            default_item = None
         default_item_type = None
         default_item_name = None
         # 若为组件
@@ -2282,7 +2818,8 @@ def generate_list_displayable(self, fgui_list : FguiList) -> list[str]:
                 default_item_type = 'image'
             else:
                 print("Ref com not found.")
-                return list_code
+                print("List default item is None.")
+                # return list_code
 
         list_length = fgui_list.get_item_list_length()
 
@@ -2294,7 +2831,8 @@ def generate_list_displayable(self, fgui_list : FguiList) -> list[str]:
         
         # 计算screen入参。
         overflow = None
-        layout = 'row'
+        # layout = 'row'
+        layout = fgui_list.layout
         row_num = 1
         column_num = 1
         transpose = False
@@ -2321,8 +2859,8 @@ def generate_list_displayable(self, fgui_list : FguiList) -> list[str]:
             row_num = fgui_list.line_item_count
             column_num = math.ceil(list_length / row_num)
         else:
-            row_num = fgui_list.line_item_count
-            column_num = fgui_list.line_item_count2
+            column_num = fgui_list.line_item_count
+            row_num = fgui_list.line_item_count2
 
         xspacing = fgui_list.col_gap
         yspacing = fgui_list.line_gap
@@ -2339,7 +2877,21 @@ def generate_list_displayable(self, fgui_list : FguiList) -> list[str]:
             # 非默认元素
             if item.item_url:
                 # TODO 非默认元素待处理
-                pass
+                # 若为组件
+                item_component = self.fgui_assets.get_component_by_id(item.item_id)
+                if item_component:# 非按钮组件可能包含多个子组件，直接引用会出现vpgrid overfull错误
+                    list_code.append(f"{self.indent_str}fixed:")
+                    self.indent_level_up()
+                    list_code.append(f"{self.indent_str}xysize {item_component.size}")
+                    list_code.append(f"{self.indent_str}use {item_component.name}()")
+                    self.indent_level_down()
+                else:
+                    image_item_name = self.fgui_assets.get_componentname_by_id(item.item_id)
+                    if image_item_name:
+                        list_code.append(f"{self.indent_str}add '{image_item_name}'")
+                    else:
+                        print(f"Image item not found: {item.item_id}")
+                # pass
             # 默认元素
             else:
                 if default_item_type == "image":
@@ -2370,16 +2922,134 @@ def generate_list_screen(self, fgui_list : FguiList, component_name : str) -> bo
         list_screen_template_content = list_screen_template_content.replace('{component_name}', f"{component_name}")
         list_screen_template_content = list_screen_template_content.replace('{list_name}', f"{fgui_list.name}")
         list_screen_template_content = list_screen_template_content.replace('{list_screen_name}', f"{component_name}_{fgui_list.name}")
+        side_layout = "'c r b'"
+        unscrollable_type = None
+        if fgui_list.scroll_bar_flags:
+            if fgui_list.scrollbar_flags_dict['display_on_left']:
+                side_layout = "'c l b'"
+            if fgui_list.scrollbar_flags_dict['hide_when_not_overflow']:
+                unscrollable_type = "'hide'"
+        list_screen_template_content = list_screen_template_content.replace('{side_layout}', f"{side_layout}")
         vertical_scrollbar_component, horizontal_scrollbar_component = self.get_scrollbar_style(fgui_list.scrollbar_res)
-        vscrollbar_style = vertical_scrollbar_component.name if vertical_scrollbar_component else self.default_scrollbar_style
-        hscrollbar_style = horizontal_scrollbar_component.name if horizontal_scrollbar_component else self.default_scrollbar_style
+        if vertical_scrollbar_component:
+            vscrollbar_style = vertical_scrollbar_component.name
+            vertical_scrollbar_size = (vertical_scrollbar_component.size[0], fgui_list.size[1])
+        else:
+            vscrollbar_style = self.default_scrollbar_style
+            vertical_scrollbar_size = (0, 0)
+        if horizontal_scrollbar_component:
+            hscrollbar_style = horizontal_scrollbar_component.name
+            horizontal_scrollbar_size = (fgui_list.size[0], horizontal_scrollbar_component.size[1])
+        else:
+            hscrollbar_style = self.default_scrollbar_style
+            horizontal_scrollbar_size = (0, 0)
         list_screen_template_content = list_screen_template_content.replace('{vscrollbar_style}', f"'{vscrollbar_style}'")
         list_screen_template_content = list_screen_template_content.replace('{hscrollbar_style}', f"'{hscrollbar_style}'")
-
+        list_screen_template_content = list_screen_template_content.replace('{vertical_scrollbar_size}', f"{vertical_scrollbar_size}")
+        list_screen_template_content = list_screen_template_content.replace('{horizontal_scrollbar_size}', f"{horizontal_scrollbar_size}")
+        list_screen_template_content = list_screen_template_content.replace('{unscrollable_type}', f"{unscrollable_type}")
         self.screen_code.append(list_screen_template_content)
 
         return True
 
+    def generate_loader_displayable(self, fgui_loader : FguiLoader) -> list[str]:
+        """
+        生成装载器对应的Ren'Py代码。
+        """
+        loader_code = []
+        if not isinstance(fgui_loader, FguiLoader):
+            print("It is not a loader displayable.")
+            return loader_code
+        
+        component_name = self.current_component_name
+        loader_screen_result = self.generate_loader_screen(fgui_loader, component_name)
+        if not loader_screen_result:
+            print(f"Failed to generate {component_name}_{fgui_loader.name} loader screen.")
+            return loader_code
+        
+        params_str = self.generate_loader_screen_params(fgui_loader)
+
+        loader_code.append(f"{self.indent_str}use {component_name}_{fgui_loader.name}({params_str})")
+        return loader_code
+
+    def generate_loader_screen(self, fgui_loader : FguiLoader, component_name : str) -> bool:
+        """
+        生成装载器组件的screen。于其他screen中使用use语句引用。
+        """
+        if not isinstance(fgui_loader, FguiLoader):
+            print("It is not a loader displayable.")
+            return False
+
+        # 根据模板内容替换部分字符串
+        loader_screen_template_content = self.loader_screen_template
+        loader_screen_template_content = loader_screen_template_content.replace('{component_name}', f"{component_name}")
+        loader_screen_template_content = loader_screen_template_content.replace('{loader_name}', f"{fgui_loader.name}")
+        loader_screen_template_content = loader_screen_template_content.replace('{loader_screen_name}', f"{component_name}_{fgui_loader.name}")
+        self.screen_code.append(loader_screen_template_content)
+        return True
+
+    def generate_loader_screen_params(self, fgui_loader : FguiLoader) -> str:
+        """
+        生成装载器组件screen的参数字符串。
+        """
+        if not isinstance(fgui_loader, FguiLoader):
+            print("It is not a loader displayable.")
+            return ''
+
+        child = None
+        child_name = 'Null()'
+        child_type = None
+        xysize = fgui_loader.size
+        pos = fgui_loader.xypos
+        if fgui_loader.item_url:
+            child = self.fgui_assets.package_desc.get_image_by_id(fgui_loader.item_url)
+            if child:
+                child_type = "'image'"
+                child_name = f"'{child.name}'"
+            else:
+                child = self.fgui_assets.get_component_by_id(fgui_loader.item_url)
+                if child:
+                    child_type = "'component'"
+                    child_name = child.name
+                else:
+                    child_name = 'Null()'
+        else:
+            child_name = 'Null()'
+        fit_xysize = (None, None)
+        fit_type = None
+        print(f"fgui_loader.fill_type: {fgui_loader.fill_type}")
+        if fgui_loader.fill_type:
+            if fgui_loader.fill_type == "scale":
+                fit_type = "'contain'"
+            elif fgui_loader.fill_type == "scaleNoBorder":
+                fit_type = "'cover'"
+            elif fgui_loader.fill_type == "scaleMatchHeight":
+                fit_type = "'cover'"
+                fit_xysize = (None, 1.0)
+            elif fgui_loader.fill_type == "scaleMatchWidth":
+                fit_type = "'cover'"
+                fit_xysize = (1.0, None)
+            elif fgui_loader.fill_type == "scaleFree":
+                fit_type = "'fit'"
+                fit_xysize = (1.0, 1.0)
+        if fgui_loader.shrink_only:
+            fit_type = "'scale-down'"
+            print(f"child.size: {child.size}, xysize: {xysize}")
+            if child.size[0] < xysize[0]:
+                fit_xsize = 1.0
+            else:
+                fit_xsize = None
+            if child.size[1] < xysize[1]:
+                fit_ysize = 1.0
+            else:
+                fit_ysize = None
+                fit_ysize = 1.0
+            fit_xysize = (fit_xsize, fit_ysize)
+        xalign = self.align_dict.get(fgui_loader.align, 0.0)
+        yalign = self.align_dict.get(fgui_loader.v_align, 0.0)
+
+        return f"xysize={xysize}, pos={pos}, child={child_name}, child_type={child_type}, fit_xysize={fit_xysize}, fit_type={fit_type}, child_align=({xalign},{yalign})"
+
     def generate_renpy_code(self):
         """生成完整的Ren'Py代码"""
 
@@ -2400,19 +3070,23 @@ def generate_renpy_code(self):
         # 生成图像定义
         self.generate_image_definitions()
 
+        # 生成音频定义
+        self.generate_sound_definitions()
+
         for component in self.fgui_assets.fgui_component_set:
             if component.extention == 'Button':
                 self.generate_button_screen(component)
             elif component.extention == 'ScrollBar':
-                self.generate_scroll_bar_style(component)
+                self.generate_scrollbar_style(component)
             elif component.extention == 'Label':
-                pass
+                self.generate_label_screen(component)
             elif component.extention == 'Slider':
                 self.generate_slider_style(component)
             elif component.extention == 'ComboBox':
                 pass
+                # self.generate_combobox_screen(component)
             elif component.extention == 'ProgressBar':
-                pass
+                self.generate_progressbar_style(component)
             else:
                 self.generate_screen(component)
         
@@ -2591,6 +3265,18 @@ def copy_atlas_files(self, source_dir, target_dir):
 
         return len(atlas_files)
 
+    def copy_sound_files(self, source_dir, target_dir):
+        """复制音频文件到目标目录"""
+        # 所有音频文件
+        sound_files = self.fgui_assets.fgui_sound_dicts.values()
+        for sound_file in sound_files:
+            source_path = os.path.join(source_dir, sound_file)
+            target_path = os.path.join(target_dir, sound_file)
+            shutil.copy2(source_path, target_path)
+            print(f"✓ 复制音频文件: {sound_file} ")
+
+        return len(sound_files)
+
 def convert(argv):
     """
     主函数：解析FguiDemoPackage并转换为Ren'Py代码
@@ -2605,10 +3291,18 @@ def convert(argv):
                         help='输入FairyGUI资源文件所在目录名 (目录中需存在同名 .bytes 文件)')
     parser.add_argument('-o', '--output', type=str, 
                         help='输出Ren\'Py项目基目录路径 (即Ren\'Py项目根目录)')
-    
+    parser.add_argument('--main-menu-title', type=str, metavar='KEY=VALUE',
+                        help='主菜单标题，键值对用空格分隔写在一个字符串里，如: "text_str=标题 text_color=#ff0000"')
+    parser.add_argument('--main-menu-logo', type=str,
+                        help='主菜单logo（可选）')
     # 解析命令行参数
     args = parser.parse_args(argv[1:] if argv and len(argv) > 1 else [])
     
+    # 将 --main-menu-title 的单个字符串按空格拆成键值对列表后解析为 MainMenuTitle
+    raw = getattr(args, 'main_menu_title', None)
+    pairs = raw.strip().split() if raw else None
+    main_menu_title = _parse_main_menu_title_kv(pairs)
+    
     # 检查必需的参数
     if not args.input:
         print("错误: 必须指定输入目录 (-i 或 --input)")
@@ -2650,16 +3344,17 @@ def convert(argv):
         game_dir = os.path.join(renpy_base_dir, "game")
         images_dir = os.path.join(game_dir, "images")
         scripts_dir = os.path.join(game_dir, "scripts")
-
+        audio_dir = os.path.join(game_dir, "audio")
         # 创建目录
         os.makedirs(game_dir, exist_ok=True)
         os.makedirs(images_dir, exist_ok=True)
         os.makedirs(scripts_dir, exist_ok=True)
+        os.makedirs(audio_dir, exist_ok=True)
         print(f"创建目录结构: {renpy_base_dir}/")
         print(f"├── game/")
         print(f"└── game/images/")
         print(f"└── game/scripts/")
-
+        print(f"└── game/audio/")
         # 创建FguiAssets对象
         print("\n正在解析FairyGUI资源...")
         fgui_assets = FguiAssets(fgui_project_path)
@@ -2671,6 +3366,10 @@ def convert(argv):
         converter.game_dir = game_dir
         converter.scripts_dir = scripts_dir
         converter.images_dir = images_dir
+        converter.audio_dir = audio_dir
+        # 设置自定义参数，用于替换Fgui发布资源中的值。
+        converter.main_menu_title = main_menu_title
+        converter.main_menu_logo = args.main_menu_logo
         print("转换器创建完成")
 
         # 生成Ren'Py代码
@@ -2681,12 +3380,14 @@ def convert(argv):
         # 保存.rpy文件到game目录
         global_variables_output_file = os.path.join(converter.scripts_dir, "preppipe_global_variables.rpy")
         image_definition_output_file = os.path.join(converter.scripts_dir, "preppipe_image_definition.rpy")
+        sound_definition_output_file = os.path.join(converter.scripts_dir, "preppipe_sound_definition.rpy")
         style_output_file = os.path.join(converter.scripts_dir, "preppipe_styles.rpy")
         screen_output_file = os.path.join(converter.scripts_dir, "preppipe_screens.rpy")
 
         converter.save_code_to_file(global_variables_output_file, converter.game_global_variables_code)
         converter.image_definition_code.extend(converter.graph_definition_code)
         converter.save_code_to_file(image_definition_output_file, converter.image_definition_code)
+        converter.save_code_to_file(sound_definition_output_file, converter.sound_definition_code)
         converter.save_code_to_file(style_output_file, converter.style_code)
         converter.save_code_to_file(screen_output_file, converter.screen_code)
 
@@ -2699,10 +3400,15 @@ def convert(argv):
 
         # 复制图集文件到images目录
         print("\n正在复制图集文件...")
-        current_dir = os.getcwd()
+        # current_dir = os.getcwd()
         atlas_count = converter.copy_atlas_files(fgui_project_path, converter.images_dir)
         print(f"复制了 {atlas_count} 个图集文件")
 
+        # 复制音频文件到audio目录
+        print("\n正在复制音频文件...")
+        audio_count = converter.copy_sound_files(fgui_project_path, converter.audio_dir)
+        print(f"复制了 {audio_count} 个音频文件")
+
         # 一些清理
         fgui_assets.clear()
         converter.cleanup()
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/02_renpy_shader.rpy b/src/fgui_converter/utils/renpy/renpy_templates/02_renpy_shader.rpy
index 2256c87..16bd0df 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/02_renpy_shader.rpy
+++ b/src/fgui_converter/utils/renpy/renpy_templates/02_renpy_shader.rpy
@@ -4,16 +4,19 @@ init python:
         uniform vec4 u_rectangle_color;
         uniform vec4 u_stroke_color;
         uniform vec2 u_model_size;
-        uniform float u_radius;
+        uniform vec4 u_radius;
         uniform float u_thickness;
-        attribute vec2 a_tex_coord;
+        attribute vec4 a_position;
         varying vec2 v_tex_coord;
     """, vertex_300="""
-        v_tex_coord = a_tex_coord;
+        v_tex_coord = a_position.xy / u_model_size;
     """,fragment_functions="""
-    float roundedBoxSDF(vec2 pos, vec2 border, float radius){
-        vec2 dis = abs(pos) - border + vec2(radius,radius);
-        return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - radius;
+    float roundedBoxSDF(vec2 pos, vec2 border, vec4 radius){
+        vec4 r = radius;
+        r.xy = (pos.x<0.0) ? radius.xz : radius.yw;
+        r.x = (pos.y<0.0) ? r.x : r.y;
+        vec2 dis = abs(pos) - border + vec2(r.x,r.x);
+        return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - r.x;
     }
     """,fragment_300="""
         vec2 uv = v_tex_coord - vec2(0.5, 0.5);
@@ -31,17 +34,20 @@ init python:
         uniform vec4 u_rectangle_color;
         uniform vec4 u_stroke_color;
         uniform vec2 u_model_size;
-        uniform float u_radius;
+        uniform vec4 u_radius;
         uniform float u_thickness;
         uniform float u_edge_softness;
-        attribute vec2 a_tex_coord;
+        attribute vec4 a_position;
         varying vec2 v_tex_coord;
     """, vertex_300="""
-        v_tex_coord = a_tex_coord;
+        v_tex_coord = a_position.xy / u_model_size;
     """,fragment_functions="""
-    float roundedBoxSDF(vec2 pos, vec2 border, float radius){
-        vec2 dis = abs(pos) - border + vec2(radius,radius);
-        return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - radius;
+    float roundedBoxSDF(vec2 pos, vec2 border, vec4 radius){
+        vec4 r = radius;
+        r.xy = (pos.x<0.0) ? radius.xz : radius.yw;
+        r.x = (pos.y<0.0) ? r.x : r.y;
+        vec2 dis = abs(pos) - border + vec2(r.x,r.x);
+        return length(max(dis, 0.0)) + min(max(dis.x, dis.y), 0.0) - r.x;
     }
     """,fragment_300="""
         vec2 uv = v_tex_coord - vec2(0.5, 0.5);
@@ -60,10 +66,10 @@ init python:
         uniform vec4 u_stroke_color;
         uniform vec2 u_model_size;
         uniform float u_thickness;
-        attribute vec2 a_tex_coord;
+        attribute vec4 a_position;
         varying vec2 v_tex_coord;
     """, vertex_300="""
-        v_tex_coord = a_tex_coord;
+        v_tex_coord = a_position.xy / u_model_size;
     """,fragment_300="""
         vec2 uv = v_tex_coord - vec2(0.5, 0.5);
         float out_distance = length(uv);
@@ -82,10 +88,10 @@ init python:
         uniform vec2 u_model_size;
         uniform float u_thickness;
         uniform float u_edge_softness;
-        attribute vec2 a_tex_coord;
+        attribute vec4 a_position;
         varying vec2 v_tex_coord;
     """, vertex_300="""
-        v_tex_coord = a_tex_coord;
+        v_tex_coord = a_position.xy / u_model_size;
     """,fragment_300="""
         vec2 uv = v_tex_coord - vec2(0.5, 0.5);
         float out_distance = length(uv);
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipseAA_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipseAA_template.txt
index 6777d81..0675ce9 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipseAA_template.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipseAA_template.txt
@@ -1,6 +1,6 @@
 # 抗锯齿的椭圆(圆形)图形定义
 image {image_name}:
-    Model().child(Solid('000')).shader('CursedOctopus.ellipseAA').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 0.01)
+    Model().shader('CursedOctopus.ellipseAA').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 0.01)
     xysize {xysize}
     pos {pos}
     anchor {anchor}
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipse_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipse_template.txt
index 25aaa92..97d82f9 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipse_template.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_ellipse_template.txt
@@ -1,6 +1,6 @@
 # 椭圆(圆形)图形定义
 image {image_name}:
-    Model().child(Solid('000')).shader('CursedOctopus.ellipse').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness})
+    Model().shader('CursedOctopus.ellipse').uniform('u_ellipse_color', {ellipse_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_thickness', {stroke_thickness})
     xysize {xysize}
     pos {pos}
     anchor {anchor}
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_font_map_definition.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_font_map_definition.txt
index 3255979..b7c2a1c 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_font_map_definition.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_font_map_definition.txt
@@ -1,7 +1,7 @@
 init python:
     import os
     font_name_list = [{font_name_list}]
-    font_file_ext = [".ttf", ".ttc", ".otf"]
+    font_file_ext = [".ttf", ".ttc", ".otf", ".woff", ".woff2"]
 
     for font_name in font_name_list:
         for font_ext in font_file_ext:
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_list_screen_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_list_screen_template.txt
index 57eb36a..1f6916f 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_list_screen_template.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_list_screen_template.txt
@@ -43,12 +43,10 @@ screen {list_screen_name}(overflow=None, layout='row', row_num=1, column_num=1,
                     transclude
     else:
         frame:
-            has side 'c r b'
+            has side {side_layout}  pos pos xysize xysize
             viewport id '{list_screen_name}_vp':
                 draggable True
                 mousewheel True
-                pos pos
-                xysize xysize
                 if layout == 'row':
                     hbox:
                         spacing xspacing
@@ -66,10 +64,10 @@ screen {list_screen_name}(overflow=None, layout='row', row_num=1, column_num=1,
             vbar:
                 value YScrollValue('{list_screen_name}_vp')
                 style {vscrollbar_style}
-                ysize xysize[1]
-                pos pos
+                xysize {vertical_scrollbar_size}
+                unscrollable {unscrollable_type}
             bar:
                 value XScrollValue('{list_screen_name}_vp')
                 style {hscrollbar_style}
-                xsize xysize[0]
-                pos pos
+                xysize {horizontal_scrollbar_size}
+                unscrollable {unscrollable_type}
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_loader_screen_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_loader_screen_template.txt
new file mode 100644
index 0000000..25557e2
--- /dev/null
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_loader_screen_template.txt
@@ -0,0 +1,14 @@
+# {component_name} 内部装载器 {loader_name}
+screen {loader_screen_name}(xysize=(0,0), pos=(0,0), child=None, child_type="image", fit_xysize=(None,None), fit_type=None, child_align=(0.0,0.0)):
+    frame:
+        pos pos
+        xysize xysize
+        if child_type == "image":
+            add child:
+                xysize fit_xysize
+                fit fit_type
+                align child_align
+        elif child_type == "screen":
+            use expression child
+        else:
+            add null
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangleAA_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangleAA_template.txt
index f795118..b456b2f 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangleAA_template.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangleAA_template.txt
@@ -1,6 +1,6 @@
 # 带光滑渐变的圆角矩形图形定义
 image {image_name}:
-    Model().child(Solid('000')).shader('CursedOctopus.rectangleAA').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 1.0)
+    Model().shader('CursedOctopus.rectangleAA').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness}).uniform('u_edge_softness', 1.0)
     xysize {xysize}
     pos {pos}
     anchor {anchor}
diff --git a/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangle_template.txt b/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangle_template.txt
index b58d746..4b4f874 100644
--- a/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangle_template.txt
+++ b/src/fgui_converter/utils/renpy/renpy_templates/renpy_rectangle_template.txt
@@ -1,6 +1,6 @@
 # 圆角矩形图形定义
 image {image_name}:
-    Model().child(Solid('000')).shader('CursedOctopus.rectangle').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness})
+    Model().shader('CursedOctopus.rectangle').uniform('u_rectangle_color', {rectangle_color}).uniform('u_stroke_color', {stroke_color}).uniform('u_model_size', {image_size}).uniform('u_radius', {round_radius}).uniform('u_thickness', {stroke_thickness})
     xysize {xysize}
     pos {pos}
     anchor {anchor}
diff --git a/src/preppipe_gui_pyside6/toolwidgets/home.py b/src/preppipe_gui_pyside6/toolwidgets/home.py
index 72215fe..4f3a00e 100644
--- a/src/preppipe_gui_pyside6/toolwidgets/home.py
+++ b/src/preppipe_gui_pyside6/toolwidgets/home.py
@@ -8,6 +8,7 @@
 from .maininput import MainInputWidget
 from .setting import SettingWidget
 from .assetbrowser import AssetBrowserWidget
+from .scriptfloweditor import ScriptFlowEditorWidget
 
 class HomeWidget(QWidget, ToolWidgetInterface):
   _tr_toolname_home = TR_gui_mainwindow.tr("toolname_home",
@@ -39,6 +40,7 @@ class HomeWidget(QWidget, ToolWidgetInterface):
     MainInputWidget,
     SettingWidget,
     AssetBrowserWidget,
+    ScriptFlowEditorWidget,
   ]
   @classmethod
   def getToolInfo(cls) -> ToolWidgetInfo:
diff --git a/src/preppipe_gui_pyside6/toolwidgets/scriptfloweditor.py b/src/preppipe_gui_pyside6/toolwidgets/scriptfloweditor.py
new file mode 100644
index 0000000..27670f2
--- /dev/null
+++ b/src/preppipe_gui_pyside6/toolwidgets/scriptfloweditor.py
@@ -0,0 +1,953 @@
+# -*- coding: utf-8 -*-
+"""脚本流程编辑器工具：嵌入 NodeGraphQt-PySide6，与 ScriptFlowEditor 数据模型双向同步。"""
+
+from __future__ import annotations
+
+import json
+import re
+from pathlib import Path
+
+from PySide6.QtCore import Qt, QPointF
+from PySide6.QtGui import QAction, QKeySequence, QShortcut
+from PySide6.QtWidgets import (
+    QButtonGroup,
+    QCheckBox,
+    QDialog,
+    QDialogButtonBox,
+    QDoubleSpinBox,
+    QFileDialog,
+    QFormLayout,
+    QGraphicsProxyWidget,
+    QHBoxLayout,
+    QMenu,
+    QPlainTextEdit,
+    QPushButton,
+    QRadioButton,
+    QSpinBox,
+    QVBoxLayout,
+    QWidget,
+)
+from NodeGraphQt import BaseNode, NodeGraph
+from NodeGraphQt.qgraphics.pipe import PipeItem
+
+from ScriptFlowEditor.models import (
+    FlagType,
+    FlagVariable,
+    GameScriptFlow,
+    SegmentPath,
+    StorySegment,
+)
+
+from preppipe.language import TranslationDomain
+
+from ..mainwindowinterface import MainWindowInterface
+from ..toolwidgetinterface import ToolWidgetInterface, ToolWidgetInfo, ToolWidgetUniqueLevel
+
+# 节点类型标识
+_SEGMENT_NODE_TYPE = "preppipe.scriptflow.SegmentNode"
+_FLAG_NODE_TYPE = "preppipe.scriptflow.FlagNode"
+
+TR_gui_scriptfloweditor = TranslationDomain("gui_scriptfloweditor")
+
+
+class SegmentContentEditDialog(QDialog):
+    """编辑剧情段落正文的对话框，关闭时若接受则把文本写入对应 StorySegment.content。"""
+
+    def __init__(
+        self,
+        parent: QWidget | None,
+        segment: StorySegment,
+        *,
+        title: str = "",
+        placeholder: str = "",
+    ):
+        super().__init__(parent)
+        self._segment = segment
+        self.setWindowTitle(title or ("Edit Segment Content" if not parent else parent.tr("编辑剧情文本")))
+        layout = QVBoxLayout(self)
+        self._text_edit = QPlainTextEdit(self)
+        self._text_edit.setPlaceholderText(placeholder or ("Enter segment content…" if not parent else parent.tr("在此输入该段落的剧情文本…")))
+        self._text_edit.setPlainText(segment.content or "")
+        layout.addWidget(self._text_edit)
+        self._buttons = QDialogButtonBox(
+            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+        )
+        self._buttons.accepted.connect(self.accept)
+        self._buttons.rejected.connect(self.reject)
+        layout.addWidget(self._buttons)
+
+    def accept(self) -> None:
+        self._segment.content = self._text_edit.toPlainText()
+        super().accept()
+
+
+class SegmentCommentEditDialog(QDialog):
+    """编辑剧情段落注释的对话框，关闭时若接受则把文本写入对应 StorySegment.comment。"""
+
+    def __init__(
+        self,
+        parent: QWidget | None,
+        segment: StorySegment,
+        *,
+        title: str = "",
+        placeholder: str = "",
+    ):
+        super().__init__(parent)
+        self._segment = segment
+        self.setWindowTitle(title or "Edit Comment")
+        layout = QVBoxLayout(self)
+        self._text_edit = QPlainTextEdit(self)
+        self._text_edit.setPlaceholderText(placeholder or "Enter comment…")
+        self._text_edit.setPlainText(segment.comment or "")
+        layout.addWidget(self._text_edit)
+        self._buttons = QDialogButtonBox(
+            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+        )
+        self._buttons.accepted.connect(self.accept)
+        self._buttons.rejected.connect(self.reject)
+        layout.addWidget(self._buttons)
+
+    def accept(self) -> None:
+        self._segment.comment = self._text_edit.toPlainText()
+        super().accept()
+
+
+class PathConditionEditDialog(QDialog):
+    """编辑连线分支条件的对话框，关闭时若接受则把文本写入对应 SegmentPath.condition_expression。"""
+
+    def __init__(
+        self,
+        parent: QWidget | None,
+        segment_path: SegmentPath,
+        *,
+        title: str = "",
+        placeholder: str = "",
+    ):
+        super().__init__(parent)
+        self._path = segment_path
+        self.setWindowTitle(title or "Edit Branch Condition")
+        layout = QVBoxLayout(self)
+        self._text_edit = QPlainTextEdit(self)
+        self._text_edit.setPlaceholderText(placeholder or "Enter condition expression…")
+        self._text_edit.setPlainText(segment_path.condition_expression or "")
+        layout.addWidget(self._text_edit)
+        self._buttons = QDialogButtonBox(
+            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+        )
+        self._buttons.accepted.connect(self.accept)
+        self._buttons.rejected.connect(self.reject)
+        layout.addWidget(self._buttons)
+
+    def accept(self) -> None:
+        self._path.condition_expression = self._text_edit.toPlainText()
+        super().accept()
+
+
+class FlagCommentEditDialog(QDialog):
+    """编辑 Flag 变量注释的对话框，关闭时若接受则把文本写入对应 FlagVariable.comment。"""
+
+    def __init__(
+        self,
+        parent: QWidget | None,
+        flag_var: FlagVariable,
+        *,
+        title: str = "",
+        placeholder: str = "",
+    ):
+        super().__init__(parent)
+        self._flag_var = flag_var
+        self.setWindowTitle(title or "Edit Comment")
+        layout = QVBoxLayout(self)
+        self._text_edit = QPlainTextEdit(self)
+        self._text_edit.setPlaceholderText(placeholder or "Enter comment…")
+        self._text_edit.setPlainText(flag_var.comment or "")
+        layout.addWidget(self._text_edit)
+        self._buttons = QDialogButtonBox(
+            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+        )
+        self._buttons.accepted.connect(self.accept)
+        self._buttons.rejected.connect(self.reject)
+        layout.addWidget(self._buttons)
+
+    def accept(self) -> None:
+        self._flag_var.comment = self._text_edit.toPlainText()
+        super().accept()
+
+
+class FlagInitialValueEditDialog(QDialog):
+    """编辑 Flag 变量初始值的对话框，根据 flag_type 显示布尔单选/整数/浮点数输入。"""
+
+    def __init__(
+        self,
+        parent: QWidget | None,
+        flag_var: FlagVariable,
+        *,
+        title: str = "",
+    ):
+        super().__init__(parent)
+        self._flag_var = flag_var
+        self.setWindowTitle(title or "Edit Flag Initial Value")
+        layout = QFormLayout(self)
+        if flag_var.flag_type == FlagType.BOOL:
+            group = QButtonGroup(self)
+            self._radio_true = QRadioButton("True", self)
+            self._radio_false = QRadioButton("False", self)
+            group.addButton(self._radio_true)
+            group.addButton(self._radio_false)
+            self._radio_true.setChecked(bool(flag_var.initial_value))
+            self._radio_false.setChecked(not bool(flag_var.initial_value))
+            bool_row = QWidget(self)
+            bool_layout = QHBoxLayout(bool_row)
+            bool_layout.setContentsMargins(0, 0, 0, 0)
+            bool_layout.addWidget(self._radio_true)
+            bool_layout.addWidget(self._radio_false)
+            self._widget = bool_row
+        elif flag_var.flag_type == FlagType.INT:
+            self._widget = QSpinBox(self)
+            self._widget.setRange(-(2**31), 2**31 - 1)
+            self._widget.setValue(int(flag_var.initial_value))
+        else:
+            self._widget = QDoubleSpinBox(self)
+            self._widget.setDecimals(6)
+            self._widget.setRange(-1e308, 1e308)
+            self._widget.setValue(float(flag_var.initial_value))
+        layout.addRow(self._widget)
+        self._buttons = QDialogButtonBox(
+            QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
+        )
+        self._buttons.accepted.connect(self.accept)
+        self._buttons.rejected.connect(self.reject)
+        layout.addRow(self._buttons)
+
+    def accept(self) -> None:
+        w = self._widget
+        if self._flag_var.flag_type == FlagType.BOOL:
+            self._flag_var.initial_value = self._radio_true.isChecked()
+        elif self._flag_var.flag_type == FlagType.INT:
+            self._flag_var.initial_value = w.value()
+        else:
+            self._flag_var.initial_value = w.value()
+        super().accept()
+
+
+class SegmentNode(BaseNode):
+    """剧情段落节点：一个输入、一个输出，对应数据层 StorySegment。"""
+    __identifier__ = "preppipe.scriptflow"
+    NODE_NAME = "Segment"
+
+    def __init__(self):
+        super().__init__()
+        self.add_input("in", multi_input=True, display_name=True)
+        self.add_output("out", display_name=True)
+
+
+class FlagNode(BaseNode):
+    """Flag 节点：无端口、不允许连线，对应数据层 FlagVariable；中间显示 initial_value。"""
+    __identifier__ = "preppipe.scriptflow"
+    NODE_NAME = "Flag"
+
+    def __init__(self):
+        super().__init__()
+        # 不添加 input/output，节点不可连线
+        self.add_text_input("initial_value_display", label="", text="—")
+        w = self.get_widget("initial_value_display")
+        le = w.get_custom_widget()
+        le.setReadOnly(True)
+        le.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)  # 避免关闭对话框后弹出 QLineEdit 默认菜单（Select All 等）
+        QGraphicsProxyWidget.setToolTip(w, "")  # 禁用显示初始值的控件的 tooltip（避免库默认加属性名）
+
+
+class ScriptFlowEditorWidget(QWidget, ToolWidgetInterface):
+    """脚本流程编辑器 Tab：内嵌节点图，与 GameScriptFlow 数据层双向同步。"""
+
+    # 多语言：与 setting、home 等一致，使用 TranslationDomain + bind_text，语言切换时由 update_text 更新
+    # _tr_add_node = TR_gui_scriptfloweditor.tr("add_node", en="Add Node", zh_cn="添加节点", zh_hk="添加節點")
+    _tr_add_script_node = TR_gui_scriptfloweditor.tr("add_script_node", en="Add Script Node", zh_cn="增加剧情节点", zh_hk="增加劇情節點")
+    _tr_add_flag = TR_gui_scriptfloweditor.tr("add_flag_variable", en="Add Flag Variable", zh_cn="添加Flag变量", zh_hk="添加Flag變量")
+    # _tr_btn_add_flag = TR_gui_scriptfloweditor.tr("btn_add_flag", en="Add Flag", zh_cn="添加Flag", zh_hk="添加Flag")
+    _tr_btn_add_flag_tooltip = TR_gui_scriptfloweditor.tr("btn_add_flag_tooltip", en="Add a flag variable node.", zh_cn="添加一个 Flag 变量节点", zh_hk="添加一個 Flag 變量節點")
+    _tr_edit_segment_content = TR_gui_scriptfloweditor.tr("edit_segment_content", en="Edit Segment Content", zh_cn="编辑剧情文本", zh_hk="編輯劇情文本")
+    _tr_edit_comment = TR_gui_scriptfloweditor.tr("edit_comment", en="Edit Comment", zh_cn="编辑注释", zh_hk="編輯註釋")
+    _tr_edit_dialog_title = TR_gui_scriptfloweditor.tr("edit_dialog_title", en="Edit Segment Content", zh_cn="编辑剧情文本", zh_hk="編輯劇情文本")
+    _tr_edit_comment_dialog_title = TR_gui_scriptfloweditor.tr("edit_comment_dialog_title", en="Edit Comment", zh_cn="编辑注释", zh_hk="編輯註釋")
+    _tr_edit_comment_dialog_placeholder = TR_gui_scriptfloweditor.tr("edit_comment_dialog_placeholder", en="Enter comment for this node…", zh_cn="在此输入该节点的注释…", zh_hk="在此輸入該節點的註釋…")
+    _tr_edit_dialog_placeholder = TR_gui_scriptfloweditor.tr("edit_dialog_placeholder", en="Enter the segment story text here…", zh_cn="在此输入该段落的剧情文本…", zh_hk="在此輸入該段落的劇情文本…")
+    # _tr_btn_add = TR_gui_scriptfloweditor.tr("btn_add", en="Add Segment Node", zh_cn="增加剧情节点", zh_hk="增加劇情節點")
+    _tr_btn_add_tooltip = TR_gui_scriptfloweditor.tr("btn_add_tooltip", en="Add a new segment node at the top-left of the view.", zh_cn="当前视图左上角新增一个剧情节点", zh_hk="當前視圖左上角新增一個劇情節點")
+    _tr_btn_del = TR_gui_scriptfloweditor.tr("btn_del", en="Delete Selected", zh_cn="删除选中节点", zh_hk="刪除選中節點")
+    _tr_btn_del_tooltip = TR_gui_scriptfloweditor.tr("btn_del_tooltip", en="Delete the selected nodes.", zh_cn="删除当前选中节点", zh_hk="刪除當前選中節點")
+    _tr_chk_acyclic = TR_gui_scriptfloweditor.tr("chk_acyclic", en="Acyclic", zh_cn="无环模式", zh_hk="無環模式")
+    _tr_chk_acyclic_tooltip = TR_gui_scriptfloweditor.tr("chk_acyclic_tooltip", en="When checked, cycles are not allowed.", zh_cn="勾选后，禁止创建闭环连接", zh_hk="勾選後，禁止創建閉環連接")
+    _tr_save_flow = TR_gui_scriptfloweditor.tr("save_flow", en="Save Flow", zh_cn="保存流程", zh_hk="保存流程")
+    _tr_save_tooltip = TR_gui_scriptfloweditor.tr("save_tooltip", en="Save current flow and node positions to a JSON file.", zh_cn="将当前流程与节点位置保存为 JSON 文件", zh_hk="將當前流程與節點位置保存為 JSON 文件")
+    _tr_load_flow = TR_gui_scriptfloweditor.tr("load_flow", en="Load Flow", zh_cn="打开流程", zh_hk="打開流程")
+    _tr_open_tooltip = TR_gui_scriptfloweditor.tr("open_tooltip", en="Load flow and node positions from a JSON file.", zh_cn="从 JSON 文件加载流程与节点位置", zh_hk="從 JSON 文件加載流程與節點位置")
+    _tr_save_dialog_title = TR_gui_scriptfloweditor.tr("save_dialog_title", en="Save Flow", zh_cn="保存流程", zh_hk="保存流程")
+    _tr_open_dialog_title = TR_gui_scriptfloweditor.tr("open_dialog_title", en="Open Flow", zh_cn="打开流程", zh_hk="打開流程")
+    _tr_json_filter = TR_gui_scriptfloweditor.tr("json_filter", en="JSON files (*.json);;All files (*)", zh_cn="JSON 文件 (*.json);;所有文件 (*)", zh_hk="JSON 文件 (*.json);;所有文件 (*)")
+
+    _tr_node_tooltip_ending = TR_gui_scriptfloweditor.tr("node_tooltip_ending", en="Ending segment.", zh_cn="剧本结局节点", zh_hk="劇本結局節點")
+    _tr_node_tooltip_start = TR_gui_scriptfloweditor.tr("node_tooltip_start", en="Start segment.", zh_cn="剧本起始节点", zh_hk="劇本起始節點")
+    _tr_node_tooltip_default = TR_gui_scriptfloweditor.tr("node_tooltip_default", en="Segment node.", zh_cn="新剧情段落节点", zh_hk="新劇情段落節點")
+    _tr_node_tooltip_flag_default = TR_gui_scriptfloweditor.tr("node_tooltip_flag_default", en="Flag variable node.", zh_cn="Flag 变量节点", zh_hk="Flag 變量節點")
+    _tr_node_name_edit_tooltip = TR_gui_scriptfloweditor.tr("node_name_edit_tooltip", en="Double-click to edit node name.", zh_cn="双击编辑节点名称", zh_hk="雙擊編輯節點名稱")
+
+    _tr_flag_change_type = TR_gui_scriptfloweditor.tr("flag_change_type", en="Change Flag Type", zh_cn="修改flag变量类型", zh_hk="修改flag變量類型")
+    _tr_flag_type_bool = TR_gui_scriptfloweditor.tr("flag_type_bool", en="Boolean", zh_cn="布尔型", zh_hk="布爾型")
+    _tr_flag_type_int = TR_gui_scriptfloweditor.tr("flag_type_int", en="Integer", zh_cn="整型", zh_hk="整型")
+    _tr_flag_type_float = TR_gui_scriptfloweditor.tr("flag_type_float", en="Float", zh_cn="浮点型", zh_hk="浮點型")
+    _tr_flag_change_initial = TR_gui_scriptfloweditor.tr("flag_change_initial", en="Change Flag Initial Value", zh_cn="修改flag初始值", zh_hk="修改flag初始值")
+    _tr_flag_initial_dialog_title = TR_gui_scriptfloweditor.tr("flag_initial_dialog_title", en="Edit Flag Initial Value", zh_cn="修改flag初始值", zh_hk="修改flag初始值")
+
+    _tr_edit_branch_condition = TR_gui_scriptfloweditor.tr("edit_branch_condition", en="Edit Branch Condition", zh_cn="编辑分支条件", zh_hk="編輯分支條件")
+    _tr_edit_branch_condition_dialog_title = TR_gui_scriptfloweditor.tr("edit_branch_condition_dialog_title", en="Edit Branch Condition", zh_cn="编辑分支条件", zh_hk="編輯分支條件")
+    _tr_edit_branch_condition_placeholder = TR_gui_scriptfloweditor.tr("edit_branch_condition_placeholder", en="Enter condition expression for this path…", zh_cn="在此输入该连线的条件表达式…", zh_hk="在此輸入該連線的條件表達式…")
+
+    _tr_toolname = MainWindowInterface.tr_toolname_scriptflow_editor
+
+    @classmethod
+    def getToolInfo(cls) -> ToolWidgetInfo:
+        return ToolWidgetInfo(
+            idstr="scriptflow_editor",
+            name=ScriptFlowEditorWidget._tr_toolname,
+            widget=cls,
+            uniquelevel=ToolWidgetUniqueLevel.SINGLE_INSTANCE,
+        )
+
+    def __init__(self, parent: QWidget | None = None):
+        super().__init__(parent)
+        main_layout = QVBoxLayout(self)
+        main_layout.setContentsMargins(0, 0, 0, 0)
+
+        self._graph = NodeGraph(parent=self)
+        self._graph.register_node(SegmentNode)
+        self._graph.register_node(FlagNode)
+        self._graph.set_acyclic(True)
+
+        # 数据层：与节点视图一一对应
+        self._flow = GameScriptFlow(name="ScriptFlow")
+        self._node_id_to_segment_id: dict[str, str] = {}
+        self._node_id_to_flag_id: dict[str, str] = {}
+        # 为 True 时表示正在根据 flow 构建图，不把连线/断开同步回 flow，避免重复路径与误判闭环
+        self._building_from_flow = False
+
+        # 视图 → 数据：连线、删除、改名
+        self._graph.port_connected.connect(self._on_port_connected)
+        self._graph.port_disconnected.connect(self._on_port_disconnected)
+        self._graph.nodes_deleted.connect(self._on_nodes_deleted)
+        self._graph.viewer().node_name_changed.connect(self._on_node_name_changed)
+
+        # 在节点图右键菜单中增加「Add Node」；右键空白处时在点击位置创建节点；右键节点时记录节点 id 供 Flag 子菜单使用
+        self._last_context_menu_scene_pos = None
+        self._last_context_menu_node_id: str | None = None
+        # 右键前缓存的选中连线（右键后 selection 可能被清空，用于「编辑分支条件」）
+        self._context_menu_pipe_selection: list = []
+        self._graph.context_menu_prompt.connect(self._on_context_menu_prompt)
+        graph_menu = self._graph.viewer().context_menus()["graph"]
+        self._ctx_action_add_node = QAction(self._tr_add_script_node.get(), self)
+        self._ctx_action_add_node.triggered.connect(self._on_add_node)
+        graph_menu.addAction(self._ctx_action_add_node)
+        self.bind_text(self._ctx_action_add_node.setText, self._tr_add_script_node)
+        self._ctx_action_add_flag = QAction(self._tr_add_flag.get(), self)
+        self._ctx_action_add_flag.triggered.connect(self._on_add_flag)
+        graph_menu.addAction(self._ctx_action_add_flag)
+        self.bind_text(self._ctx_action_add_flag.setText, self._tr_add_flag)
+        self._ctx_action_edit_path_condition = QAction(self._tr_edit_branch_condition.get(), self)
+        self._ctx_action_edit_path_condition.triggered.connect(self._on_edit_path_condition)
+        graph_menu.addAction(self._ctx_action_edit_path_condition)
+        self.bind_text(self._ctx_action_edit_path_condition.setText, self._tr_edit_branch_condition)
+
+        # 节点右键菜单：编辑剧情文本（保留 command 引用以便 update_text 时更新文案）
+        nodes_menu = self._graph.get_context_menu("nodes")
+        self._ctx_command_edit_content = nodes_menu.add_command(
+            self._tr_edit_segment_content.get(),
+            func=self._on_edit_segment_content,
+            node_class=SegmentNode,
+        )
+        self._ctx_command_edit_comment = nodes_menu.add_command(
+            self._tr_edit_comment.get(),
+            func=self._on_edit_segment_comment,
+            node_class=SegmentNode,
+        )
+        # FlagNode 右键菜单：「修改flag变量类型」二级菜单（布尔型/整型/浮点型）、「修改flag初始值」
+        self._ctx_command_edit_flag_initial = nodes_menu.add_command(
+            self._tr_flag_change_initial.get(),
+            func=self._on_edit_flag_initial_value,
+            node_class=FlagNode,
+        )
+        self._ctx_command_edit_flag_comment = nodes_menu.add_command(
+            self._tr_edit_comment.get(),
+            func=self._on_edit_flag_comment,
+            node_class=FlagNode,
+        )
+        # 在 FlagNode 子菜单前插入「修改flag变量类型」二级菜单（库只对直接 action 设置 node_id，子菜单项用 _last_context_menu_node_id）
+        nodes_qmenu = nodes_menu.qmenu
+        for action in nodes_qmenu.actions():
+            sub = action.menu()
+            if sub is not None and getattr(sub, "node_class", None) is FlagNode:
+                type_submenu = QMenu(self._tr_flag_change_type.get(), self)
+                for label_tr, flag_type in [
+                    (self._tr_flag_type_bool, FlagType.BOOL),
+                    (self._tr_flag_type_int, FlagType.INT),
+                    (self._tr_flag_type_float, FlagType.FLOAT),
+                ]:
+                    a = QAction(label_tr.get(), self)
+                    a.triggered.connect(lambda checked=False, ft=flag_type: self._on_set_flag_type(ft))
+                    type_submenu.addAction(a)
+                sub.insertMenu(sub.actions()[0], type_submenu)
+                self._ctx_menu_flag_type = type_submenu
+                break
+        else:
+            self._ctx_menu_flag_type = None
+
+        # 工具栏：所有文案用 bind_text 绑定，语言切换时由 update_text 更新
+        toolbar = QWidget()
+        bar_layout = QHBoxLayout(toolbar)
+        bar_layout.setContentsMargins(4, 2, 4, 2)
+        self._btn_add = QPushButton(self._tr_add_script_node.get())
+        self._btn_add.clicked.connect(self._on_add_node)
+        self.bind_text(self._btn_add.setText, self._tr_add_script_node)
+        self.bind_text(self._btn_add.setToolTip, self._tr_btn_add_tooltip)
+        self._btn_del = QPushButton(self._tr_btn_del.get())
+        self._btn_del.clicked.connect(self._on_delete_selected)
+        self.bind_text(self._btn_del.setText, self._tr_btn_del)
+        self.bind_text(self._btn_del.setToolTip, self._tr_btn_del_tooltip)
+        self._chk_acyclic = QCheckBox(self._tr_chk_acyclic.get())
+        self._chk_acyclic.setChecked(True)
+        self._chk_acyclic.toggled.connect(self._on_acyclic_toggled)
+        self.bind_text(self._chk_acyclic.setText, self._tr_chk_acyclic)
+        self.bind_text(self._chk_acyclic.setToolTip, self._tr_chk_acyclic_tooltip)
+        self._btn_save = QPushButton(self._tr_save_flow.get())
+        self._btn_save.clicked.connect(self._on_save)
+        self.bind_text(self._btn_save.setText, self._tr_save_flow)
+        self.bind_text(self._btn_save.setToolTip, self._tr_save_tooltip)
+        self._btn_load = QPushButton(self._tr_load_flow.get())
+        self._btn_load.clicked.connect(self._on_load)
+        self.bind_text(self._btn_load.setText, self._tr_load_flow)
+        self.bind_text(self._btn_load.setToolTip, self._tr_open_tooltip)
+        self._btn_add_flag = QPushButton(self._tr_add_flag.get())
+        self._btn_add_flag.clicked.connect(self._on_add_flag)
+        self.bind_text(self._btn_add_flag.setText, self._tr_add_flag)
+        self.bind_text(self._btn_add_flag.setToolTip, self._tr_btn_add_flag_tooltip)
+        bar_layout.addWidget(self._btn_add)
+        bar_layout.addWidget(self._btn_add_flag)
+        bar_layout.addWidget(self._btn_del)
+        bar_layout.addWidget(self._chk_acyclic)
+        bar_layout.addWidget(self._btn_save)
+        bar_layout.addWidget(self._btn_load)
+        bar_layout.addStretch()
+        main_layout.addWidget(toolbar)
+
+        # 从数据层构建初始图：Start → Ending
+        self._build_initial_flow()
+        self._build_graph_from_flow()
+
+        main_layout.addWidget(self._graph.widget)
+        for key in (Qt.Key.Key_Delete, Qt.Key.Key_Backspace):
+            shortcut = QShortcut(QKeySequence(key), self, context=Qt.ShortcutContext.WidgetWithChildrenShortcut)
+            shortcut.activated.connect(self._on_delete_key)
+
+    def update_text(self) -> None:
+        super().update_text()
+        # 图右键菜单项需随语言更新
+        self._ctx_action_add_node.setText(self._tr_add_script_node.get())
+        self._ctx_action_add_flag.setText(self._tr_add_flag.get())
+        self._ctx_action_edit_path_condition.setText(self._tr_edit_branch_condition.get())
+        # 节点右键菜单由库管理，需手动更新
+        self._ctx_command_edit_content.qaction.setText(self._tr_edit_segment_content.get())
+        self._ctx_command_edit_comment.qaction.setText(self._tr_edit_comment.get())
+        self._ctx_command_edit_flag_initial.qaction.setText(self._tr_flag_change_initial.get())
+        self._ctx_command_edit_flag_comment.qaction.setText(self._tr_edit_comment.get())
+        if hasattr(self, "_ctx_menu_flag_type") and self._ctx_menu_flag_type is not None:
+            self._ctx_menu_flag_type.setTitle(self._tr_flag_change_type.get())
+            for action, label_tr in zip(
+                self._ctx_menu_flag_type.actions(),
+                (self._tr_flag_type_bool, self._tr_flag_type_int, self._tr_flag_type_float),
+            ):
+                action.setText(label_tr.get())
+        # 所有节点及名称区的 tooltip 随语言更新
+        for node in self._graph.all_nodes():
+            seg_id = self._node_id_to_segment_id.get(node.id)
+            if seg_id is not None:
+                seg = self._flow.get_segment_by_id(seg_id)
+                if seg is not None:
+                    self._set_node_tooltips(node, seg)
+                continue
+            flag_id = self._node_id_to_flag_id.get(node.id)
+            if flag_id is not None:
+                flag_var = self._flow.get_flag_by_id(flag_id)
+                if flag_var is not None:
+                    self._set_flag_node_tooltips(node, flag_var)
+
+    def _build_initial_flow(self) -> None:
+        """数据层初始状态：Start 段落、Ending 段落、一条路径。"""
+        seg_start = StorySegment(name="Start", content="\"剧情从这里开始。\"", is_ending_segment=False)
+        seg_ending = StorySegment(name="Ending", content="\"剧情在这里结束。\"", is_ending_segment=True)
+        self._flow.segments = [seg_start, seg_ending]
+        path = SegmentPath(prev_segment_id=seg_start.id, next_segment_id=seg_ending.id)
+        self._flow.paths = [path]
+        seg_start.add_path_segment_id(path.id, seg_ending.id)
+
+    def _build_graph_from_flow(
+        self,
+        segment_positions: dict[str, list[float]] | None = None,
+        flag_positions: dict[str, list[float]] | None = None,
+    ) -> None:
+        """根据当前 flow 在图中创建节点与连线；segment_positions / flag_positions 用于恢复位置。"""
+        self._building_from_flow = True
+        try:
+            self._build_graph_from_flow_impl(
+                segment_positions=segment_positions,
+                flag_positions=flag_positions,
+            )
+        finally:
+            self._building_from_flow = False
+
+    def _build_graph_from_flow_impl(
+        self,
+        segment_positions: dict[str, list[float]] | None = None,
+        flag_positions: dict[str, list[float]] | None = None,
+    ) -> None:
+        segment_positions = segment_positions or {}
+        flag_positions = flag_positions or {}
+        default_positions = [(0, 0), (400, 0)]
+        for i, seg in enumerate(self._flow.segments):
+            pos = segment_positions.get(seg.id)
+            if pos is not None and len(pos) >= 2:
+                pos = (float(pos[0]), float(pos[1]))
+            else:
+                pos = default_positions[i] if i < len(default_positions) else (0, 0)
+            node = self._graph.create_node(
+                _SEGMENT_NODE_TYPE,
+                name=seg.name,
+                pos=pos,
+            )
+            self._node_id_to_segment_id[node.id] = seg.id
+            self._set_node_tooltips(node, seg)
+        for f in self._flow.flags:
+            pos = flag_positions.get(f.id)
+            if pos is not None and len(pos) >= 2:
+                pos = (float(pos[0]), float(pos[1]))
+            else:
+                pos = (0, 0)
+            node = self._graph.create_node(
+                _FLAG_NODE_TYPE,
+                name=f.name,
+                pos=pos,
+            )
+            self._node_id_to_flag_id[node.id] = f.id
+            self._update_flag_node_display(node)
+            self._set_flag_node_tooltips(node, f)
+        seg_id_to_node = {
+            self._node_id_to_segment_id[n.id]: n
+            for n in self._graph.all_nodes()
+            if n.id in self._node_id_to_segment_id
+        }
+        for path in self._flow.paths:
+            prev_node = seg_id_to_node.get(path.prev_segment_id)
+            next_node = seg_id_to_node.get(path.next_segment_id)
+            if prev_node is not None and next_node is not None:
+                prev_node.set_output(0, next_node.input(0))
+        self._update_pipe_tooltips()
+
+    def _update_pipe_tooltips(self) -> None:
+        """根据 SegmentPath.condition_expression 刷新场景中所有连线的 tooltip。"""
+        scene = self._graph.viewer().scene()
+        for item in scene.items():
+            if not isinstance(item, PipeItem):
+                continue
+            if not item.input_port or not item.output_port:
+                continue
+            prev_seg_id = self._node_id_to_segment_id.get(item.output_port.node.id)
+            next_seg_id = self._node_id_to_segment_id.get(item.input_port.node.id)
+            if prev_seg_id is None or next_seg_id is None:
+                item.setToolTip("")
+                continue
+            path = next(
+                (p for p in self._flow.paths if p.prev_segment_id == prev_seg_id and p.next_segment_id == next_seg_id),
+                None,
+            )
+            text = (path.condition_expression or "").strip() if path is not None else ""
+            item.setToolTip(text)
+
+    def _set_node_tooltips(self, node, segment: StorySegment) -> None:
+        """根据段落类型或注释设置节点与名称区的 tooltip（使用当前语言）。有注释时节点 tooltip 显示注释。"""
+        if segment.comment and segment.comment.strip():
+            node.view.setToolTip(segment.comment.strip())
+        elif segment.is_ending_segment:
+            node.view.setToolTip(self._tr_node_tooltip_ending.get())
+        elif segment.name == "Start":
+            node.view.setToolTip(self._tr_node_tooltip_start.get())
+        else:
+            node.view.setToolTip(self._tr_node_tooltip_default.get())
+        node.view.text_item.setToolTip(self._tr_node_name_edit_tooltip.get())
+
+    def _set_flag_node_tooltips(self, node, flag_var: FlagVariable) -> None:
+        """根据 FlagVariable.comment 或当前语言默认文案设置 Flag 节点及名称区的 tooltip。"""
+        if flag_var.comment and flag_var.comment.strip():
+            node.view.setToolTip(flag_var.comment.strip())
+        else:
+            node.view.setToolTip(self._tr_node_tooltip_flag_default.get())
+        node.view.text_item.setToolTip(self._tr_node_name_edit_tooltip.get())
+
+    def _format_flag_initial_value(self, flag_var: FlagVariable) -> str:
+        """将 FlagVariable.initial_value 格式化为节点中部显示的字符串。"""
+        if flag_var.flag_type == FlagType.BOOL:
+            return "True" if flag_var.initial_value else "False"
+        if flag_var.flag_type == FlagType.INT:
+            return str(int(flag_var.initial_value))
+        return str(float(flag_var.initial_value))
+
+    def _update_flag_node_display(self, node) -> None:
+        """根据节点对应的 FlagVariable 更新节点中部的 initial_value 显示。"""
+        flag_id = self._node_id_to_flag_id.get(node.id)
+        if flag_id is None:
+            return
+        flag_var = self._flow.get_flag_by_id(flag_id)
+        if flag_var is None:
+            return
+        if node.view.has_widget("initial_value_display"):
+            node.get_widget("initial_value_display").set_value(
+                self._format_flag_initial_value(flag_var)
+            )
+
+    def _next_segment_name(self) -> str:
+        """生成下一个 segment_<index> 名称，保证不与已有节点重名。"""
+        pattern = re.compile(r"^segment_(\d+)$")
+        max_index = -1
+        for node in self._graph.all_nodes():
+            m = pattern.match(node.name())
+            if m:
+                max_index = max(max_index, int(m.group(1)))
+        return "segment_{}".format(max_index + 1)
+
+    def _next_flag_name(self) -> str:
+        """生成下一个 flag_<index> 名称，保证不与已有节点重名。"""
+        pattern = re.compile(r"^flag_(\d+)$")
+        max_index = -1
+        for node in self._graph.all_nodes():
+            m = pattern.match(node.name())
+            if m:
+                max_index = max(max_index, int(m.group(1)))
+        return "flag_{}".format(max_index + 1)
+
+    def _on_context_menu_prompt(self, _menu: object, node: object) -> None:
+        """右键菜单即将弹出时记录场景坐标，供「Add Node」在点击位置创建节点；记录节点 id 供 Flag 子菜单使用；图菜单时根据光标下或已选中的单条连线启用「编辑分支条件」。"""
+        if node is None:
+            pos = self._graph.viewer().scene_cursor_pos()
+            self._last_context_menu_scene_pos = (pos.x(), pos.y())
+            self._last_context_menu_node_id = None
+            # 优先用当前选中连线；若无则取光标下的连线（右键后 selection 常被清空）
+            self._context_menu_pipe_selection = list(self._graph.selected_pipes())
+            if len(self._context_menu_pipe_selection) != 1:
+                scene = self._graph.viewer().scene()
+                pt = QPointF(pos.x(), pos.y())
+                items_at = scene.items(pt)
+                pipes_at = [i for i in items_at if isinstance(i, PipeItem)]
+                if len(pipes_at) == 1:
+                    old_sel = list(scene.selectedItems())
+                    scene.clearSelection()
+                    pipes_at[0].setSelected(True)
+                    self._context_menu_pipe_selection = list(self._graph.selected_pipes())
+                    scene.clearSelection()
+                    for item in old_sel:
+                        item.setSelected(True)
+            self._ctx_action_edit_path_condition.setEnabled(len(self._context_menu_pipe_selection) == 1)
+        else:
+            self._last_context_menu_scene_pos = None
+            self._last_context_menu_node_id = node.id
+
+    def _on_add_node(self) -> None:
+        if self._last_context_menu_scene_pos is not None:
+            pos = self._last_context_menu_scene_pos
+            self._last_context_menu_scene_pos = None
+        else:
+            pos = (0, 0)
+        name = self._next_segment_name()
+        segment = StorySegment(name=name, content="", is_ending_segment=False)
+        self._flow.segments.append(segment)
+        node = self._graph.create_node(_SEGMENT_NODE_TYPE, name=name, pos=pos)
+        self._node_id_to_segment_id[node.id] = segment.id
+        self._set_node_tooltips(node, segment)
+
+    def _on_add_flag(self) -> None:
+        """在图中添加一个 FlagNode，并在 flow.flags 中新增对应 FlagVariable。"""
+        if self._last_context_menu_scene_pos is not None:
+            pos = self._last_context_menu_scene_pos
+            self._last_context_menu_scene_pos = None
+        else:
+            pos = (0, 0)
+        name = self._next_flag_name()
+        flag_var = FlagVariable(name=name, flag_type=FlagType.BOOL, initial_value=False)
+        self._flow.flags.append(flag_var)
+        node = self._graph.create_node(_FLAG_NODE_TYPE, name=name, pos=pos)
+        self._node_id_to_flag_id[node.id] = flag_var.id
+        self._update_flag_node_display(node)
+        self._set_flag_node_tooltips(node, flag_var)
+
+    def _on_delete_selected(self) -> None:
+        self._delete_selected_pipes()
+        self._graph.delete_nodes(self._graph.selected_nodes())
+
+    def _on_acyclic_toggled(self, checked: bool) -> None:
+        self._graph.set_acyclic(checked)
+
+    def _on_delete_key(self) -> None:
+        self._on_delete_selected()
+
+    def _on_edit_path_condition(self) -> None:
+        """图右键「编辑分支条件」：对当前选中的单条连线，编辑对应 SegmentPath.condition_expression。"""
+        pipes = self._context_menu_pipe_selection if len(self._context_menu_pipe_selection) == 1 else self._graph.selected_pipes()
+        if len(pipes) != 1:
+            return
+        in_port, out_port = pipes[0]
+        prev_seg_id = self._node_id_to_segment_id.get(out_port.node().id)
+        next_seg_id = self._node_id_to_segment_id.get(in_port.node().id)
+        if prev_seg_id is None or next_seg_id is None:
+            return
+        path = next(
+            (p for p in self._flow.paths if p.prev_segment_id == prev_seg_id and p.next_segment_id == next_seg_id),
+            None,
+        )
+        if path is None:
+            return
+        dialog = PathConditionEditDialog(
+            self,
+            segment_path=path,
+            title=self._tr_edit_branch_condition_dialog_title.get(),
+            placeholder=self._tr_edit_branch_condition_placeholder.get(),
+        )
+        if dialog.exec() == QDialog.DialogCode.Accepted:
+            self._update_pipe_tooltips()
+
+    def _on_port_connected(self, in_port, out_port) -> None:
+        """连线建立：在数据层添加 SegmentPath。NodeGraphQt 信号参数顺序为 (input_port, output_port)。同一条连线只添加一次。"""
+        if self._building_from_flow:
+            return
+        out_node = out_port.node()
+        in_node = in_port.node()
+        prev_seg_id = self._node_id_to_segment_id.get(out_node.id)
+        next_seg_id = self._node_id_to_segment_id.get(in_node.id)
+        if prev_seg_id is None or next_seg_id is None:
+            return
+        already = any(
+            p.prev_segment_id == prev_seg_id and p.next_segment_id == next_seg_id
+            for p in self._flow.paths
+        )
+        if already:
+            return
+        path = SegmentPath(prev_segment_id=prev_seg_id, next_segment_id=next_seg_id)
+        self._flow.paths.append(path)
+        prev_seg = self._flow.get_segment_by_id(prev_seg_id)
+        if prev_seg is not None:
+            prev_seg.add_path_segment_id(path.id, next_seg_id)
+        self._update_pipe_tooltips()
+
+    def _on_port_disconnected(self, in_port, out_port) -> None:
+        """连线断开：从数据层移除对应 SegmentPath。NodeGraphQt 信号参数顺序为 (input_port, output_port)。"""
+        if self._building_from_flow:
+            return
+        out_node = out_port.node()
+        in_node = in_port.node()
+        prev_seg_id = self._node_id_to_segment_id.get(out_node.id)
+        next_seg_id = self._node_id_to_segment_id.get(in_node.id)
+        if prev_seg_id is None or next_seg_id is None:
+            return
+        to_remove = [
+            p for p in self._flow.paths
+            if p.prev_segment_id == prev_seg_id and p.next_segment_id == next_seg_id
+        ]
+        for p in to_remove:
+            self._flow.paths.remove(p)
+            prev_seg = self._flow.get_segment_by_id(prev_seg_id)
+            if prev_seg is not None and p.id in prev_seg.paths_segment_ids:
+                del prev_seg.paths_segment_ids[p.id]
+
+    def _on_nodes_deleted(self, node_ids: list) -> None:
+        """节点删除：从数据层移除对应段落/Flag 及关联路径，并清理映射。"""
+        for nid in node_ids:
+            seg_id = self._node_id_to_segment_id.pop(nid, None)
+            if seg_id is not None:
+                self._flow.segments = [s for s in self._flow.segments if s.id != seg_id]
+                self._flow.paths = [
+                    p for p in self._flow.paths
+                    if p.prev_segment_id != seg_id and p.next_segment_id != seg_id
+                ]
+                for s in self._flow.segments:
+                    for path_id in list(s.paths_segment_ids.keys()):
+                        if s.paths_segment_ids[path_id] == seg_id:
+                            del s.paths_segment_ids[path_id]
+                continue
+            flag_id = self._node_id_to_flag_id.pop(nid, None)
+            if flag_id is not None:
+                self._flow.flags = [f for f in self._flow.flags if f.id != flag_id]
+
+    def _on_node_name_changed(self, node_id: str, name: str) -> None:
+        """节点改名：同步到数据层 StorySegment.name 或 FlagVariable.name（id 保持不变），Segment 依 comment 重设 tooltip。"""
+        seg_id = self._node_id_to_segment_id.get(node_id)
+        if seg_id is not None:
+            seg = self._flow.get_segment_by_id(seg_id)
+            if seg is not None:
+                seg.name = name
+                node = self._graph.get_node_by_id(node_id)
+                if node is not None:
+                    self._set_node_tooltips(node, seg)
+            return
+        flag_id = self._node_id_to_flag_id.get(node_id)
+        if flag_id is not None:
+            flag_var = self._flow.get_flag_by_id(flag_id)
+            if flag_var is not None:
+                flag_var.name = name
+                node = self._graph.get_node_by_id(node_id)
+                if node is not None:
+                    self._set_flag_node_tooltips(node, flag_var)
+
+    def _on_edit_segment_content(self, _graph, node) -> None:
+        """节点右键「编辑剧情文本」：弹出对话框编辑对应 StorySegment.content，关闭时保存。"""
+        seg_id = self._node_id_to_segment_id.get(node.id)
+        if seg_id is None:
+            return
+        seg = self._flow.get_segment_by_id(seg_id)
+        if seg is None:
+            return
+        dialog = SegmentContentEditDialog(
+            self,
+            segment=seg,
+            title=self._tr_edit_dialog_title.get(),
+            placeholder=self._tr_edit_dialog_placeholder.get(),
+        )
+        dialog.exec()
+
+    def _on_edit_segment_comment(self, _graph, node) -> None:
+        """节点右键「编辑注释」：弹出对话框编辑对应 StorySegment.comment，关闭时保存并设为节点 tooltip。"""
+        seg_id = self._node_id_to_segment_id.get(node.id)
+        if seg_id is None:
+            return
+        seg = self._flow.get_segment_by_id(seg_id)
+        if seg is None:
+            return
+        dialog = SegmentCommentEditDialog(
+            self,
+            segment=seg,
+            title=self._tr_edit_comment_dialog_title.get(),
+            placeholder=self._tr_edit_comment_dialog_placeholder.get(),
+        )
+        if dialog.exec() == QDialog.DialogCode.Accepted:
+            self._set_node_tooltips(node, seg)
+
+    def _on_set_flag_type(self, new_type: FlagType) -> None:
+        """将当前右键的 Flag 节点对应的 FlagVariable 的 flag_type 设为 new_type；类型未变则不改 initial_value，类型改变则 bool→False、int/float→0。"""
+        node_id = self._last_context_menu_node_id
+        if node_id is None:
+            return
+        flag_id = self._node_id_to_flag_id.get(node_id)
+        if flag_id is None:
+            return
+        flag_var = self._flow.get_flag_by_id(flag_id)
+        if flag_var is None:
+            return
+        old_type = flag_var.flag_type
+        flag_var.flag_type = new_type
+        if old_type != new_type:
+            flag_var.initial_value = False if new_type == FlagType.BOOL else 0
+        graph_node = self._graph.get_node_by_id(node_id)
+        if graph_node is not None:
+            self._update_flag_node_display(graph_node)
+
+    def _on_edit_flag_initial_value(self, _graph, node) -> None:
+        """节点右键「修改flag初始值」：弹出对话框编辑对应 FlagVariable.initial_value。"""
+        flag_id = self._node_id_to_flag_id.get(node.id)
+        if flag_id is None:
+            return
+        flag_var = self._flow.get_flag_by_id(flag_id)
+        if flag_var is None:
+            return
+        dialog = FlagInitialValueEditDialog(
+            self,
+            flag_var=flag_var,
+            title=self._tr_flag_initial_dialog_title.get(),
+        )
+        if dialog.exec() == QDialog.DialogCode.Accepted:
+            self._update_flag_node_display(node)
+            self._set_flag_node_tooltips(node, flag_var)
+
+    def _on_edit_flag_comment(self, _graph, node) -> None:
+        """节点右键「编辑注释」：弹出对话框编辑对应 FlagVariable.comment，关闭时同步到节点 tooltip。"""
+        flag_id = self._node_id_to_flag_id.get(node.id)
+        if flag_id is None:
+            return
+        flag_var = self._flow.get_flag_by_id(flag_id)
+        if flag_var is None:
+            return
+        dialog = FlagCommentEditDialog(
+            self,
+            flag_var=flag_var,
+            title=self._tr_edit_comment_dialog_title.get(),
+            placeholder=self._tr_edit_comment_dialog_placeholder.get(),
+        )
+        if dialog.exec() == QDialog.DialogCode.Accepted:
+            self._set_flag_node_tooltips(node, flag_var)
+
+    def _on_save(self) -> None:
+        """将当前流程数据与节点位置保存为 JSON 文件。"""
+        path, _ = QFileDialog.getSaveFileName(
+            self,
+            self._tr_save_dialog_title.get(),
+            "",
+            self._tr_json_filter.get(),
+        )
+        if not path:
+            return
+        path = Path(path)
+        if path.suffix.lower() != ".json":
+            path = path.with_suffix(".json")
+        flow_dict = self._flow.to_dict()
+        node_positions = {}
+        flag_positions = {}
+        for node in self._graph.all_nodes():
+            pos = getattr(node.model, "pos", [0.0, 0.0])
+            pos_list = list(pos) if isinstance(pos, (list, tuple)) else [0.0, 0.0]
+            seg_id = self._node_id_to_segment_id.get(node.id)
+            if seg_id is not None:
+                node_positions[seg_id] = pos_list
+            else:
+                flag_id = self._node_id_to_flag_id.get(node.id)
+                if flag_id is not None:
+                    flag_positions[flag_id] = pos_list
+        flow_dict["node_positions"] = node_positions
+        flow_dict["flag_positions"] = flag_positions
+        path.write_text(
+            json.dumps(flow_dict, indent=2, ensure_ascii=False),
+            encoding="utf-8",
+        )
+
+    def _on_load(self) -> None:
+        """从 JSON 文件加载流程数据与节点位置，重建视图。"""
+        path, _ = QFileDialog.getOpenFileName(
+            self,
+            self._tr_open_dialog_title.get(),
+            "",
+            self._tr_json_filter.get(),
+        )
+        if not path:
+            return
+        path = Path(path)
+        if not path.is_file():
+            return
+        data = json.loads(path.read_text(encoding="utf-8"))
+        node_positions = data.pop("node_positions", {})
+        flag_positions = data.pop("flag_positions", {})
+        self._node_id_to_segment_id.clear()
+        self._node_id_to_flag_id.clear()
+        for node in list(self._graph.all_nodes()):
+            self._graph.delete_nodes([node])
+        self._flow = GameScriptFlow.load_from_dict(data)
+        self._build_graph_from_flow(
+            segment_positions=node_positions,
+            flag_positions=flag_positions,
+        )
+
+    def _delete_selected_pipes(self) -> None:
+        """删除当前选中的连线。selected_pipes() 返回 (Port, Port) 列表。"""
+        for port1, port2 in self._graph.selected_pipes():
+            port1.disconnect_from(port2)
