(x, y)
的组合,比如资源类型,就是一个完整的类类型表示。Project::m_notifier
管理了所有的Listener
,即所有设置的Listener都加入到了Project::m_notifier
数组中,在每个槽函数中完成数据的遍历操作。
即通知中心在一个工程中只有一个,用于管理所有的监听者。
Listener
Project::m_notifier
SetNotifier
时传入Project::m_notifier
所有需要监听来自通知中心发送的事件的对象,可以继承自Listener
,重写对应的虚函数,即可。
接口分为两类:
virtual void OnBeginEdit(bool isUndoRedo) {}
virtual void OnAfterEndEdit(const ChangeDataCtn &changeDataCtn, bool undoRedo) {}
virtual void OnBeginExecAtomCmd() {}
virtual void OnAfterEndExecAtomCmd(const ChangeDataCtn &changeDataCtn) {}
这类接口通过CommandCtrl控制,一般是在命令执行前后发送。
DECLARE_LISTEN_FUNC(OnAfterEffectInserted)
DECLARE_LISTEN_FUNC(OnAfterEffectRemoved)
这类接口需要继承Listener的子类,实现相应的接口完成。
在通知中心的构造函数中,通过使用QT的信号槽机制,将对应的命令控制中心的信号槽连接起来。
信号由命令控制中心发送,连接到对应的槽。
也可以主动发送通知:
此处主动调用通知中心的接口OnAfterEventNodeEdited
,而内部会调用到Listen的同名接口中,如果有继承自Listen的子类重写了这个接口,那么就能收到和处理这类消息。
···
// 执行
virtual void Exec() = 0;
// 撤销
virtual void Undo() = 0;
// 恢复
virtual void Redo() = 0;
···
void DataAccessBase::ExecAtomCommand(AtomCommand::ptr atomCommand)
执行原子命令,只有三步:
执行undo撤销时,
执行redo恢复时,
命令组里存放一次undo/redo的所有命令,用一个vector存放。命令组的作用是为了在像滑杆这种控件拖动时会产生大量的命令,将他们打包到一个命令组中,而每次的命令,依旧会去执行底层数据模型的更新(这种更新是有必要的,有时需要把这个滑杆的数据变化体现在另外的UI界面上),在撤销恢复时,依旧保证一次恢复到原始位置。
merge,在把命令加入到命令组中前,判断这条命令的类型是不是自己想要的类型。因为,每条命令都是由一个继承AtomCommand
原子命令的子类,那么merge的实现也是在子类中实现,那么就可以在merge的时候判断传入的命令类型是否是自己即可。
是的话就插入到命令组中,并把之前的命令出栈。
bool LiquifyDeformDragCmd::Merge(AtomCommand *other)
{
auto dragCmd = dynamic_cast(other);
if (!dragCmd) {
return false;
}
m_curValue = dragCmd->m_curValue;
return true;
}
数据模型层,数据访问层,执行层,UI层
对最终的数据进行存储和操作的层。在代码中使用Private后缀的类,比如EffectModelPrivate。数据模型层只可以通过数据访问层进行访问,与其他层次完全隔离。
针对每个数据模型定义一系列的操作接口,接口中负责对数据模型进行操作,并且记录原子命令,收集操作日志,与数据模型强绑定。
所有外部需要更改数据,只能通过数据访问层接口去操作。
执行层抽象为一个个单独的执行器,一个功能对应于一个执行器,功能之间彼此互不干扰。执行层对底层使用数据访问层的接口对数据模型进行操作,对上层则使用key与工厂的方式实现与UI的解耦合。
UI层采用QWidget + QML的方式组织工程。工程的主体框架使用QDockWidget以实现自定义布局,每一个Dock内则使用QQuickWidget嵌入QML窗体。
EffectPluginManager
使用一个全局单实例,来作为插件管理器。
#define REGISTER_EFFECT_PLUGIN(type, classname) \
inline bool s_bRegiste##classname = \
EffectPluginManager::Instance()->InsertEffectPlugin(type, new classname());
注册时传入一个type
类型,和一个具体插件的类名。
注意:
#表示:对应变量字符串化
##表示:把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符
连接符#@:它将单字符标记符变换为单字符,即加单引号。例如:
#define B(x) #@x 则B(a)即'a',B(1)即'1',但B(abc)却不甚有效
定义所有具体插件需要实现的接口。
EffectPluginInterface
基本上接口中定义的都是纯虚函数,里面的接口需要具体的插件类去选择实现之。
注意:
满足下面条件的 C++ 类成为接口:
类中没有定义任何变量
所有的成员函数都是纯虚函数,且是公有的
所有的插件都需要直接或者间接的继承自插件接口类EffectPluginInterface
。
目的:让新添加效果的时候更简单,只需要简单的配置JSON配置文件(供给非开发人员使用)
配合插件系统,让面板的layout成为插件的一部分,由插件掌控,后续方便更新,也方便一旦UI稿变化后,只需要小动JSON配置文件即可。
方法:
ListView2里的Loader加载的独立的QML文件,采用模板方法,抽取一个公共的基类GroupItem
和基本接口,具体的子类根据需要去实现相应的接口,完成流程上的规范处理。
当前基类中规定了对于更新model、更新UI布局(只需要首次才需要)、及更UI数据(每次刷新都需要更新)的规定:
function updateAttributeValue(varListModel) {
itemListModel = varListModel
updateWidgetInfo()
updateWidgetValue()
}
调用栈:
InspectorEventModel::TermAdd()
EventAddTermExecutor::Exec()
EventSelectExecutor::Exec()
Executor::EndLog()
CommandCtrl::EndLog
m_undoStack.push // 如撤销栈
首先创建一个全局唯一的命令控制中心:CommandCtrl对象
创建一个原子命令对象EffectSetAttrCmd,EffectSetAttrCmd继承自AtomCommand,并实现执行、撤销、恢复接口。class EffectSetAttrCmd : public AtomCommand
auto command = std::make_shared(m_owner, GetUniqueKey(), key, value);
ExecAtomCommand(command);
执行原子命令,即调用命令控制中心的执行接口Exec
void DataAccessBase::ExecAtomCommand(AtomCommand::ptr atomCommand)
{
m_commandCtrl->Exec(atomCommand);
}
void CommandCtrl::Exec(AtomCommand::ptr command)
,完成:
m_atomCmdQueue