【SDU Chart Team - Core】抽象组件接口 和 组件基础模块

抽象组件接口和组件基础模块

抽象组件是对所有组件的泛化,也是所有组件的基类;组件基础模块是对这些抽象内容的基本实现。两者之间是继承关系。

抽象组件接口

私有属性

组件抽象接口规定了如下私有属性,也是全体组件的固有属性:

属性名类型解释
_type字符串组件的类型:用于识别组件的种类、创建和序列化
_module列表组件支持的模块:用于检测组件是否支持某条命令
_id字符串组件的Id:组件的UUID,全局唯一
parent弱指针组件的父亲:树形关系中组件的父亲
children指针列表组件的儿子:树形关系中组件的儿子

反射式模块管理

由于组件使用了模块化设计,判断组件是否支持某个模块是最开始需要解决的问题。组件抽象接口使用模块表解决了这个问题。

  • 模块表

    一个字符串的集合

    std::set<std::string> _modules;
    

    其中每个元素都是预定义的模块名。

    • 模块表的维护

      定义如下方法对模块表进行维护(仅含增、查功能):

      // 添加模块类型
      void addModuleTypes(const std::string &module);
      // 获取模块类型
      const std::set<std::string> getModuleTypes() const;
      // 查看模块类型是否存在
      bool moduleContains(const std::string &module) const;
      

      模块表事实上是一个只读表,仅在特定时间段初始化,故只需要添加的模块。其他时间,使用后面两个方法只读。

    • 模块表初始化

      在组件的init方法中进行初始化,即在组建被创建的过程中建立该表。init方法要求先调用父类的init方法。基于此原则可以正确地初始化所有祖先。

  • 游标切换器中的过滤机制

    切换器中传入的指针均为有效支持该模块的指针,主要使用如下方法进行过滤:

    // 返回合法选中的组件,否则为Null
    std::shared_ptr<ComponentAbstract> Register::selected_with(const std::string &module) {
        if (auto c = cursor.lock()) if (module == "" || c->moduleContains(module)) return c;
        return std::shared_ptr<ComponentAbstract>(nullptr);
    }
    
    // 返回合法选中的组件,若无则空
    std::vector<std::shared_ptr<ComponentAbstract>> Register::multi_selected_with(const std::string &module) {
        std::vector<std::shared_ptr<ComponentAbstract>> vec;
        for (auto &wp : cursors) if (auto c = wp.lock()) if (module == "" || c->moduleContains(module)) vec.push_back(c);
        return std::move(vec);
    }
    

    单游标不支持则返回空指针;多游标均不支持则返回空列表。

  • 运行时动态类型转换

    保证了模块名和模块一一对应后,游标选择其中可以合法进行动态类型转换。例如组件基础模块中:

    auto comp = std::dynamic_pointer_cast<ComponentBasics>(Canvas::components[id]);
    

组件树形关系

  • 组件的树形关系

    组件存在树形的逻辑关系,基于树形关系进一步实现几何绑定、分组等功能。在组件生命周期中的体现则是祖先组件的删除会使得所有的后代组件被删除、祖先组件的拷贝将拷贝所有后代组件(这些情况类似于DOM树)。
    组件树形结构与生命周期

上图是类DOM的生命周期处理方式。项目实际处理中,提供了默认处理方法:拷贝与类DOM一致;但删除只删除一个元素,保留被删除元素的儿子并使他们成为顶级组件。

  • 树形关系的管理

    以下是一些常规树上操作:

    // 获取子节点
    std::vector<std::shared_ptr<ComponentAbstract>> ComponentAbstract::getChildren() const {
        std::vector<std::shared_ptr<ComponentAbstract>> vec;
        for (auto &p : children) vec.push_back(p);
        return std::move(vec);
    }
    // 获取父节点
    std::shared_ptr<ComponentAbstract> ComponentAbstract::getParent() const {
        return parent.lock();
    }
    // 获取所有祖先
    std::vector<std::shared_ptr<ComponentAbstract>> ComponentAbstract::getAncestors() const {
        if (auto pp = parent.lock()) {
            auto list = pp->getAncestors();
            list.push_back(pp);
            return std::move(list);
        }
        return std::move(std::vector<std::shared_ptr<ComponentAbstract>>());
    }
    // 获取所有后代
    std::vector<std::shared_ptr<ComponentAbstract>> ComponentAbstract::getDescendants() const {
        auto vec = getChildren();
        for (auto &p : children) {
            auto vecp = p->getAncestors();
            vec.insert(vec.end(), vecp.begin(), vecp.end());
        }
        return std::move(vec);
    }
    // 是祖先(包括自己)
    bool ComponentAbstract::isAncestorOf(const std::weak_ptr<ComponentAbstract> &component) const {
        auto comp = component.lock();
        while (comp) {
            if (comp == shared_from_this()) return true;
            comp = comp->getParent();
        }
        return false;
    }
    
  • 获取顶级节点

    输入:一组节点

    输出:若这些节点中存在祖先后代关系,只输出祖先

    std::vector<std::shared_ptr<ComponentAbstract>> ComponentAbstract::extractTop(const std::vector<std::shared_ptr<ComponentAbstract>> &components) {
        std::set<int> not_top;
        int n = components.size();
        for (int i = 0; i < n; i++) {
            if (not_top.count(i)) continue;
            for (int j = i + 1; j < n; j++) {
                if (not_top.count(j)) continue;
                auto &a = components[i];
                auto &b = components[j];
                if (a->isAncestorOf(b)) not_top.insert(j); // a是b的祖先,b不是顶级节点
                else if (b->isAncestorOf(a)) not_top.insert(i); // b是a的祖先,a不是顶级节点
            }
        }
        std::vector<std::shared_ptr<ComponentAbstract>> vec;
        for (int i = 0; i < n; i++) if (!not_top.count(i)) vec.push_back(components[i]);
        return std::move(vec);
    }
    

    项目中只进行了基本实现,使用暴力的方法,复杂度为 O ( n 2 h ) O(n^2h) O(n2h)

