跳转到内容

5.5 SVG 集成

SVG(Scalable Vector Graphics)支持让 RmlUi 能够显示高质量的矢量图形。本节将深入讲解如何在 RmlUi 中集成和使用 SVG。


一、SVG 插件

1.1 启用 SVG 支持

cpp
// SVGPlugin.h
#pragma once
#include <RmlUi/Core/Plugin.h>

class SVGPlugin : public Rml::Plugin
{
public:
    void Initialise() override;
    void Shutdown() override;
    Rml::EventListener* GetEventListener() override;
    Rml::String GetName() const override { return "SVG"; }
    int GetVersion() const override { return 1; }
};

// SVGPlugin.cpp
#include "SVGPlugin.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Factory.h>

void SVGPlugin::Initialise()
{
    Rml::Log::Message(Rml::Log::LT_INFO, "Initializing SVG plugin");
    
    // SVG 元素会自动注册
    // 只需要确保 SVG 库已链接
}

void SVGPlugin::Shutdown()
{
    Rml::Log::Message(Rml::Log::LT_INFO, "Shutting down SVG plugin");
}

Rml::EventListener* SVGPlugin::GetEventListener()
{
    return nullptr;
}

// 注册插件
void InitializeSVG()
{
    SVGPlugin* plugin = new SVGPlugin();
    Rml::RegisterPlugin(plugin);
}

1.2 在 RML 中使用 SVG

xml
<rml>
<head>
    <title>SVG 示例</title>
    <link type="text/rcss" href="svg_demo.rcss"/>
</head>
<body>
    <h1>SVG 集成示例</h1>
    
    <!-- 直接嵌入 SVG -->
    <svg width="100" height="100" viewBox="0 0 100 100">
        <circle cx="50" cy="50" r="40" fill="#3498db"/>
        <rect x="20" y="20" width="60" height="60" fill="#e74c3c"/>
    </svg>
    
    <!-- 加载外部 SVG 文件 -->
    <svg src="icons/settings.svg" width="48" height="48"/>
    <svg src="icons/user.svg" width="48" height="48"/>
    <svg src="icons/home.svg" width="48" height="48"/>
    
    <!-- 带样式的 SVG -->
    <div class="icon-container">
        <svg src="icons/play.svg" class="icon-play"/>
        <svg src="icons/pause.svg" class="icon-pause"/>
        <svg src="icons/stop.svg" class="icon-stop"/>
    </div>
</body>
</rml>

1.3 SVG 样式

css
/* svg_demo.rcss */
svg {
    display: inline-block;
    vertical-align: middle;
}

.icon-container {
    display: flex;
    gap: 10px;
}

.icon-play,
.icon-pause,
.icon-stop {
    width: 32px;
    height: 32px;
    cursor: pointer;
    transition: transform 0.2s, filter 0.2s;
}

.icon-play:hover {
    transform: scale(1.1);
    filter: drop-shadow(0 0 5px rgba(52, 152, 219, 0.5));
}

.icon-pause:hover {
    transform: scale(1.1);
    filter: drop-shadow(0 0 5px rgba(231, 76, 60, 0.5));
}

.icon-stop:hover {
    transform: scale(1.1);
    filter: drop-shadow(0 0 5px rgba(46, 204, 113, 0.5));
}

二、SVG 装饰器

2.1 创建 SVG 装饰器

cpp
// SVGDecorator.h
#pragma once
#include <RmlUi/Core/Decorator.h>

class DecoratorSVG : public Rml::Decorator
{
public:
    DecoratorSVG();
    virtual ~DecoratorSVG();

    bool Initialise(const Rml::String& svg_path);

    Rml::DecoratorDataHandle GenerateElementData(
        Rml::Element* element,
        Rml::BoxArea paint_area) const override;

    void ReleaseElementData(
        Rml::DecoratorDataHandle element_data) const override;

    void RenderElement(
        Rml::Element* element,
        Rml::DecoratorDataHandle element_data) const override;

private:
    Rml::String svg_path_;
    int image_index_;
};

class DecoratorInstancerSVG : public Rml::DecoratorInstancer
{
public:
    DecoratorInstancerSVG();
    virtual ~DecoratorInstancerSVG();

    Rml::SharedPtr<Rml::Decorator> InstanceDecorator(
        const Rml::String& name,
        const Rml::PropertyDictionary& properties,
        const Rml::DecoratorInstancerInterface& instancer_interface) override;

private:
    Rml::PropertyId id_src_;
};
cpp
// SVGDecorator.cpp
#include "SVGDecorator.h"
#include <RmlUi/Core/Core.h>
#include <RmlUi/Core/Element.h>
#include <RmlUi/Core/PropertyDefinition.h>

DecoratorSVG::DecoratorSVG()
    : image_index_(-1)
{
}

DecoratorSVG::~DecoratorSVG()
{
}

bool DecoratorSVG::Initialise(const Rml::String& svg_path)
{
    svg_path_ = svg_path;
    
    // 加载 SVG 为纹理
    Rml::Vector2i dimensions;
    image_index_ = AddTexture(Rml::GetTexture(svg_path, dimensions));
    
    return image_index_ >= 0;
}

