1#include "editor_history.h"
2
3#include "editor.h"
4#include "editor_actions.h"
5
6#include <engine/font_icons.h>
7#include <engine/shared/config.h>
8
9void CEditorHistory::RecordAction(const std::shared_ptr<IEditorAction> &pAction)
10{
11 RecordAction(pAction, pDisplay: nullptr);
12}
13
14void CEditorHistory::Execute(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay)
15{
16 pAction->Redo();
17 RecordAction(pAction, pDisplay);
18}
19
20void CEditorHistory::RecordAction(const std::shared_ptr<IEditorAction> &pAction, const char *pDisplay)
21{
22 if(m_IsBulk)
23 {
24 m_vpBulkActions.push_back(x: pAction);
25 return;
26 }
27
28 m_vpRedoActions.clear();
29
30 if((int)m_vpUndoActions.size() >= g_Config.m_ClEditorMaxHistory)
31 {
32 m_vpUndoActions.pop_front();
33 }
34
35 if(pDisplay == nullptr)
36 m_vpUndoActions.emplace_back(args: pAction);
37 else
38 m_vpUndoActions.emplace_back(args: std::make_shared<CEditorActionBulk>(args: Map(), args: std::vector<std::shared_ptr<IEditorAction>>{pAction}, args&: pDisplay));
39}
40
41bool CEditorHistory::Undo()
42{
43 if(m_vpUndoActions.empty())
44 return false;
45
46 auto pLastAction = m_vpUndoActions.back();
47 m_vpUndoActions.pop_back();
48
49 pLastAction->Undo();
50
51 m_vpRedoActions.emplace_back(args&: pLastAction);
52 return true;
53}
54
55bool CEditorHistory::Redo()
56{
57 if(m_vpRedoActions.empty())
58 return false;
59
60 auto pLastAction = m_vpRedoActions.back();
61 m_vpRedoActions.pop_back();
62
63 pLastAction->Redo();
64
65 m_vpUndoActions.emplace_back(args&: pLastAction);
66 return true;
67}
68
69void CEditorHistory::Clear()
70{
71 m_vpUndoActions.clear();
72 m_vpRedoActions.clear();
73}
74
75void CEditorHistory::BeginBulk()
76{
77 m_IsBulk = true;
78 m_vpBulkActions.clear();
79}
80
81void CEditorHistory::EndBulk(const char *pDisplay)
82{
83 if(!m_IsBulk)
84 return;
85 m_IsBulk = false;
86
87 // Record bulk action
88 if(!m_vpBulkActions.empty())
89 RecordAction(pAction: std::make_shared<CEditorActionBulk>(args: Map(), args&: m_vpBulkActions, args&: pDisplay, args: true));
90
91 m_vpBulkActions.clear();
92}
93
94void CEditorHistory::EndBulk(int DisplayToUse)
95{
96 EndBulk(pDisplay: (DisplayToUse < 0 || DisplayToUse >= (int)m_vpBulkActions.size()) ? nullptr : m_vpBulkActions[DisplayToUse]->DisplayText());
97}
98
99void CEditor::RenderEditorHistory(CUIRect View)
100{
101 enum EHistoryType
102 {
103 EDITOR_HISTORY,
104 ENVELOPE_HISTORY,
105 SERVER_SETTINGS_HISTORY
106 };
107
108 static EHistoryType s_HistoryType = EDITOR_HISTORY;
109 static int s_ActionSelectedIndex = 0;
110 static CListBox s_ListBox;
111 s_ListBox.SetActive(m_Dialog == DIALOG_NONE && !Ui()->IsPopupOpen());
112
113 const bool GotSelection = s_ListBox.Active() && s_ActionSelectedIndex >= 0 && (size_t)s_ActionSelectedIndex < Map()->m_vSettings.size();
114
115 CUIRect ToolBar, Button, Label, List, DragBar;
116 View.HSplitTop(Cut: 22.0f, pTop: &DragBar, pBottom: nullptr);
117 DragBar.y -= 2.0f;
118 DragBar.w += 2.0f;
119 DragBar.h += 4.0f;
120 DoEditorDragBar(View, pDragBar: &DragBar, Side: EDragSide::TOP, pValue: &m_aExtraEditorSplits[EXTRAEDITOR_HISTORY]);
121 View.HSplitTop(Cut: 20.0f, pTop: &ToolBar, pBottom: &View);
122 View.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &List);
123 ToolBar.HMargin(Cut: 2.0f, pOtherRect: &ToolBar);
124
125 CUIRect TypeButtons, HistoryTypeButton;
126 const int HistoryTypeBtnSize = 70.0f;
127 ToolBar.VSplitLeft(Cut: 3 * HistoryTypeBtnSize, pLeft: &TypeButtons, pRight: &Label);
128
129 // history type buttons
130 {
131 TypeButtons.VSplitLeft(Cut: HistoryTypeBtnSize, pLeft: &HistoryTypeButton, pRight: &TypeButtons);
132 static int s_EditorHistoryButton = 0;
133 if(DoButton_Ex(pId: &s_EditorHistoryButton, pText: "Editor", Checked: s_HistoryType == EDITOR_HISTORY, pRect: &HistoryTypeButton, Flags: BUTTONFLAG_LEFT, pToolTip: "Show map editor history.", Corners: IGraphics::CORNER_L))
134 {
135 s_HistoryType = EDITOR_HISTORY;
136 }
137
138 TypeButtons.VSplitLeft(Cut: HistoryTypeBtnSize, pLeft: &HistoryTypeButton, pRight: &TypeButtons);
139 static int s_EnvelopeEditorHistoryButton = 0;
140 if(DoButton_Ex(pId: &s_EnvelopeEditorHistoryButton, pText: "Envelope", Checked: s_HistoryType == ENVELOPE_HISTORY, pRect: &HistoryTypeButton, Flags: BUTTONFLAG_LEFT, pToolTip: "Show envelope editor history.", Corners: IGraphics::CORNER_NONE))
141 {
142 s_HistoryType = ENVELOPE_HISTORY;
143 }
144
145 TypeButtons.VSplitLeft(Cut: HistoryTypeBtnSize, pLeft: &HistoryTypeButton, pRight: &TypeButtons);
146 static int s_ServerSettingsHistoryButton = 0;
147 if(DoButton_Ex(pId: &s_ServerSettingsHistoryButton, pText: "Settings", Checked: s_HistoryType == SERVER_SETTINGS_HISTORY, pRect: &HistoryTypeButton, Flags: BUTTONFLAG_LEFT, pToolTip: "Show server settings editor history.", Corners: IGraphics::CORNER_R))
148 {
149 s_HistoryType = SERVER_SETTINGS_HISTORY;
150 }
151 }
152
153 SLabelProperties InfoProps;
154 InfoProps.m_MaxWidth = ToolBar.w - 60.f;
155 InfoProps.m_EllipsisAtEnd = true;
156 Label.VSplitLeft(Cut: 8.0f, pLeft: nullptr, pRight: &Label);
157 Ui()->DoLabel(pRect: &Label, pText: "Editor history. Click on an action to undo all actions above.", Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: InfoProps);
158
159 CEditorHistory *pCurrentHistory;
160 if(s_HistoryType == EDITOR_HISTORY)
161 pCurrentHistory = &Map()->m_EditorHistory;
162 else if(s_HistoryType == ENVELOPE_HISTORY)
163 pCurrentHistory = &Map()->m_EnvelopeEditorHistory;
164 else if(s_HistoryType == SERVER_SETTINGS_HISTORY)
165 pCurrentHistory = &Map()->m_ServerSettingsHistory;
166 else
167 return;
168
169 // delete button
170 ToolBar.VSplitRight(Cut: 25.0f, pLeft: &ToolBar, pRight: &Button);
171 ToolBar.VSplitRight(Cut: 5.0f, pLeft: &ToolBar, pRight: nullptr);
172 static int s_DeleteButton = 0;
173 if(DoButton_FontIcon(pId: &s_DeleteButton, pText: FontIcon::TRASH, Checked: (!pCurrentHistory->m_vpUndoActions.empty() || !pCurrentHistory->m_vpRedoActions.empty()) ? 0 : -1, pRect: &Button, Flags: BUTTONFLAG_LEFT, pToolTip: "Clear the history.", Corners: IGraphics::CORNER_ALL, FontSize: 9.0f) || (GotSelection && CLineInput::GetActiveInput() == nullptr && m_Dialog == DIALOG_NONE && Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_DELETE)))
174 {
175 pCurrentHistory->Clear();
176 s_ActionSelectedIndex = 0;
177 }
178
179 // actions list
180 int RedoSize = (int)pCurrentHistory->m_vpRedoActions.size();
181 int UndoSize = (int)pCurrentHistory->m_vpUndoActions.size();
182 s_ActionSelectedIndex = RedoSize;
183 s_ListBox.DoStart(RowHeight: 15.0f, NumItems: RedoSize + UndoSize, ItemsPerRow: 1, RowsPerScroll: 3, SelectedIndex: s_ActionSelectedIndex, pRect: &List);
184
185 for(int i = 0; i < RedoSize; i++)
186 {
187 const CListboxItem Item = s_ListBox.DoNextItem(pId: &pCurrentHistory->m_vpRedoActions[i], Selected: s_ActionSelectedIndex >= 0 && s_ActionSelectedIndex == i);
188 if(!Item.m_Visible)
189 continue;
190
191 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
192
193 SLabelProperties Props;
194 Props.m_MaxWidth = Label.w;
195 Props.m_EllipsisAtEnd = true;
196 TextRender()->TextColor(Color: {.5f, .5f, .5f});
197 TextRender()->TextOutlineColor(Color: TextRender()->DefaultTextOutlineColor());
198 Ui()->DoLabel(pRect: &Label, pText: pCurrentHistory->m_vpRedoActions[i]->DisplayText(), Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: Props);
199 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
200 }
201
202 for(int i = 0; i < UndoSize; i++)
203 {
204 const CListboxItem Item = s_ListBox.DoNextItem(pId: &pCurrentHistory->m_vpUndoActions[UndoSize - i - 1], Selected: s_ActionSelectedIndex >= RedoSize && s_ActionSelectedIndex == (i + RedoSize));
205 if(!Item.m_Visible)
206 continue;
207
208 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
209
210 SLabelProperties Props;
211 Props.m_MaxWidth = Label.w;
212 Props.m_EllipsisAtEnd = true;
213 Ui()->DoLabel(pRect: &Label, pText: pCurrentHistory->m_vpUndoActions[UndoSize - i - 1]->DisplayText(), Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: Props);
214 }
215
216 { // Base action "Loaded map" that cannot be undone
217 static int s_BaseAction;
218 const CListboxItem Item = s_ListBox.DoNextItem(pId: &s_BaseAction, Selected: s_ActionSelectedIndex == RedoSize + UndoSize);
219 if(Item.m_Visible)
220 {
221 Item.m_Rect.VMargin(Cut: 5.0f, pOtherRect: &Label);
222
223 Ui()->DoLabel(pRect: &Label, pText: "Loaded map", Size: 10.0f, Align: TEXTALIGN_ML);
224 }
225 }
226
227 const int NewSelected = s_ListBox.DoEnd();
228 if(s_ActionSelectedIndex != NewSelected)
229 {
230 // Figure out if we should undo or redo some actions
231 // Undo everything until the selected index
232 if(NewSelected > s_ActionSelectedIndex)
233 {
234 for(int i = 0; i < (NewSelected - s_ActionSelectedIndex); i++)
235 {
236 pCurrentHistory->Undo();
237 }
238 }
239 else
240 {
241 for(int i = 0; i < (s_ActionSelectedIndex - NewSelected); i++)
242 {
243 pCurrentHistory->Redo();
244 }
245 }
246 s_ActionSelectedIndex = NewSelected;
247 }
248}
249