跳转到内容

2.4 模板系统

RmlUi 的模板系统允許你创建可复用的界面组件,提高开发效率並保持一致性。


一、模板基础

模板定义

xml
<!-- template.rml -->
<rml>
<head>
    <template name="window-frame">
        <div class="window">
            <div class="window-header">
                <h2 class="window-title">{v-pre{title}v-pre}</h2>
                <button class="window-close" onclick="close_window()">×</button>
            </div>
            <div class="window-content">
                <!-- 内容將被插入到這裡 -->
            </div>
            <div class="window-footer">
                <button class="btn-primary">确定</button>
                <button class="btn-secondary">取消</button>
            </div>
        </div>
    </template>
</head>
<body>
    <!-- 使用模板 -->
</body>
</rml>

引用模板

xml
<rml>
<head>
    <link type="text/template" href="templates/window-frame.rml"/>
    <link type="text/rcss" href="style.rcss"/>
</head>
<body>
    <!-- 模板内容會被注入到這裡 -->
    <div class="window">
        <div class="window-header">
            <h2 class="window-title">我的窗口</h2>
            <button class="window-close">×</button>
        </div>
        <div class="window-content">
            <p>这是窗口内容</p>
        </div>
        <div class="window-footer">
            <button class="btn-primary">确定</button>
            <button class="btn-secondary">取消</button>
        </div>
    </div>
</body>
</rml>

二、使用 C++ 加载模板

cpp
// 加载模板文件
Rml::ElementDocument* template_doc = context->LoadDocument("templates/window-frame.rml");

// 获取模板元素
Rml::Element* template_element = template_doc->GetElementById("window-template");

// 实例化模板
Rml::Element* instance = template_element->Clone();
document->AppendChild(instance);

三、實戰示例:对话框模板

模板定义

xml
<!-- templates/dialog.rml -->
<rml>
<head>
    <template name="dialog">
        <div class="dialog-overlay" id="dialog-overlay">
            <div class="dialog">
                <div class="dialog-header">
                    <h3 class="dialog-title" id="dialog-title">标题</h3>
                    <button class="dialog-close" onclick="close_dialog()">×</button>
                </div>
                <div class="dialog-body" id="dialog-body">
                    <!-- 内容 -->
                </div>
                <div class="dialog-footer" id="dialog-footer">
                    <!-- 按钮 -->
                </div>
            </div>
        </div>
    </template>
</head>
</rml>

使用模板

cpp
#include <RmlUi/Core.h>

class DialogManager
{
public:
    DialogManager(Rml::Context* context) : context(context) {}

    // 显示简单对话框
    void ShowDialog(const Rml::String& title, const Rml::String& message)
    {
        // 加载模板
        Rml::ElementDocument* template_doc = context->LoadDocument("templates/dialog.rml");

        // 克隆模板
        Rml::Element* dialog = template_doc->GetElementById("dialog-overlay")->Clone();

        // 设置内容
        Rml::Element* title_el = dialog->GetElementById("dialog-title");
        if (title_el) {
            title_el->SetInnerRML(title);
        }

        Rml::Element* body_el = dialog->GetElementById("dialog-body");
        if (body_el) {
            body_el->SetInnerRML(message);
        }

        // 添加到文档
        context->GetDocument(0)->AppendChild(dialog);
    }

    // 显示確認对话框
    void ShowConfirmDialog(const Rml::String& title,
                           const Rml::String& message,
                           std::function<void(bool)> callback)
    {
        Rml::ElementDocument* template_doc = context->LoadDocument("templates/dialog.rml");
        Rml::Element* dialog = template_doc->GetElementById("dialog-overlay")->Clone();

        // 设置标题和内容
        dialog->GetElementById("dialog-title")->SetInnerRML(title);
        dialog->GetElementById("dialog-body")->SetInnerRML(message);

        // 添加按钮
        Rml::Element* footer = dialog->GetElementById("dialog-footer");

        Rml::Element* btn_ok = context->CreateElement("button");
        btn_ok->SetInnerRML("确定");
        btn_ok->AddClass("btn-primary");
        btn_ok->AddEventListener(Rml::EventId::Click, [this, dialog, callback]() {
            callback(true);
            CloseDialog(dialog);
        });
        footer->AppendChild(btn_ok);

        Rml::Element* btn_cancel = context->CreateElement("button");
        btn_cancel->SetInnerRML("取消");
        btn_cancel->AddClass("btn-secondary");
        btn_cancel->AddEventListener(Rml::EventId::Click, [this, dialog, callback]() {
            callback(false);
            CloseDialog(dialog);
        });
        footer->AppendChild(btn_cancel);

        context->GetDocument(0)->AppendChild(dialog);
    }

private:
    void CloseDialog(Rml::Element* dialog)
    {
        dialog->GetParentNode()->RemoveChild(dialog);
    }

    Rml::Context* context;
};

四、样式定义

css
/* dialog.rcss */

.dialog-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.6);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
}

.dialog {
    background: #fff;
    border-radius: 8px;
    min-width: 400px;
    max-width: 600px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
    animation: dialog-slide-in 0.3s ease;
}

@keyframes dialog-slide-in {
    from {
        opacity: 0;
        transform: translateY(-20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.dialog-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 20px;
    border-bottom: 1px solid #eee;
}

.dialog-title {
    margin: 0;
    font-size: 18px;
    color: #333;
}

.dialog-close {
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: #999;
    padding: 0;
    width: 30px;
    height: 30px;
}

.dialog-close:hover {
    color: #333;
}

.dialog-body {
    padding: 20px;
    max-height: 300px;
    overflow-y: auto;
}

.dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 15px 20px;
    border-top: 1px solid #eee;
}

.dialog-footer button {
    padding: 8px 16px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 14px;
}

.btn-primary {
    background: #3498db;
    color: white;
}

.btn-primary:hover {
    background: #2980b9;
}

.btn-secondary {
    background: #e0e0e0;
    color: #333;
}

.btn-secondary:hover {
    background: #d0d0d0;
}

五、高级模板技巧

1. 使用数据绑定

xml
<!-- templates/character-card.rml -->
<rml>
<head>
    <template name="character-card">
        <div class="character-card" data-model="character">
            <div class="avatar">
                <img src="{v-pre{ avatar }v-pre}"/>
            </div>
            <div class="info">
                <h3 class="name">{v-pre{ name }v-pre}</h3>
                <p class="level">等级 {v-pre{ level }v-pre}</p>
                <div class="stats">
                    <span>HP: {v-pre{ hp }v-pre}/{v-pre{ max_hp }v-pre}</span>
                    <span>MP: {v-pre{ mp }v-pre}/{v-pre{ max_mp }v-pre}</span>
                </div>
            </div>
        </div>
    </template>
</head>
</rml>

2. 條件内容

xml
<template name="item-slot">
    <div class="item-slot">
        <img src="{v-pre{ item.icon }v-pre}" if="item.icon"/>
        <span class="count" if="item.count > 1">{v-pre{ item.count }v-pre}</span>
        <div class="overlay" if="item.locked">
            <span class="lock-icon">🔒</span>
        </div>
    </div>
</template>

六、實踐练习

练习 1:创建通知弹窗模板

创建一个可复用的通知弹窗:

  • 支持不同类型(成功、警告、错误)
  • 自动消失功能
  • 可配置位置

练习 2:创建菜单模板

创建一个横向菜单模板:

  • 可配置菜单项
  • 支持图标
  • 支持子菜单

练习 3:创建表单模板

创建一个通用表单模板:

  • 可配置表单字段
  • 支持验证
  • 支持提交回調

📝 檢查清单

基于 MIT 许可发布