SVG接口

// 获取维护的SVGI
virtual std::weak_ptr<Lewzen::SVGIElement> getSVGI() const = 0;
// 获取需要的Defs
virtual std::vector<std::weak_ptr<Lewzen::SVGIElement>> getDefs() const = 0;

具体的SVGI和Desf交给组件基础模块实现。

几何学

  • 坐标系

    // 获取坐标系
    virtual const std::shared_ptr<Lewzen::CoordinateSystem> getCoordinateSystem() const = 0;
    

    交给具体的组件模块实现。

  • 创建点

    // 创建该坐标系下的点
    Lewzen::Point2D createPoint() const;
    Lewzen::Point2D createPoint(const double &x, const double &y) const;
    // 创建一个关键点
    std::shared_ptr<CorePoint> createCorePoint(const std::string &id) const;
    std::shared_ptr<CorePoint> createCorePoint(const std::string &id, const double &x, const double &y) const;
    

    一种是创建普通的点,一种是创建关键点。

  • 点坐标系转换

    结合坐标系转换实现。

生命周期操作

这些操作用于组件在生命周期中的各个状态间进行转移,这些操作被包含在画布管理,通过画布管理中的接口进行调用。

  • 初始化

    // 非构造初始化
    virtual void init() = 0;
    

    初始化中需要初始化父类、复合组件的子组件、然后是自己的模块、关键点、SVG等信息。

  • 拷贝 & 克隆

    // 拷贝
    virtual ComponentAbstract &operator=(const ComponentAbstract &comp) = 0;
    // 克隆
    virtual std::shared_ptr<ComponentAbstract> clone() = 0;
    

    拷贝是重载等号运算符;克隆等价于创建组件后进行拷贝。

  • 序列化

    // 序列化,并记录已操作的
    virtual void serialize(json &j, std::vector<std::string> &processed) = 0;
    

    将组件中的关键信息序列化为Json。

  • 反序列化 & 反序列化生成

    // 反序列化
    virtual ComponentAbstract &operator=(const json &j) = 0;
    // 反序列化生成
    static std::shared_ptr<ComponentAbstract> deserialize(const json &j);
    

    反序列化将Json中的信息设置到对象中;反序列化生成等价于创建组件后进行反序列化。

事件

  • 关于生命周期的事件

    virtual void onAdded() = 0;
    virtual void onChanged() = 0;
    virtual void onRemoved() = 0;
    virtual void onReadded() = 0;
    virtual void onDiscarded() = 0;
    

    默认实现中会对画布范围进行更新。

  • 关于父子关系更新的事件

    virtual void onParentChanged(const std::weak_ptr<ComponentAbstract> &prev, const std::weak_ptr<ComponentAbstract> &now) = 0;
    virtual void onChildAdded(const std::weak_ptr<ComponentAbstract> &child) = 0;
    virtual void onChildRemoved(const std::weak_ptr<ComponentAbstract> &child) = 0;
    

    默认实现中会对坐标系进行重变换,使其满足父子关系。

组件基础模块

除了对组件抽象接口进行了完整的实现外,还包含以下内容:

移动

组件基础模块中的移动是对所有关键点进行移动,即无视其他变换的最简单的实现:

// 移动组件
void ComponentBasics::move(const double &dx, const double &dy) {
    for (auto &p : getCorePoints()) {
        if (auto pp = p.lock()) {
            *pp += createPoint(dx, dy);
        }
    }
    onMoved(dx, dy); // 移动变换后操作
    onChanged();
}

另外,还需要实现移动至方法:

// 移动组件
void ComponentBasics::moveTo(const double &x, const double &y) {
    auto delta = createPoint(x, y) - getCenter(); // 计算坐标系的Δ
    ComponentBasics::move(delta.get_x(), delta.get_y());
}

关键点移动

关键点用于图形的生成,可被用户交互。组件中维护的是关键点的“关键点Id-关键点”映射。用户可以移动关键点:

// 移动关键点
void ComponentBasics::moveCorePoint(const std::string &id, const double &dx, const double &dy) {
    onChanged();
}

移除关键点

某些关键点允许用户的删除:

void ComponentBasics::removeCorePoint(const std::string &id) {
 if (!corePoints.count(id)) return;
    corePoints[id]->on_remove();
    onChanged();
}

组件层级变换

调用画布管理中的层级变换:

// 调整层级
void ComponentBasics::forward() {
    Canvas::forward(getId());
}
void ComponentBasics::backward() {
    Canvas::backward(getId());
}
void ComponentBasics::front() {
    Canvas::front(getId());
}
void ComponentBasics::back() {
    Canvas::back(getId());
}

移动绑定

规定了一系列接口,依托组件的树形关系,使得移动可以被绑定、解绑。默认被移动绑定的组件会与父组件一起移动。
移动绑定
移动绑定时间的实现如下:

// 移动绑定事件
void ComponentBasics::moveBindEvent(const double &dx, const double &dy) {
    for (auto c : getChildren()) if (c->moduleContains("Basics")) { // 绑定移动,遍历合法孩子进行移动
        auto cb = std::dynamic_pointer_cast<ComponentBasics>(c);
        if (cb->isMoveBinded()) cb->move(dx, dy);
    }
}

组件基础模块工作流

组件基础模块工作流
图片描述了整个组件基础模块的工作流,反映了各个方法之间的调用关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值