Rml::DecoratorDataHandle DecoratorSVG::GenerateElementData(
    Rml::Element* element,
    Rml::BoxArea paint_area) const
{
    // 返回图像索引作为数据句柄
    return (Rml::DecoratorDataHandle)image_index_;
}

void DecoratorSVG::ReleaseElementData(
    Rml::DecoratorDataHandle element_data) const
{
    // 不需要释放,由装饰器管理
}

void DecoratorSVG::RenderElement(
    Rml::Element* element,
    Rml::DecoratorDataHandle element_data) const
{
    if (image_index_ < 0)
        return;
    
    Rml::TextureHandle texture = GetTexture(image_index_);
    if (!texture)
        return;
    
    // 获取元素尺寸
    const Rml::Box& box = element->GetBox();
    Rml::Rectangle rect = box.GetEdge(paint_area);
    
    // 生成顶点
    Rml::Vertex vertices[4] = {
        {{rect.left, rect.top}, {0, 0}, Rml::Colourb(255, 255, 255, 255)},
        {{rect.right, rect.top}, {1, 0}, Rml::Colourb(255, 255, 255, 255)},
        {{rect.right, rect.bottom}, {1, 1}, Rml::Colourb(255, 255, 255, 255)},
        {{rect.left, rect.bottom}, {0, 1}, Rml::Colourb(255, 255, 255, 255)}
    };
    
    int indices[6] = {0, 1, 2, 0, 2, 3};
    
    // 渲染 SVG
    if (auto* render_manager = element->GetRenderManager())
    {
        render_manager->RenderGeometry(
            vertices,
            indices,
            texture,
            Rml::Matrix4f::Identity()
        );
    }
}

DecoratorInstancerSVG::DecoratorInstancerSVG()
{
    id_src_ = RegisterProperty("src", "", false, false)
        .AddParser("string")
        .GetId();
}

DecoratorInstancerSVG::~DecoratorInstancerSVG()
{
}

Rml::SharedPtr<Rml::Decorator> DecoratorInstancerSVG::InstanceDecorator(
    const Rml::String& name,
    const Rml::PropertyDictionary& properties,
    const Rml::DecoratorInstancerInterface& instancer_interface)
{
    Rml::String src = properties.GetProperty(id_src_)->Get<Rml::String>();
    
    auto decorator = Rml::MakeShared<DecoratorSVG>();
    if (decorator->Initialise(src))
        return decorator;
    
    return nullptr;
}

2.2 使用 SVG 装饰器

css
/* 使用 SVG 装饰器 */
.icon-button {
    decorator: svg(src="icons/play.svg");
    width: 48px;
    height: 48px;
}

.icon-button:hover {
    decorator: svg(src="icons/play_hover.svg");
}

三、SVG 图标系统

3.1 图标管理器

cpp
// IconManager.h
#pragma once
#include <RmlUi/Core/Types.h>
#include <string>
#include <unordered_map>

class IconManager
{
public:
    static IconManager& Instance()
    {
        static IconManager instance;
        return instance;
    }

    // 加载图标
    bool LoadIcon(const std::string& name, const std::string& path);
    
    // 获取图标路径
    std::string GetIconPath(const std::string& name) const;
    
    // 创建图标元素
    Rml::Element* CreateIcon(Rml::Context* context, const std::string& name, const std::string& class_name = "");
    
    // 批量加载图标
    bool LoadIconsFromDirectory(const std::string& directory);

private:
    IconManager() = default;
    
    std::unordered_map<std::string, std::string> icons_;
};

// IconManager.cpp
bool IconManager::LoadIcon(const std::string& name, const std::string& path)
{
    icons_[name] = path;
    return true;
}

std::string IconManager::GetIconPath(const std::string& name) const
{
    auto it = icons_.find(name);
    return it != icons_.end() ? it->second : "";
}

Rml::Element* IconManager::CreateIcon(Rml::Context* context, const std::string& name, const std::string& class_name)
{
    std::string path = GetIconPath(name);
    if (path.empty())
        return nullptr;
    
    Rml::Element* svg = context->CreateElement("svg");
    svg->SetAttribute("src", path);
    
    if (!class_name.empty())
        svg->SetClass(class_name);
    
    return svg;
}

bool IconManager::LoadIconsFromDirectory(const std::string& directory)
{
    // 扫描目录中的所有 SVG 文件
    std::vector<std::string> files = ScanDirectory(directory, "*.svg");
    
    for (const auto& file : files)
    {
        std::string name = GetFileNameWithoutExtension(file);
        std::string path = directory + "/" + file;
        
        LoadIcon(name, path);
    }
    
    return true;
}

3.2 使用图标系统

cpp
// 初始化图标系统
void InitializeIcons()
{
    auto& icon_manager = IconManager::Instance();
    
    // 加载图标
    icon_manager.LoadIcon("play", "icons/play.svg");
    icon_manager.LoadIcon("pause", "icons/pause.svg");
    icon_manager.LoadIcon("stop", "icons/stop.svg");
    icon_manager.LoadIcon("settings", "icons/settings.svg");
    icon_manager.LoadIcon("user", "icons/user.svg");
    
    // 或批量加载
    icon_manager.LoadIconsFromDirectory("icons");
}

