Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/editor_bottom_panel.cpp
9905 views
1
/**************************************************************************/
2
/* editor_bottom_panel.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "editor_bottom_panel.h"
32
33
#include "editor/debugger/editor_debugger_node.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_string_names.h"
36
#include "editor/gui/editor_toaster.h"
37
#include "editor/gui/editor_version_button.h"
38
#include "editor/settings/editor_command_palette.h"
39
#include "editor/themes/editor_scale.h"
40
#include "scene/gui/box_container.h"
41
#include "scene/gui/button.h"
42
#include "scene/gui/scroll_container.h"
43
#include "scene/gui/split_container.h"
44
45
void EditorBottomPanel::_notification(int p_what) {
46
switch (p_what) {
47
case NOTIFICATION_THEME_CHANGED: {
48
pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
49
expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
50
left_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));
51
right_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));
52
} break;
53
54
case NOTIFICATION_TRANSLATION_CHANGED:
55
case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: {
56
if (is_layout_rtl()) {
57
bottom_hbox->move_child(left_button, button_scroll->get_index() + 1);
58
bottom_hbox->move_child(right_button, 0);
59
} else {
60
bottom_hbox->move_child(right_button, button_scroll->get_index() + 1);
61
bottom_hbox->move_child(left_button, 0);
62
}
63
} break;
64
}
65
}
66
67
void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock) {
68
for (int i = 0; i < items.size(); i++) {
69
if (items[i].control == p_control) {
70
_switch_to_item(p_visible, i, p_ignore_lock);
71
return;
72
}
73
}
74
}
75
76
void EditorBottomPanel::_scroll(bool p_right) {
77
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
78
if (Input::get_singleton()->is_key_pressed(Key::CTRL)) {
79
h_scroll->set_value(p_right ? h_scroll->get_max() : 0);
80
} else if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) {
81
h_scroll->set_value(h_scroll->get_value() + h_scroll->get_page() * (p_right ? 1 : -1));
82
} else {
83
h_scroll->set_value(h_scroll->get_value() + (h_scroll->get_page() * 0.5) * (p_right ? 1 : -1));
84
}
85
}
86
87
void EditorBottomPanel::_update_scroll_buttons() {
88
bool show_arrows = button_hbox->get_size().width > button_scroll->get_size().width;
89
left_button->set_visible(show_arrows);
90
right_button->set_visible(show_arrows);
91
92
if (show_arrows) {
93
_update_disabled_buttons();
94
}
95
}
96
97
void EditorBottomPanel::_update_disabled_buttons() {
98
HScrollBar *h_scroll = button_scroll->get_h_scroll_bar();
99
left_button->set_disabled(h_scroll->get_value() == 0);
100
right_button->set_disabled(h_scroll->get_value() + h_scroll->get_page() == h_scroll->get_max());
101
}
102
103
void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
104
ERR_FAIL_INDEX(p_idx, items.size());
105
106
if (items[p_idx].control->is_visible() == p_visible) {
107
return;
108
}
109
110
SplitContainer *center_split = Object::cast_to<SplitContainer>(get_parent());
111
ERR_FAIL_NULL(center_split);
112
113
if (p_visible) {
114
if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
115
return;
116
}
117
118
for (int i = 0; i < items.size(); i++) {
119
items[i].button->set_pressed_no_signal(i == p_idx);
120
items[i].control->set_visible(i == p_idx);
121
}
122
if (EditorDebuggerNode::get_singleton() == items[p_idx].control) {
123
// This is the debug panel which uses tabs, so the top section should be smaller.
124
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanelDebuggerOverride"), EditorStringName(EditorStyles)));
125
} else {
126
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
127
}
128
129
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
130
center_split->set_collapsed(false);
131
pin_button->show();
132
133
expand_button->show();
134
if (expand_button->is_pressed()) {
135
EditorNode::get_top_split()->hide();
136
}
137
callable_mp(button_scroll, &ScrollContainer::ensure_control_visible).call_deferred(items[p_idx].button);
138
} else {
139
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
140
items[p_idx].button->set_pressed_no_signal(false);
141
items[p_idx].control->set_visible(false);
142
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
143
center_split->set_collapsed(true);
144
pin_button->hide();
145
146
expand_button->hide();
147
if (expand_button->is_pressed()) {
148
EditorNode::get_top_split()->show();
149
}
150
}
151
152
last_opened_control = items[p_idx].control;
153
}
154
155
void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
156
lock_panel_switching = p_pressed;
157
}
158
159
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
160
EditorNode::get_top_split()->set_visible(!p_pressed);
161
}
162
163
bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
164
if (!p_button->is_pressed()) {
165
_switch_by_control(true, p_control, true);
166
}
167
return false;
168
}
169
170
void EditorBottomPanel::save_layout_to_config(Ref<ConfigFile> p_config_file, const String &p_section) const {
171
int selected_item_idx = -1;
172
for (int i = 0; i < items.size(); i++) {
173
if (items[i].button->is_pressed()) {
174
selected_item_idx = i;
175
break;
176
}
177
}
178
if (selected_item_idx != -1) {
179
p_config_file->set_value(p_section, "selected_bottom_panel_item", selected_item_idx);
180
} else {
181
p_config_file->set_value(p_section, "selected_bottom_panel_item", Variant());
182
}
183
}
184
185
void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, const String &p_section) {
186
bool has_active_tab = false;
187
if (p_config_file->has_section_key(p_section, "selected_bottom_panel_item")) {
188
int selected_item_idx = p_config_file->get_value(p_section, "selected_bottom_panel_item");
189
if (selected_item_idx >= 0 && selected_item_idx < items.size()) {
190
// Make sure we don't try to open contextual editors which are not enabled in the current context.
191
if (items[selected_item_idx].button->is_visible()) {
192
_switch_to_item(true, selected_item_idx);
193
has_active_tab = true;
194
}
195
}
196
}
197
// If there is no active tab we need to collapse the panel.
198
if (!has_active_tab) {
199
items[0].control->show(); // _switch_to_item() can collapse only visible tabs.
200
_switch_to_item(false, 0);
201
}
202
}
203
204
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
205
Button *tb = memnew(Button);
206
tb->set_theme_type_variation("BottomPanelButton");
207
tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item, true));
208
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
209
tb->set_text(p_text);
210
tb->set_shortcut(p_shortcut);
211
tb->set_toggle_mode(true);
212
tb->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
213
item_vbox->add_child(p_item);
214
215
bottom_hbox->move_to_front();
216
button_hbox->add_child(tb);
217
if (p_at_front) {
218
button_hbox->move_child(tb, 0);
219
}
220
p_item->set_v_size_flags(Control::SIZE_EXPAND_FILL);
221
p_item->hide();
222
223
BottomPanelItem bpi;
224
bpi.button = tb;
225
bpi.control = p_item;
226
bpi.name = p_text;
227
if (p_at_front) {
228
items.insert(0, bpi);
229
} else {
230
items.push_back(bpi);
231
}
232
233
return tb;
234
}
235
236
void EditorBottomPanel::remove_item(Control *p_item) {
237
bool was_visible = false;
238
for (int i = 0; i < items.size(); i++) {
239
if (items[i].control == p_item) {
240
if (p_item->is_visible_in_tree()) {
241
was_visible = true;
242
}
243
item_vbox->remove_child(items[i].control);
244
button_hbox->remove_child(items[i].button);
245
memdelete(items[i].button);
246
items.remove_at(i);
247
break;
248
}
249
}
250
251
if (was_visible) {
252
// Open the first panel to ensure that if the removed dock was visible, the bottom
253
// panel will not collapse.
254
_switch_to_item(true, 0, true);
255
} else if (last_opened_control == p_item) {
256
// When a dock is removed by plugins, it might not have been visible, and it
257
// might have been the last_opened_control. We need to make sure to reset the last opened control.
258
last_opened_control = items[0].control;
259
}
260
}
261
262
void EditorBottomPanel::make_item_visible(Control *p_item, bool p_visible, bool p_ignore_lock) {
263
_switch_by_control(p_visible, p_item, p_ignore_lock);
264
}
265
266
void EditorBottomPanel::move_item_to_end(Control *p_item) {
267
for (int i = 0; i < items.size(); i++) {
268
if (items[i].control == p_item) {
269
items[i].button->move_to_front();
270
SWAP(items.write[i], items.write[items.size() - 1]);
271
break;
272
}
273
}
274
}
275
276
void EditorBottomPanel::hide_bottom_panel() {
277
for (int i = 0; i < items.size(); i++) {
278
if (items[i].control->is_visible()) {
279
_switch_to_item(false, i);
280
break;
281
}
282
}
283
}
284
285
void EditorBottomPanel::toggle_last_opened_bottom_panel() {
286
// Select by control instead of index, so that the last bottom panel is opened correctly
287
// if it's been reordered since.
288
if (last_opened_control) {
289
_switch_by_control(!last_opened_control->is_visible(), last_opened_control, true);
290
} else {
291
// Open the first panel in the list if no panel was opened this session.
292
_switch_to_item(true, 0, true);
293
}
294
}
295
296
void EditorBottomPanel::set_expanded(bool p_expanded) {
297
expand_button->set_pressed(p_expanded);
298
}
299
300
EditorBottomPanel::EditorBottomPanel() {
301
item_vbox = memnew(VBoxContainer);
302
add_child(item_vbox);
303
304
bottom_hbox = memnew(HBoxContainer);
305
bottom_hbox->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); // Adjust for the height of the "Expand Bottom Dock" icon.
306
item_vbox->add_child(bottom_hbox);
307
308
left_button = memnew(Button);
309
left_button->set_tooltip_text(TTRC("Scroll Left\nHold Ctrl to scroll to the begin.\nHold Shift to scroll one page."));
310
left_button->set_accessibility_name(TTRC("Scroll Left"));
311
left_button->set_theme_type_variation("BottomPanelButton");
312
left_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
313
left_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(false));
314
bottom_hbox->add_child(left_button);
315
left_button->hide();
316
317
button_scroll = memnew(ScrollContainer);
318
button_scroll->set_h_size_flags(Control::SIZE_EXPAND_FILL);
319
button_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_SHOW_NEVER);
320
button_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
321
button_scroll->get_h_scroll_bar()->connect(CoreStringName(changed), callable_mp(this, &EditorBottomPanel::_update_scroll_buttons), CONNECT_DEFERRED);
322
button_scroll->get_h_scroll_bar()->connect(SceneStringName(value_changed), callable_mp(this, &EditorBottomPanel::_update_disabled_buttons).unbind(1), CONNECT_DEFERRED);
323
bottom_hbox->add_child(button_scroll);
324
325
right_button = memnew(Button);
326
right_button->set_tooltip_text(TTRC("Scroll Right\nHold Ctrl to scroll to the end.\nHold Shift to scroll one page."));
327
right_button->set_accessibility_name(TTRC("Scroll Right"));
328
right_button->set_theme_type_variation("BottomPanelButton");
329
right_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
330
right_button->connect(SceneStringName(pressed), callable_mp(this, &EditorBottomPanel::_scroll).bind(true));
331
bottom_hbox->add_child(right_button);
332
right_button->hide();
333
334
callable_mp(this, &EditorBottomPanel::_update_scroll_buttons).call_deferred();
335
336
button_hbox = memnew(HBoxContainer);
337
button_hbox->set_h_size_flags(Control::SIZE_EXPAND | Control::SIZE_SHRINK_BEGIN);
338
button_scroll->add_child(button_hbox);
339
340
editor_toaster = memnew(EditorToaster);
341
bottom_hbox->add_child(editor_toaster);
342
343
EditorVersionButton *version_btn = memnew(EditorVersionButton(EditorVersionButton::FORMAT_BASIC));
344
// Fade out the version label to be less prominent, but still readable.
345
version_btn->set_self_modulate(Color(1, 1, 1, 0.65));
346
version_btn->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
347
bottom_hbox->add_child(version_btn);
348
349
// Add a dummy control node for horizontal spacing.
350
Control *h_spacer = memnew(Control);
351
bottom_hbox->add_child(h_spacer);
352
353
pin_button = memnew(Button);
354
bottom_hbox->add_child(pin_button);
355
pin_button->hide();
356
pin_button->set_theme_type_variation("FlatMenuButton");
357
pin_button->set_toggle_mode(true);
358
pin_button->set_tooltip_text(TTRC("Pin Bottom Panel Switching"));
359
pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
360
361
expand_button = memnew(Button);
362
bottom_hbox->add_child(expand_button);
363
expand_button->hide();
364
expand_button->set_theme_type_variation("FlatMenuButton");
365
expand_button->set_toggle_mode(true);
366
expand_button->set_accessibility_name(TTRC("Expand Bottom Panel"));
367
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTRC("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
368
expand_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_expand_button_toggled));
369
}
370
371