// 在界面中使用图标
void CreateMediaControls(Rml::Context* context, Rml::Element* parent)
{
    auto& icon_manager = IconManager::Instance();
    
    // 创建播放按钮
    Rml::Element* play_btn = context->CreateElement("button");
    play_btn->SetClass("media-button");
    play_btn->AppendChild(icon_manager.CreateIcon(context, "play"));
    parent->AppendChild(play_btn);
    
    // 创建暂停按钮
    Rml::Element* pause_btn = context->CreateElement("button");
    pause_btn->SetClass("media-button");
    pause_btn->AppendChild(icon_manager.CreateIcon(context, "pause"));
    parent->AppendChild(pause_btn);
    
    // 创建停止按钮
    Rml::Element* stop_btn = context->CreateElement("button");
    stop_btn->SetClass("media-button");
    stop_btn->AppendChild(icon_manager.CreateIcon(context, "stop"));
    parent->AppendChild(stop_btn);
}

四、SVG 动画

4.1 SVG 动画集成

xml
<rml>
<head>
    <title>SVG 动画</title>
    <link type="text/rcss" href="svg_animation.rcss"/>
</head>
<body>
    <h1>SVG 动画示例</h1>
    
    <!-- 旋转动画 -->
    <svg src="icons/spinner.svg" class="spinner"/>
    
    <!-- 脉冲动画 -->
    <svg src="icons/pulse.svg" class="pulse"/>
    
    <!-- 弹跳动画 -->
    <svg src="icons/bounce.svg" class="bounce"/>
</body>
</rml>
css
/* svg_animation.rcss */
.spinner {
    width: 48px;
    height: 48px;
    animation: rotate 1s linear infinite;
}

.pulse {
    width: 48px;
    height: 48px;
    animation: pulse 2s ease-in-out infinite;
}

.bounce {
    width: 48px;
    height: 48px;
    animation: bounce 1s ease-in-out infinite;
}

@keyframes rotate {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

@keyframes pulse {
    0%, 100% {
        transform: scale(1);
        opacity: 1;
    }
    50% {
        transform: scale(1.1);
        opacity: 0.8;
    }
}

@keyframes bounce {
    0%, 100% {
        transform: translateY(0);
    }
    50% {
        transform: translateY(-10px);
    }
}

4.2 程序化 SVG 动画

cpp
// SVGAnimator.h
#pragma once
#include <RmlUi/Core/Element.h>
#include <string>

class SVGAnimator
{
public:
    static void AnimateRotation(Rml::Element* svg, float angle, float duration);
    static void AnimateScale(Rml::Element* svg, float scale, float duration);
    static void AnimateOpacity(Rml::Element* svg, float opacity, float duration);

private:
    static void ApplyTransform(Rml::Element* element, const std::string& transform);
};

// SVGAnimator.cpp
void SVGAnimator::AnimateRotation(Rml::Element* svg, float angle, float duration)
{
    std::string transform = "rotate(" + std::to_string(angle) + "deg)";
    
    svg->Animate(
        "transform",
        transform,
        duration,
        Rml::Tween::CubicEaseInOut
    );
}

void SVGAnimator::AnimateScale(Rml::Element* svg, float scale, float duration)
{
    std::string transform = "scale(" + std::to_string(scale) + ")";
    
    svg->Animate(
        "transform",
        transform,
        duration,
        Rml::Tween::BackEaseOut
    );
}

void SVGAnimator::AnimateOpacity(Rml::Element* svg, float opacity, float duration)
{
    svg->Animate(
        "opacity",
        std::to_string(opacity),
        duration,
        Rml::Tween::Linear
    );
}

五、实战练习

练习 1:创建图标库

创建一个完整的图标库:

  • 加载多个 SVG 图标
  • 实现图标大小变化
  • 支持图标颜色主题

练习 2:实现 SVG 背景

使用 SVG 创建动态背景:

  • 渐变动画
  • 粒子效果
  • 几何图案

练习 3:创建 SVG 交互

创建可交互的 SVG 界面:

  • 可点击的区域
  • 悬停效果
  • 拖拽支持

六、最佳实践

6.1 性能优化

  • ✅ 缓存已加载的 SVG
  • ✅ 使用 SVG 图标精灵
  • ✅ 优化 SVG 文件大小
  • ✅ 避免过度使用 SVG

6.2 兼容性

  • ✅ 提供备用图标
  • ✅ 处理加载失败
  • ✅ 支持不同分辨率
  • ✅ 考虑性能影响

6.3 设计建议

  • ✅ 保持 SVG 简洁
  • ✅ 使用一致的样式
  • ✅ 考虑可访问性
  • ✅ 提供多个变体

七、下一步

继续学习 Lottie 动画 来实现更复杂的动画效果。


📝 检查清单

基于 MIT 许可发布