summaryrefslogtreecommitdiff
path: root/scene/gui
diff options
context:
space:
mode:
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/base_button.cpp7
-rw-r--r--scene/gui/color_picker.cpp51
-rw-r--r--scene/gui/color_picker.h3
-rw-r--r--scene/gui/control.cpp81
-rw-r--r--scene/gui/control.h3
-rw-r--r--scene/gui/dialogs.h2
-rw-r--r--scene/gui/file_dialog.cpp26
-rw-r--r--scene/gui/file_dialog.h2
-rw-r--r--scene/gui/gradient_edit.cpp7
-rw-r--r--scene/gui/grid_container.cpp28
-rw-r--r--scene/gui/item_list.cpp112
-rw-r--r--scene/gui/item_list.h6
-rw-r--r--scene/gui/line_edit.cpp2
-rw-r--r--scene/gui/menu_button.cpp2
-rw-r--r--scene/gui/option_button.cpp12
-rw-r--r--scene/gui/option_button.h1
-rw-r--r--scene/gui/popup_menu.cpp258
-rw-r--r--scene/gui/popup_menu.h17
-rw-r--r--scene/gui/rich_text_label.cpp55
-rw-r--r--scene/gui/rich_text_label.h2
-rw-r--r--scene/gui/scroll_bar.cpp56
-rw-r--r--scene/gui/slider.cpp26
-rw-r--r--scene/gui/tab_container.cpp22
-rw-r--r--scene/gui/tab_container.h1
-rw-r--r--scene/gui/tabs.cpp2
-rw-r--r--scene/gui/text_edit.cpp537
-rw-r--r--scene/gui/text_edit.h170
-rw-r--r--scene/gui/texture_progress.cpp129
-rw-r--r--scene/gui/texture_progress.h12
-rw-r--r--scene/gui/tree.cpp524
-rw-r--r--scene/gui/tree.h4
-rw-r--r--scene/gui/video_player.cpp15
32 files changed, 1306 insertions, 869 deletions
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index 9dfd388c3d..562dd155f9 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -211,6 +211,11 @@ void BaseButton::_gui_input(Ref<InputEvent> p_event) {
if (!toggle_mode) { //mouse press attempt
pressed();
+ if (get_script_instance()) {
+ Variant::CallError ce;
+ get_script_instance()->call(SceneStringNames::get_singleton()->_pressed, NULL, 0, ce);
+ }
+
emit_signal("pressed");
} else {
@@ -307,6 +312,8 @@ void BaseButton::toggled(bool p_pressed) {
}
void BaseButton::set_disabled(bool p_disabled) {
+ if (status.disabled == p_disabled)
+ return;
status.disabled = p_disabled;
update();
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
index 30bcc48149..31be18612f 100644
--- a/scene/gui/color_picker.cpp
+++ b/scene/gui/color_picker.cpp
@@ -77,8 +77,7 @@ void ColorPicker::_notification(int p_what) {
void ColorPicker::set_focus_on_line_edit() {
- c_text->grab_focus();
- c_text->select();
+ c_text->call_deferred("grab_focus");
}
void ColorPicker::_update_controls() {
@@ -159,14 +158,16 @@ void ColorPicker::_update_color() {
updating = true;
for (int i = 0; i < 4; i++) {
- scroll[i]->set_max(255);
scroll[i]->set_step(0.01);
if (raw_mode_enabled) {
+ scroll[i]->set_max(100);
if (i == 3)
scroll[i]->set_max(1);
scroll[i]->set_value(color.components[i]);
} else {
- scroll[i]->set_value(color.components[i] * 255);
+ const int byte_value = color.components[i] * 255;
+ scroll[i]->set_max(next_power_of_2(MAX(255, byte_value)) - 1);
+ scroll[i]->set_value(byte_value);
}
}
@@ -242,6 +243,7 @@ bool ColorPicker::is_raw_mode() const {
}
void ColorPicker::_update_text_value() {
+ bool visible = true;
if (text_is_constructor) {
String t = "Color(" + String::num(color.r) + "," + String::num(color.g) + "," + String::num(color.b);
if (edit_alpha && color.a < 1)
@@ -250,8 +252,13 @@ void ColorPicker::_update_text_value() {
t += ")";
c_text->set_text(t);
} else {
- c_text->set_text(color.to_html(edit_alpha && color.a < 1));
+ if (color.r > 1 || color.g > 1 || color.b > 1 || color.r < 0 || color.g < 0 || color.b < 0) {
+ visible = false;
+ } else {
+ c_text->set_text(color.to_html(edit_alpha && color.a < 1));
+ }
}
+ c_text->set_visible(visible);
}
void ColorPicker::_sample_draw() {
@@ -462,6 +469,31 @@ void ColorPicker::_screen_pick_pressed() {
screen->show_modal();
}
+void ColorPicker::_focus_enter() {
+ if (c_text->has_focus()) {
+ c_text->select_all();
+ return;
+ }
+ for (int i = 0; i < 4; i++) {
+ if (values[i]->get_line_edit()->has_focus()) {
+ values[i]->get_line_edit()->select_all();
+ break;
+ }
+ }
+}
+
+void ColorPicker::_focus_exit() {
+ for (int i = 0; i < 4; i++) {
+ values[i]->get_line_edit()->select(0, 0);
+ }
+ c_text->select(0, 0);
+}
+
+void ColorPicker::_html_focus_exit() {
+ _html_entered(c_text->get_text());
+ _focus_exit();
+}
+
void ColorPicker::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_pick_color", "color"), &ColorPicker::set_pick_color);
@@ -483,6 +515,9 @@ void ColorPicker::_bind_methods() {
ClassDB::bind_method(D_METHOD("_w_input"), &ColorPicker::_w_input);
ClassDB::bind_method(D_METHOD("_preset_input"), &ColorPicker::_preset_input);
ClassDB::bind_method(D_METHOD("_screen_input"), &ColorPicker::_screen_input);
+ ClassDB::bind_method(D_METHOD("_focus_enter"), &ColorPicker::_focus_enter);
+ ClassDB::bind_method(D_METHOD("_focus_exit"), &ColorPicker::_focus_exit);
+ ClassDB::bind_method(D_METHOD("_html_focus_exit"), &ColorPicker::_html_focus_exit);
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_pick_color", "get_pick_color");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "edit_alpha"), "set_edit_alpha", "is_editing_alpha");
@@ -559,11 +594,14 @@ ColorPicker::ColorPicker() :
scroll[i] = memnew(HSlider);
scroll[i]->set_v_size_flags(SIZE_SHRINK_CENTER);
+ scroll[i]->set_focus_mode(FOCUS_NONE);
hbc->add_child(scroll[i]);
values[i] = memnew(SpinBox);
scroll[i]->share(values[i]);
hbc->add_child(values[i]);
+ values[i]->get_line_edit()->connect("focus_entered", this, "_focus_enter");
+ values[i]->get_line_edit()->connect("focus_exited", this, "_focus_exit");
scroll[i]->set_min(0);
scroll[i]->set_page(0);
@@ -589,6 +627,9 @@ ColorPicker::ColorPicker() :
c_text = memnew(LineEdit);
hhb->add_child(c_text);
c_text->connect("text_entered", this, "_html_entered");
+ c_text->connect("focus_entered", this, "_focus_enter");
+ c_text->connect("focus_exited", this, "_html_focus_exit");
+
text_type->set_text("#");
c_text->set_h_size_flags(SIZE_EXPAND_FILL);
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
index 01ae1cc464..40ded4fff5 100644
--- a/scene/gui/color_picker.h
+++ b/scene/gui/color_picker.h
@@ -88,6 +88,9 @@ private:
void _screen_input(const Ref<InputEvent> &p_event);
void _add_preset_pressed();
void _screen_pick_pressed();
+ void _focus_enter();
+ void _focus_exit();
+ void _html_focus_exit();
protected:
void _notification(int);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 01415594d3..c71320f207 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -49,31 +49,41 @@
Dictionary Control::_edit_get_state() const {
Dictionary s;
- s["rect"] = get_rect();
s["rotation"] = get_rotation();
s["scale"] = get_scale();
+ s["pivot"] = get_pivot_offset();
Array anchors;
anchors.push_back(get_anchor(MARGIN_LEFT));
anchors.push_back(get_anchor(MARGIN_TOP));
anchors.push_back(get_anchor(MARGIN_RIGHT));
anchors.push_back(get_anchor(MARGIN_BOTTOM));
s["anchors"] = anchors;
+ Array margins;
+ margins.push_back(get_margin(MARGIN_LEFT));
+ margins.push_back(get_margin(MARGIN_TOP));
+ margins.push_back(get_margin(MARGIN_RIGHT));
+ margins.push_back(get_margin(MARGIN_BOTTOM));
+ s["margins"] = margins;
return s;
}
void Control::_edit_set_state(const Dictionary &p_state) {
Dictionary state = p_state;
- Rect2 rect = state["rect"];
- set_position(rect.position);
- set_size(rect.size);
set_rotation(state["rotation"]);
set_scale(state["scale"]);
+ set_pivot_offset(state["pivot"]);
Array anchors = state["anchors"];
- set_anchor(MARGIN_LEFT, anchors[0]);
- set_anchor(MARGIN_TOP, anchors[1]);
- set_anchor(MARGIN_RIGHT, anchors[2]);
- set_anchor(MARGIN_BOTTOM, anchors[3]);
+ data.anchor[MARGIN_LEFT] = anchors[0];
+ data.anchor[MARGIN_TOP] = anchors[1];
+ data.anchor[MARGIN_RIGHT] = anchors[2];
+ data.anchor[MARGIN_BOTTOM] = anchors[3];
+ Array margins = state["margins"];
+ data.margin[MARGIN_LEFT] = margins[0];
+ data.margin[MARGIN_TOP] = margins[1];
+ data.margin[MARGIN_RIGHT] = margins[2];
+ data.margin[MARGIN_BOTTOM] = margins[3];
+ _size_changed();
}
void Control::_edit_set_position(const Point2 &p_position) {
@@ -85,19 +95,8 @@ Point2 Control::_edit_get_position() const {
};
void Control::_edit_set_rect(const Rect2 &p_edit_rect) {
-
- Transform2D xform = _get_internal_transform();
-
- Vector2 new_pos = xform.basis_xform(p_edit_rect.position);
-
- Vector2 pos = get_position() + new_pos;
-
- Rect2 new_rect = get_rect();
- new_rect.position = pos.snapped(Vector2(1, 1));
- new_rect.size = p_edit_rect.size.snapped(Vector2(1, 1));
-
- set_position(new_rect.position);
- set_size(new_rect.size);
+ set_position((get_position() + get_transform().basis_xform(p_edit_rect.position)).snapped(Vector2(1, 1)));
+ set_size(p_edit_rect.size.snapped(Vector2(1, 1)));
}
Rect2 Control::_edit_get_rect() const {
@@ -121,6 +120,9 @@ bool Control::_edit_use_rotation() const {
}
void Control::_edit_set_pivot(const Point2 &p_pivot) {
+ Vector2 delta_pivot = p_pivot - get_pivot_offset();
+ Vector2 move = Vector2((cos(data.rotation) - 1.0) * delta_pivot.x - sin(data.rotation) * delta_pivot.y, sin(data.rotation) * delta_pivot.x + (cos(data.rotation) - 1.0) * delta_pivot.y);
+ set_position(get_position() + move);
set_pivot_offset(p_pivot);
}
@@ -1279,25 +1281,28 @@ void Control::_size_changed() {
Size2 minimum_size = get_combined_minimum_size();
- if (data.h_grow == GROW_DIRECTION_BEGIN) {
- if (minimum_size.width > new_size_cache.width) {
- new_pos_cache.x = new_pos_cache.x + new_size_cache.width - minimum_size.width;
- new_size_cache.width = minimum_size.width;
+ if (minimum_size.width > new_size_cache.width) {
+ if (data.h_grow == GROW_DIRECTION_BEGIN) {
+ new_pos_cache.x += new_size_cache.width - minimum_size.width;
+ } else if (data.h_grow == GROW_DIRECTION_BOTH) {
+ new_pos_cache.x += 0.5 * (new_size_cache.width - minimum_size.width);
}
- } else {
- new_size_cache.width = MAX(minimum_size.width, new_size_cache.width);
+
+ new_size_cache.width = minimum_size.width;
}
- if (data.v_grow == GROW_DIRECTION_BEGIN) {
- if (minimum_size.height > new_size_cache.height) {
- new_pos_cache.y = new_pos_cache.y + new_size_cache.height - minimum_size.height;
- new_size_cache.height = minimum_size.height;
+ if (minimum_size.height > new_size_cache.height) {
+ if (data.v_grow == GROW_DIRECTION_BEGIN) {
+ new_pos_cache.y += new_size_cache.height - minimum_size.height;
+ } else if (data.v_grow == GROW_DIRECTION_BOTH) {
+ new_pos_cache.y += 0.5 * (new_size_cache.height - minimum_size.height);
}
- } else {
- new_size_cache.height = MAX(minimum_size.height, new_size_cache.height);
+
+ new_size_cache.height = minimum_size.height;
}
- if (get_viewport()->is_snap_controls_to_pixels_enabled()) {
+ // We use a little workaround to avoid flickering when moving the pivot with _edit_set_pivot()
+ if (Math::abs(Math::sin(data.rotation * 4.0f)) < 0.00001f && get_viewport()->is_snap_controls_to_pixels_enabled()) {
new_size_cache = new_size_cache.floor();
new_pos_cache = new_pos_cache.floor();
}
@@ -1378,7 +1383,6 @@ void Control::set_anchor(Margin p_margin, float p_anchor, bool p_keep_margin, bo
data.margin[(p_margin + 2) % 4] = _s2a(previous_opposite_margin_pos, data.anchor[(p_margin + 2) % 4], parent_range);
}
}
-
if (is_inside_tree()) {
_size_changed();
}
@@ -2836,8 +2840,8 @@ void Control::_bind_methods() {
ADD_PROPERTYINZ(PropertyInfo(Variant::INT, "margin_bottom", PROPERTY_HINT_RANGE, "-4096,4096"), "set_margin", "get_margin", MARGIN_BOTTOM);
ADD_GROUP("Grow Direction", "grow_");
- ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End"), "set_h_grow_direction", "get_h_grow_direction");
- ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End"), "set_v_grow_direction", "get_v_grow_direction");
+ ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_horizontal", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_h_grow_direction", "get_h_grow_direction");
+ ADD_PROPERTYNO(PropertyInfo(Variant::INT, "grow_vertical", PROPERTY_HINT_ENUM, "Begin,End,Both"), "set_v_grow_direction", "get_v_grow_direction");
ADD_GROUP("Rect", "rect_");
ADD_PROPERTYNZ(PropertyInfo(Variant::VECTOR2, "rect_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_position", "get_position");
@@ -2847,7 +2851,7 @@ void Control::_bind_methods() {
ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "rect_rotation", PROPERTY_HINT_RANGE, "-1080,1080,0.01"), "set_rotation_degrees", "get_rotation_degrees");
ADD_PROPERTYNO(PropertyInfo(Variant::VECTOR2, "rect_scale"), "set_scale", "get_scale");
ADD_PROPERTYNO(PropertyInfo(Variant::VECTOR2, "rect_pivot_offset"), "set_pivot_offset", "get_pivot_offset");
- ADD_PROPERTYNZ(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "rect_clip_content"), "set_clip_contents", "is_clipping_contents");
ADD_GROUP("Hint", "hint_");
ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "hint_tooltip", PROPERTY_HINT_MULTILINE_TEXT), "set_tooltip", "_get_tooltip");
@@ -2937,6 +2941,7 @@ void Control::_bind_methods() {
BIND_ENUM_CONSTANT(GROW_DIRECTION_BEGIN);
BIND_ENUM_CONSTANT(GROW_DIRECTION_END);
+ BIND_ENUM_CONSTANT(GROW_DIRECTION_BOTH);
BIND_ENUM_CONSTANT(ANCHOR_BEGIN);
BIND_ENUM_CONSTANT(ANCHOR_END);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index 51325f27b5..65f75c8a66 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -60,7 +60,8 @@ public:
enum GrowDirection {
GROW_DIRECTION_BEGIN,
- GROW_DIRECTION_END
+ GROW_DIRECTION_END,
+ GROW_DIRECTION_BOTH
};
enum FocusMode {
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
index e61ede7c3d..feb080dd06 100644
--- a/scene/gui/dialogs.h
+++ b/scene/gui/dialogs.h
@@ -108,7 +108,7 @@ class AcceptDialog : public WindowDialog {
HBoxContainer *hbc;
Label *label;
Button *ok;
- //Button *cancel; no more cancel (there is X on tht titlebar)
+ //Button *cancel; no more cancel (there is X on that titlebar)
bool hide_on_ok;
void _custom_action(const String &p_action);
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
index 58717edbae..4bd92d888d 100644
--- a/scene/gui/file_dialog.cpp
+++ b/scene/gui/file_dialog.cpp
@@ -210,7 +210,7 @@ void FileDialog::_action_pressed() {
bool valid = false;
if (filter->get_selected() == filter->get_item_count() - 1) {
- valid = true; //match none
+ valid = true; // match none
} else if (filters.size() > 1 && filter->get_selected() == 0) {
// match all filters
for (int i = 0; i < filters.size(); i++) {
@@ -287,7 +287,7 @@ bool FileDialog::_is_open_should_be_disabled() {
TreeItem *ti = tree->get_selected();
// We have something that we can't select?
if (!ti)
- return true;
+ return mode != MODE_OPEN_DIR; // In "Open folder" mode, having nothing selected picks the current folder.
Dictionary d = ti->get_metadata(0);
@@ -319,17 +319,15 @@ void FileDialog::deselect_items() {
case MODE_OPEN_FILE:
case MODE_OPEN_FILES:
- get_ok()->set_text(TTR("Open"));
- get_ok()->set_disabled(false);
+ get_ok()->set_text(RTR("Open"));
break;
-
case MODE_OPEN_DIR:
- get_ok()->set_text(TTR("Select Current Folder"));
- get_ok()->set_disabled(false);
+ get_ok()->set_text(RTR("Select Current Folder"));
break;
}
}
}
+
void FileDialog::_tree_selected() {
TreeItem *ti = tree->get_selected();
@@ -341,13 +339,13 @@ void FileDialog::_tree_selected() {
file->set_text(d["name"]);
} else if (mode == MODE_OPEN_DIR) {
- get_ok()->set_text(TTR("Select this Folder"));
+ get_ok()->set_text(RTR("Select this Folder"));
}
get_ok()->set_disabled(_is_open_should_be_disabled());
}
-void FileDialog::_tree_dc_selected() {
+void FileDialog::_tree_item_activated() {
TreeItem *ti = tree->get_selected();
if (!ti)
@@ -756,7 +754,7 @@ void FileDialog::_bind_methods() {
ClassDB::bind_method(D_METHOD("_unhandled_input"), &FileDialog::_unhandled_input);
ClassDB::bind_method(D_METHOD("_tree_selected"), &FileDialog::_tree_selected);
- ClassDB::bind_method(D_METHOD("_tree_db_selected"), &FileDialog::_tree_dc_selected);
+ ClassDB::bind_method(D_METHOD("_tree_item_activated"), &FileDialog::_tree_item_activated);
ClassDB::bind_method(D_METHOD("_dir_entered"), &FileDialog::_dir_entered);
ClassDB::bind_method(D_METHOD("_file_entered"), &FileDialog::_file_entered);
ClassDB::bind_method(D_METHOD("_action_pressed"), &FileDialog::_action_pressed);
@@ -845,7 +843,7 @@ FileDialog::FileDialog() {
HBoxContainer *hbc = memnew(HBoxContainer);
dir_up = memnew(ToolButton);
- dir_up->set_tooltip(TTR("Go to parent folder"));
+ dir_up->set_tooltip(RTR("Go to parent folder"));
hbc->add_child(dir_up);
dir_up->connect("pressed", this, "_go_up");
@@ -881,7 +879,7 @@ FileDialog::FileDialog() {
filter = memnew(OptionButton);
filter->set_stretch_ratio(3);
filter->set_h_size_flags(SIZE_EXPAND_FILL);
- filter->set_clip_text(true); //too many extensions overflow it
+ filter->set_clip_text(true); // too many extensions overflows it
hbc->add_child(filter);
vbc->add_child(hbc);
@@ -890,9 +888,8 @@ FileDialog::FileDialog() {
_update_drives();
connect("confirmed", this, "_action_pressed");
- //cancel->connect("pressed", this,"_cancel_pressed");
tree->connect("cell_selected", this, "_tree_selected", varray(), CONNECT_DEFERRED);
- tree->connect("item_activated", this, "_tree_db_selected", varray());
+ tree->connect("item_activated", this, "_tree_item_activated", varray());
tree->connect("nothing_selected", this, "deselect_items");
dir->connect("text_entered", this, "_dir_entered");
file->connect("text_entered", this, "_file_entered");
@@ -922,7 +919,6 @@ FileDialog::FileDialog() {
exterr->set_text(RTR("Must use a valid extension."));
add_child(exterr);
- //update_file_list();
update_filters();
update_dir();
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
index 2a09494682..ad483d5dab 100644
--- a/scene/gui/file_dialog.h
+++ b/scene/gui/file_dialog.h
@@ -107,7 +107,7 @@ private:
void _tree_selected();
void _select_drive(int p_idx);
- void _tree_dc_selected();
+ void _tree_item_activated();
void _dir_entered(String p_dir);
void _file_entered(const String &p_file);
void _action_pressed();
diff --git a/scene/gui/gradient_edit.cpp b/scene/gui/gradient_edit.cpp
index 3985039716..9fc8e98a7f 100644
--- a/scene/gui/gradient_edit.cpp
+++ b/scene/gui/gradient_edit.cpp
@@ -367,6 +367,13 @@ void GradientEdit::_notification(int p_what) {
draw_line(Vector2(-1, -1), Vector2(-1, h + 1), Color(1, 1, 1, 0.6));
}
}
+
+ if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+
+ if (!is_visible()) {
+ grabbing = false;
+ }
+ }
}
void GradientEdit::_draw_checker(int x, int y, int w, int h) {
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
index c2b8a7dfab..b401abd436 100644
--- a/scene/gui/grid_container.cpp
+++ b/scene/gui/grid_container.cpp
@@ -36,10 +36,10 @@ void GridContainer::_notification(int p_what) {
case NOTIFICATION_SORT_CHILDREN: {
- Map<int, int> col_minw;
- Map<int, int> row_minh;
- Set<int> col_expanded;
- Set<int> row_expanded;
+ Map<int, int> col_minw; // max of min_width of all controls in each col (indexed by col)
+ Map<int, int> row_minh; // max of min_height of all controls in each row (indexed by row)
+ Set<int> col_expanded; // columns which have the SIZE_EXPAND flag set
+ Set<int> row_expanded; // rows which have the SIZE_EXPAND flag set
int hsep = get_constant("hseparation");
int vsep = get_constant("vseparation");
@@ -84,17 +84,17 @@ void GridContainer::_notification(int p_what) {
if (!row_expanded.has(E->key()))
remaining_space.height -= E->get();
}
- remaining_space.height -= vsep * (max_row - 1);
- remaining_space.width -= hsep * (max_col - 1);
+ remaining_space.height -= vsep * MAX(max_row - 1, 0);
+ remaining_space.width -= hsep * MAX(max_col - 1, 0);
bool can_fit = false;
- while (!can_fit) {
+ while (!can_fit && col_expanded.size() > 0) {
// Check if all minwidth constraints are ok if we use the remaining space
can_fit = true;
- int max_index = 0;
+ int max_index = col_expanded.front()->get();
for (Set<int>::Element *E = col_expanded.front(); E; E = E->next()) {
if (col_minw[E->get()] > col_minw[max_index]) {
- max_index = col_minw[E->get()];
+ max_index = E->get();
}
if (can_fit && (remaining_space.width / col_expanded.size()) < col_minw[E->get()]) {
can_fit = false;
@@ -109,13 +109,13 @@ void GridContainer::_notification(int p_what) {
}
can_fit = false;
- while (!can_fit) {
+ while (!can_fit && row_expanded.size() > 0) {
// Check if all minwidth constraints are ok if we use the remaining space
can_fit = true;
- int max_index = 0;
+ int max_index = row_expanded.front()->get();
for (Set<int>::Element *E = row_expanded.front(); E; E = E->next()) {
if (row_minh[E->get()] > row_minh[max_index]) {
- max_index = row_minh[E->get()];
+ max_index = E->get();
}
if (can_fit && (remaining_space.height / row_expanded.size()) < row_minh[E->get()]) {
can_fit = false;
@@ -130,8 +130,8 @@ void GridContainer::_notification(int p_what) {
}
// Finally, fit the nodes
- int col_expand = remaining_space.width / col_expanded.size();
- int row_expand = remaining_space.height / row_expanded.size();
+ int col_expand = col_expanded.size() > 0 ? remaining_space.width / col_expanded.size() : 0;
+ int row_expand = row_expanded.size() > 0 ? remaining_space.height / row_expanded.size() : 0;
int col_ofs = 0;
int row_ofs = 0;
diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp
index fe85d04003..fa7c9e091e 100644
--- a/scene/gui/item_list.cpp
+++ b/scene/gui/item_list.cpp
@@ -37,6 +37,7 @@ void ItemList::add_item(const String &p_item, const Ref<Texture> &p_texture, boo
Item item;
item.icon = p_texture;
item.icon_region = Rect2i();
+ item.icon_modulate = Color(1, 1, 1, 1);
item.text = p_item;
item.selectable = p_selectable;
item.selected = false;
@@ -54,6 +55,7 @@ void ItemList::add_icon_item(const Ref<Texture> &p_item, bool p_selectable) {
Item item;
item.icon = p_item;
item.icon_region = Rect2i();
+ item.icon_modulate = Color(1, 1, 1, 1);
//item.text=p_item;
item.selectable = p_selectable;
item.selected = false;
@@ -138,6 +140,21 @@ Rect2 ItemList::get_item_icon_region(int p_idx) const {
return items[p_idx].icon_region;
}
+void ItemList::set_item_icon_modulate(int p_idx, const Color &p_modulate) {
+
+ ERR_FAIL_INDEX(p_idx, items.size());
+
+ items[p_idx].icon_modulate = p_modulate;
+ update();
+}
+
+Color ItemList::get_item_icon_modulate(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx, items.size(), Color());
+
+ return items[p_idx].icon_modulate;
+}
+
void ItemList::set_item_custom_bg_color(int p_idx, const Color &p_custom_bg_color) {
ERR_FAIL_INDEX(p_idx, items.size());
@@ -295,35 +312,21 @@ int ItemList::get_current() const {
return current;
}
-void ItemList::move_item(int p_item, int p_to_pos) {
-
- ERR_FAIL_INDEX(p_item, items.size());
- ERR_FAIL_INDEX(p_to_pos, items.size() + 1);
-
- Item it = items[p_item];
- items.remove(p_item);
+void ItemList::move_item(int p_from_idx, int p_to_idx) {
- if (p_to_pos > p_item) {
- p_to_pos--;
- }
+ ERR_FAIL_INDEX(p_from_idx, items.size());
+ ERR_FAIL_INDEX(p_to_idx, items.size());
- if (p_to_pos >= items.size()) {
- items.push_back(it);
- } else {
- items.insert(p_to_pos, it);
+ if (is_anything_selected() && get_selected_items()[0] == p_from_idx) {
+ current = p_to_idx;
}
- if (current < 0) {
- //do none
- } else if (p_item == current) {
- current = p_to_pos;
- } else if (p_to_pos > p_item && current > p_item && current < p_to_pos) {
- current--;
- } else if (p_to_pos < p_item && current < p_item && current > p_to_pos) {
- current++;
- }
+ Item item = items[p_from_idx];
+ items.remove(p_from_idx);
+ items.insert(p_to_idx, item);
update();
+ shape_changed = true;
}
int ItemList::get_item_count() const {
@@ -961,12 +964,36 @@ void ItemList::_notification(int p_what) {
Vector2 base_ofs = bg->get_offset();
base_ofs.y -= int(scroll_bar->get_value());
- Rect2 clip(Point2(), size - bg->get_minimum_size() + Vector2(0, scroll_bar->get_value()));
+ const Rect2 clip(-base_ofs, size); // visible frame, don't need to draw outside of there
+
+ int first_item_visible;
+ {
+ // do a binary search to find the first item whose rect reaches below clip.position.y
+ int lo = 0;
+ int hi = items.size();
+ while (lo < hi) {
+ const int mid = (lo + hi) / 2;
+ const Rect2 &rcache = items[mid].rect_cache;
+ if (rcache.position.y + rcache.size.y < clip.position.y) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ // we might have ended up with column 2, or 3, ..., so let's find the first column
+ while (lo > 0 && items[lo - 1].rect_cache.position.y == items[lo].rect_cache.position.y) {
+ lo -= 1;
+ }
+ first_item_visible = lo;
+ }
- for (int i = 0; i < items.size(); i++) {
+ for (int i = first_item_visible; i < items.size(); i++) {
Rect2 rcache = items[i].rect_cache;
+ if (rcache.position.y > clip.position.y + clip.size.y)
+ break; // done
+
if (!clip.intersects(rcache))
continue;
@@ -1035,7 +1062,7 @@ void ItemList::_notification(int p_what) {
draw_rect.size = adj.size;
}
- Color modulate = Color(1, 1, 1, 1);
+ Color modulate = items[i].icon_modulate;
if (items[i].disabled)
modulate.a *= 0.5;
@@ -1138,8 +1165,28 @@ void ItemList::_notification(int p_what) {
}
}
- for (int i = 0; i < separators.size(); i++) {
- draw_line(Vector2(bg->get_margin(MARGIN_LEFT), base_ofs.y + separators[i]), Vector2(width, base_ofs.y + separators[i]), guide_color);
+ int first_visible_separator = 0;
+ {
+ // do a binary search to find the first separator that is below clip_position.y
+ int lo = 0;
+ int hi = separators.size();
+ while (lo < hi) {
+ const int mid = (lo + hi) / 2;
+ if (separators[mid] < clip.position.y) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ first_visible_separator = lo;
+ }
+
+ for (int i = first_visible_separator; i < separators.size(); i++) {
+ if (separators[i] > clip.position.y + clip.size.y)
+ break; // done
+
+ const int y = base_ofs.y + separators[i];
+ draw_line(Vector2(bg->get_margin(MARGIN_LEFT), y), Vector2(width, y), guide_color);
}
}
}
@@ -1359,6 +1406,9 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_icon_region", "idx", "rect"), &ItemList::set_item_icon_region);
ClassDB::bind_method(D_METHOD("get_item_icon_region", "idx"), &ItemList::get_item_icon_region);
+ ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "idx", "modulate"), &ItemList::set_item_icon_modulate);
+ ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "idx"), &ItemList::get_item_icon_modulate);
+
ClassDB::bind_method(D_METHOD("set_item_selectable", "idx", "selectable"), &ItemList::set_item_selectable);
ClassDB::bind_method(D_METHOD("is_item_selectable", "idx"), &ItemList::is_item_selectable);
@@ -1379,9 +1429,13 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("select", "idx", "single"), &ItemList::select, DEFVAL(true));
ClassDB::bind_method(D_METHOD("unselect", "idx"), &ItemList::unselect);
+ ClassDB::bind_method(D_METHOD("unselect_all"), &ItemList::unselect_all);
+
ClassDB::bind_method(D_METHOD("is_selected", "idx"), &ItemList::is_selected);
ClassDB::bind_method(D_METHOD("get_selected_items"), &ItemList::get_selected_items);
+ ClassDB::bind_method(D_METHOD("move_item", "p_from_idx", "p_to_idx"), &ItemList::move_item);
+
ClassDB::bind_method(D_METHOD("get_item_count"), &ItemList::get_item_count);
ClassDB::bind_method(D_METHOD("remove_item", "idx"), &ItemList::remove_item);
@@ -1421,6 +1475,8 @@ void ItemList::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_auto_height", "enable"), &ItemList::set_auto_height);
ClassDB::bind_method(D_METHOD("has_auto_height"), &ItemList::has_auto_height);
+ ClassDB::bind_method(D_METHOD("is_anything_selected"), &ItemList::is_anything_selected);
+
ClassDB::bind_method(D_METHOD("get_item_at_position", "position", "exact"), &ItemList::get_item_at_position, DEFVAL(false));
ClassDB::bind_method(D_METHOD("ensure_current_is_visible"), &ItemList::ensure_current_is_visible);
diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h
index 7f34a250bd..58771c1777 100644
--- a/scene/gui/item_list.h
+++ b/scene/gui/item_list.h
@@ -54,6 +54,7 @@ private:
Ref<Texture> icon;
Rect2i icon_region;
+ Color icon_modulate;
Ref<Texture> tag_icon;
String text;
bool selectable;
@@ -135,6 +136,9 @@ public:
void set_item_icon_region(int p_idx, const Rect2 &p_region);
Rect2 get_item_icon_region(int p_idx) const;
+ void set_item_icon_modulate(int p_idx, const Color &p_modulate);
+ Color get_item_icon_modulate(int p_idx) const;
+
void set_item_selectable(int p_idx, bool p_selectable);
bool is_item_selectable(int p_idx) const;
@@ -169,7 +173,7 @@ public:
void set_current(int p_current);
int get_current() const;
- void move_item(int p_item, int p_to_pos);
+ void move_item(int p_from_idx, int p_to_idx);
int get_item_count() const;
void remove_item(int p_idx);
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 03dc6686b8..5c0e8fefc7 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -373,12 +373,14 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
case KEY_UP: {
shift_selection_check_pre(k->get_shift());
+ if (get_cursor_position() == 0) handled = false;
set_cursor_position(0);
shift_selection_check_post(k->get_shift());
} break;
case KEY_DOWN: {
shift_selection_check_pre(k->get_shift());
+ if (get_cursor_position() == text.length()) handled = false;
set_cursor_position(text.length());
shift_selection_check_post(k->get_shift());
} break;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index 2e74faa61d..87cf4dc334 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -59,7 +59,6 @@ void MenuButton::pressed() {
popup->set_size(Size2(size.width, 0));
popup->set_parent_rect(Rect2(Point2(gp - popup->get_global_position()), get_size()));
popup->popup();
- popup->set_invalidate_click_until_motion();
}
void MenuButton::_gui_input(Ref<InputEvent> p_event) {
@@ -109,7 +108,6 @@ MenuButton::MenuButton() {
add_child(popup);
popup->set_as_toplevel(true);
popup->set_pass_on_modal_close_click(false);
- connect("button_up", popup, "call_deferred", make_binds("grab_click_focus"));
set_process_unhandled_key_input(true);
set_action_mode(ACTION_MODE_BUTTON_PRESS);
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index 1a46921561..aaad10f579 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -75,6 +75,10 @@ void OptionButton::_notification(int p_what) {
}
}
+void OptionButton::_focused(int p_which) {
+ emit_signal("item_focused", p_which);
+}
+
void OptionButton::_selected(int p_which) {
int selid = -1;
@@ -114,7 +118,7 @@ void OptionButton::add_icon_item(const Ref<Texture> &p_icon, const String &p_lab
}
void OptionButton::add_item(const String &p_label, int p_ID) {
- popup->add_check_item(p_label, p_ID);
+ popup->add_radio_check_item(p_label, p_ID);
if (popup->get_item_count() == 1)
select(0);
}
@@ -290,6 +294,7 @@ void OptionButton::get_translatable_strings(List<String> *p_strings) const {
void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_selected"), &OptionButton::_selected);
+ ClassDB::bind_method(D_METHOD("_focused"), &OptionButton::_focused);
ClassDB::bind_method(D_METHOD("add_item", "label", "id"), &OptionButton::add_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_icon_item", "texture", "label", "id"), &OptionButton::add_icon_item);
@@ -318,9 +323,11 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("_set_items"), &OptionButton::_set_items);
ClassDB::bind_method(D_METHOD("_get_items"), &OptionButton::_get_items);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "items", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_items", "_get_items");
+ // "selected" property must come after "items", otherwise GH-10213 occurs
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "selected"), "_select_int", "get_selected");
ADD_SIGNAL(MethodInfo("item_selected", PropertyInfo(Variant::INT, "ID")));
+ ADD_SIGNAL(MethodInfo("item_focused", PropertyInfo(Variant::INT, "ID")));
}
OptionButton::OptionButton() {
@@ -335,6 +342,7 @@ OptionButton::OptionButton() {
popup->set_as_toplevel(true);
popup->set_pass_on_modal_close_click(false);
popup->connect("id_pressed", this, "_selected");
+ popup->connect("id_focused", this, "_focused");
}
OptionButton::~OptionButton() {
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
index f65fa1b631..d5f866d806 100644
--- a/scene/gui/option_button.h
+++ b/scene/gui/option_button.h
@@ -43,6 +43,7 @@ class OptionButton : public Button {
PopupMenu *popup;
int current;
+ void _focused(int p_which);
void _selected(int p_which);
void _select(int p_which, bool p_emit = false);
void _select_int(int p_which);
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index 89000fcde1..9ff3bd6e81 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -55,7 +55,7 @@ Size2 PopupMenu::get_minimum_size() const {
float max_w = 0;
int font_h = font->get_height();
- int check_w = get_icon("checked")->get_width();
+ int check_w = MAX(get_icon("checked")->get_width(), get_icon("radio_checked")->get_width());
int accel_max_w = 0;
for (int i = 0; i < items.size(); i++) {
@@ -74,7 +74,7 @@ Size2 PopupMenu::get_minimum_size() const {
size.width += items[i].h_ofs;
- if (items[i].checkable) {
+ if (items[i].checkable_type) {
size.width += check_w + hseparation;
}
@@ -211,86 +211,69 @@ void PopupMenu::_scroll(float p_factor, const Point2 &p_over) {
void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
- Ref<InputEventKey> k = p_event;
-
- if (k.is_valid()) {
-
- if (!k->is_pressed())
- return;
+ if (p_event->is_action("ui_down") && p_event->is_pressed()) {
- switch (k->get_scancode()) {
+ int search_from = mouse_over + 1;
+ if (search_from >= items.size())
+ search_from = 0;
- case KEY_DOWN: {
+ for (int i = search_from; i < items.size(); i++) {
- int search_from = mouse_over + 1;
- if (search_from >= items.size())
- search_from = 0;
-
- for (int i = search_from; i < items.size(); i++) {
-
- if (i < 0 || i >= items.size())
- continue;
-
- if (!items[i].separator && !items[i].disabled) {
-
- mouse_over = i;
- update();
- break;
- }
- }
- } break;
- case KEY_UP: {
-
- int search_from = mouse_over - 1;
- if (search_from < 0)
- search_from = items.size() - 1;
-
- for (int i = search_from; i >= 0; i--) {
-
- if (i < 0 || i >= items.size())
- continue;
-
- if (!items[i].separator && !items[i].disabled) {
+ if (i < 0 || i >= items.size())
+ continue;
- mouse_over = i;
- update();
- break;
- }
- }
- } break;
+ if (!items[i].separator && !items[i].disabled) {
- case KEY_LEFT: {
+ mouse_over = i;
+ emit_signal("id_focused", i);
+ update();
+ accept_event();
+ break;
+ }
+ }
+ } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
- Node *n = get_parent();
- if (!n)
- break;
+ int search_from = mouse_over - 1;
+ if (search_from < 0)
+ search_from = items.size() - 1;
- PopupMenu *pm = Object::cast_to<PopupMenu>(n);
- if (!pm)
- break;
+ for (int i = search_from; i >= 0; i--) {
- hide();
- } break;
+ if (i < 0 || i >= items.size())
+ continue;
- case KEY_RIGHT: {
+ if (!items[i].separator && !items[i].disabled) {
- if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && items[mouse_over].submenu != "" && submenu_over != mouse_over)
- _activate_submenu(mouse_over);
- } break;
+ mouse_over = i;
+ emit_signal("id_focused", i);
+ update();
+ accept_event();
+ break;
+ }
+ }
+ } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
- case KEY_ENTER:
- case KEY_KP_ENTER: {
+ Node *n = get_parent();
+ if (n && Object::cast_to<PopupMenu>(n)) {
+ hide();
+ accept_event();
+ }
+ } else if (p_event->is_action("ui_right") && p_event->is_pressed()) {
- if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
+ if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && items[mouse_over].submenu != "" && submenu_over != mouse_over) {
+ _activate_submenu(mouse_over);
+ accept_event();
+ }
+ } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
- if (items[mouse_over].submenu != "" && submenu_over != mouse_over) {
- _activate_submenu(mouse_over);
- break;
- }
+ if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) {
- activate_item(mouse_over);
- }
- } break;
+ if (items[mouse_over].submenu != "" && submenu_over != mouse_over) {
+ _activate_submenu(mouse_over);
+ } else {
+ activate_item(mouse_over);
+ }
+ accept_event();
}
}
@@ -301,7 +284,8 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
if (b->is_pressed())
return;
- switch (b->get_button_index()) {
+ int button_idx = b->get_button_index();
+ switch (button_idx) {
case BUTTON_WHEEL_DOWN: {
@@ -315,30 +299,37 @@ void PopupMenu::_gui_input(const Ref<InputEvent> &p_event) {
_scroll(b->get_factor(), b->get_position());
}
} break;
- case BUTTON_LEFT: {
+ default: {
+ // Allow activating item by releasing the LMB or any that was down when the popup appeared
+ if (button_idx == BUTTON_LEFT || (initial_button_mask & (1 << (button_idx - 1)))) {
- int over = _get_mouse_over(b->get_position());
+ bool was_during_grabbed_click = during_grabbed_click;
+ during_grabbed_click = false;
- if (invalidated_click) {
- invalidated_click = false;
- break;
- }
- if (over < 0) {
- hide();
- break; //non-activable
- }
+ int over = _get_mouse_over(b->get_position());
- if (items[over].separator || items[over].disabled)
- break;
+ if (invalidated_click) {
+ invalidated_click = false;
+ break;
+ }
+ if (over < 0) {
+ if (!was_during_grabbed_click) {
+ hide();
+ }
+ break; //non-activable
+ }
- if (items[over].submenu != "") {
+ if (items[over].separator || items[over].disabled)
+ break;
- _activate_submenu(over);
- return;
- }
- activate_item(over);
+ if (items[over].submenu != "") {
- } break;
+ _activate_submenu(over);
+ return;
+ }
+ activate_item(over);
+ }
+ }
}
//update();
@@ -425,8 +416,9 @@ void PopupMenu::_notification(int p_what) {
Ref<StyleBox> style = get_stylebox("panel");
Ref<StyleBox> hover = get_stylebox("hover");
Ref<Font> font = get_font("font");
- Ref<Texture> check = get_icon("checked");
- Ref<Texture> uncheck = get_icon("unchecked");
+ // In Item::checkable_type enum order (less the non-checkable member)
+ Ref<Texture> check[] = { get_icon("checked"), get_icon("radio_checked") };
+ Ref<Texture> uncheck[] = { get_icon("unchecked"), get_icon("radio_unchecked") };
Ref<Texture> submenu = get_icon("submenu");
Ref<StyleBox> separator = get_stylebox("separator");
@@ -469,14 +461,10 @@ void PopupMenu::_notification(int p_what) {
separator->draw(ci, Rect2(item_ofs + Point2(0, Math::floor((h - sep_h) / 2.0)), Size2(get_size().width - style->get_minimum_size().width, sep_h)));
}
- if (items[i].checkable) {
-
- if (items[i].checked)
- check->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
- else
- uncheck->draw(ci, item_ofs + Point2(0, Math::floor((h - check->get_height()) / 2.0)));
-
- item_ofs.x += check->get_width() + hseparation;
+ if (items[i].checkable_type) {
+ Texture *icon = (items[i].checked ? check[items[i].checkable_type - 1] : uncheck[items[i].checkable_type - 1]).ptr();
+ icon->draw(ci, item_ofs + Point2(0, Math::floor((h - icon->get_height()) / 2.0)));
+ item_ofs.x += icon->get_width() + hseparation;
}
if (!items[i].icon.is_null()) {
@@ -520,6 +508,11 @@ void PopupMenu::_notification(int p_what) {
update();
}
} break;
+ case NOTIFICATION_POST_POPUP: {
+
+ initial_button_mask = Input::get_singleton()->get_mouse_button_mask();
+ during_grabbed_click = (bool)initial_button_mask;
+ } break;
case NOTIFICATION_POPUP_HIDE: {
if (mouse_over >= 0) {
@@ -571,10 +564,11 @@ void PopupMenu::add_icon_check_item(const Ref<Texture> &p_icon, const String &p_
item.xl_text = tr(p_label);
item.accel = p_accel;
item.ID = p_ID;
- item.checkable = true;
+ item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
}
+
void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
Item item;
@@ -582,11 +576,18 @@ void PopupMenu::add_check_item(const String &p_label, int p_ID, uint32_t p_accel
item.xl_text = tr(p_label);
item.accel = p_accel;
item.ID = p_ID;
- item.checkable = true;
+ item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
}
+void PopupMenu::add_radio_check_item(const String &p_label, int p_ID, uint32_t p_accel) {
+
+ add_check_item(p_label, p_ID, p_accel);
+ items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
+ update();
+}
+
void PopupMenu::add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
ERR_FAIL_COND(p_shortcut.is_null());
@@ -615,6 +616,7 @@ void PopupMenu::add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_g
items.push_back(item);
update();
}
+
void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
ERR_FAIL_COND(p_shortcut.is_null());
@@ -624,7 +626,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<Sh
Item item;
item.ID = p_ID;
item.shortcut = p_shortcut;
- item.checkable = true;
+ item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
item.icon = p_icon;
item.shortcut_is_global = p_global;
items.push_back(item);
@@ -641,11 +643,18 @@ void PopupMenu::add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bo
item.ID = p_ID;
item.shortcut = p_shortcut;
item.shortcut_is_global = p_global;
- item.checkable = true;
+ item.checkable_type = Item::CHECKABLE_TYPE_CHECK_BOX;
items.push_back(item);
update();
}
+void PopupMenu::add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID, bool p_global) {
+
+ add_check_shortcut(p_shortcut, p_ID, p_global);
+ items[items.size() - 1].checkable_type = Item::CHECKABLE_TYPE_RADIO_BUTTON;
+ update();
+}
+
void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID, uint32_t p_accel) {
Item item;
@@ -653,7 +662,6 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int
item.xl_text = tr(p_label);
item.accel = p_accel;
item.ID = p_ID;
- item.checkable = false;
item.max_states = p_max_states;
item.state = p_default_state;
items.push_back(item);
@@ -828,7 +836,14 @@ bool PopupMenu::is_item_separator(int p_idx) const {
void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) {
ERR_FAIL_INDEX(p_idx, items.size());
- items[p_idx].checkable = p_checkable;
+ items[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE;
+ update();
+}
+
+void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) {
+
+ ERR_FAIL_INDEX(p_idx, items.size());
+ items[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE;
update();
}
@@ -884,7 +899,12 @@ void PopupMenu::toggle_item_multistate(int p_idx) {
bool PopupMenu::is_item_checkable(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), false);
- return items[p_idx].checkable;
+ return items[p_idx].checkable_type;
+}
+
+bool PopupMenu::is_item_radio_checkable(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx, items.size(), false);
+ return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON;
}
int PopupMenu::get_item_count() const {
@@ -958,7 +978,7 @@ void PopupMenu::activate_item(int p_item) {
// We close all parents that are chained together,
// with hide_on_item_selection enabled
- if (items[p_item].checkable) {
+ if (items[p_item].checkable_type) {
if (!hide_on_checkable_item_selection || !pop->is_hide_on_checkable_item_selection())
break;
} else if (0 < items[p_item].max_states) {
@@ -975,7 +995,7 @@ void PopupMenu::activate_item(int p_item) {
// Hides popup by default; unless otherwise specified
// by using set_hide_on_item_selection and set_hide_on_checkable_item_selection
- if (items[p_item].checkable) {
+ if (items[p_item].checkable_type) {
if (!hide_on_checkable_item_selection)
return;
} else if (0 < items[p_item].max_states) {
@@ -1027,7 +1047,9 @@ Array PopupMenu::_get_items() const {
items.push_back(get_item_text(i));
items.push_back(get_item_icon(i));
- items.push_back(is_item_checkable(i));
+ // For compatibility, use false/true for no/checkbox and integers for other values
+ int ct = this->items[i].checkable_type;
+ items.push_back(Variant(ct <= Item::CHECKABLE_TYPE_CHECK_BOX ? is_item_checkable(i) : ct));
items.push_back(is_item_checked(i));
items.push_back(is_item_disabled(i));
@@ -1070,7 +1092,9 @@ void PopupMenu::_set_items(const Array &p_items) {
String text = p_items[i + 0];
Ref<Texture> icon = p_items[i + 1];
+ // For compatibility, use false/true for no/checkbox and integers for other values
bool checkable = p_items[i + 2];
+ bool radio_checkable = (int)p_items[i + 2] == Item::CHECKABLE_TYPE_RADIO_BUTTON;
bool checked = p_items[i + 3];
bool disabled = p_items[i + 4];
@@ -1083,7 +1107,13 @@ void PopupMenu::_set_items(const Array &p_items) {
int idx = get_item_count();
add_item(text, id);
set_item_icon(idx, icon);
- set_item_as_checkable(idx, checkable);
+ if (checkable) {
+ if (radio_checkable) {
+ set_item_as_radio_checkable(idx, true);
+ } else {
+ set_item_as_checkable(idx, true);
+ }
+ }
set_item_checked(idx, checked);
set_item_disabled(idx, disabled);
set_item_id(idx, id);
@@ -1164,12 +1194,14 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_item", "label", "id", "accel"), &PopupMenu::add_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_icon_check_item", "texture", "label", "id", "accel"), &PopupMenu::add_icon_check_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_check_item", "label", "id", "accel"), &PopupMenu::add_check_item, DEFVAL(-1), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("add_radio_check_item", "label", "id", "accel"), &PopupMenu::add_radio_check_item, DEFVAL(-1), DEFVAL(0));
ClassDB::bind_method(D_METHOD("add_submenu_item", "label", "submenu", "id"), &PopupMenu::add_submenu_item, DEFVAL(-1));
ClassDB::bind_method(D_METHOD("add_icon_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_shortcut", "shortcut", "id", "global"), &PopupMenu::add_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_icon_check_shortcut", "texture", "shortcut", "id", "global"), &PopupMenu::add_icon_check_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("add_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_check_shortcut, DEFVAL(-1), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("add_radio_check_shortcut", "shortcut", "id", "global"), &PopupMenu::add_radio_check_shortcut, DEFVAL(-1), DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_text", "idx", "text"), &PopupMenu::set_item_text);
ClassDB::bind_method(D_METHOD("set_item_icon", "idx", "icon"), &PopupMenu::set_item_icon);
@@ -1181,6 +1213,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_submenu", "idx", "submenu"), &PopupMenu::set_item_submenu);
ClassDB::bind_method(D_METHOD("set_item_as_separator", "idx", "enable"), &PopupMenu::set_item_as_separator);
ClassDB::bind_method(D_METHOD("set_item_as_checkable", "idx", "enable"), &PopupMenu::set_item_as_checkable);
+ ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "idx", "enable"), &PopupMenu::set_item_as_radio_checkable);
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &PopupMenu::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_shortcut", "idx", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false));
ClassDB::bind_method(D_METHOD("set_item_multistate", "idx", "state"), &PopupMenu::set_item_multistate);
@@ -1199,6 +1232,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_submenu", "idx"), &PopupMenu::get_item_submenu);
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &PopupMenu::is_item_separator);
ClassDB::bind_method(D_METHOD("is_item_checkable", "idx"), &PopupMenu::is_item_checkable);
+ ClassDB::bind_method(D_METHOD("is_item_radio_checkable", "idx"), &PopupMenu::is_item_radio_checkable);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &PopupMenu::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_shortcut", "idx"), &PopupMenu::get_item_shortcut);
@@ -1229,18 +1263,24 @@ void PopupMenu::_bind_methods() {
ADD_PROPERTYNO(PropertyInfo(Variant::BOOL, "hide_on_state_item_selection"), "set_hide_on_state_item_selection", "is_hide_on_state_item_selection");
ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "ID")));
+ ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "ID")));
ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index")));
}
-void PopupMenu::set_invalidate_click_until_motion() {
+void PopupMenu::popup(const Rect2 &p_bounds) {
+
+ grab_click_focus();
moved = Vector2();
invalidated_click = true;
+ Popup::popup(p_bounds);
}
PopupMenu::PopupMenu() {
mouse_over = -1;
submenu_over = -1;
+ initial_button_mask = 0;
+ during_grabbed_click = false;
set_focus_mode(FOCUS_ALL);
set_as_toplevel(true);
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
index 60f36e95ec..c7851969d0 100644
--- a/scene/gui/popup_menu.h
+++ b/scene/gui/popup_menu.h
@@ -46,7 +46,11 @@ class PopupMenu : public Popup {
String text;
String xl_text;
bool checked;
- bool checkable;
+ enum {
+ CHECKABLE_TYPE_NONE,
+ CHECKABLE_TYPE_CHECK_BOX,
+ CHECKABLE_TYPE_RADIO_BUTTON,
+ } checkable_type;
int max_states;
int state;
bool separator;
@@ -63,7 +67,7 @@ class PopupMenu : public Popup {
Item() {
checked = false;
- checkable = false;
+ checkable_type = CHECKABLE_TYPE_NONE;
separator = false;
max_states = 0;
state = 0;
@@ -78,6 +82,8 @@ class PopupMenu : public Popup {
Timer *submenu_timer;
List<Rect2> autohide_areas;
Vector<Item> items;
+ int initial_button_mask;
+ bool during_grabbed_click;
int mouse_over;
int submenu_over;
Rect2 parent_rect;
@@ -115,12 +121,14 @@ public:
void add_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0);
void add_icon_check_item(const Ref<Texture> &p_icon, const String &p_label, int p_ID = -1, uint32_t p_accel = 0);
void add_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0);
+ void add_radio_check_item(const String &p_label, int p_ID = -1, uint32_t p_accel = 0);
void add_submenu_item(const String &p_label, const String &p_submenu, int p_ID = -1);
void add_icon_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false);
void add_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false);
void add_icon_check_shortcut(const Ref<Texture> &p_icon, const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false);
void add_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false);
+ void add_radio_check_shortcut(const Ref<ShortCut> &p_shortcut, int p_ID = -1, bool p_global = false);
void add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_ID = -1, uint32_t p_accel = 0);
@@ -134,6 +142,7 @@ public:
void set_item_submenu(int p_idx, const String &p_submenu);
void set_item_as_separator(int p_idx, bool p_separator);
void set_item_as_checkable(int p_idx, bool p_checkable);
+ void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable);
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_shortcut(int p_idx, const Ref<ShortCut> &p_shortcut, bool p_global = false);
void set_item_h_offset(int p_idx, int p_offset);
@@ -154,6 +163,7 @@ public:
String get_item_submenu(int p_idx) const;
bool is_item_separator(int p_idx) const;
bool is_item_checkable(int p_idx) const;
+ bool is_item_radio_checkable(int p_idx) const;
String get_item_tooltip(int p_idx) const;
Ref<ShortCut> get_item_shortcut(int p_idx) const;
int get_item_state(int p_idx) const;
@@ -178,7 +188,6 @@ public:
void add_autohide_area(const Rect2 &p_area);
void clear_autohide_areas();
- void set_invalidate_click_until_motion();
void set_hide_on_item_selection(bool p_enabled);
bool is_hide_on_item_selection() const;
@@ -188,6 +197,8 @@ public:
void set_hide_on_multistate_item_selection(bool p_enabled);
bool is_hide_on_multistate_item_selection() const;
+ virtual void popup(const Rect2 &p_bounds = Rect2());
+
PopupMenu();
~PopupMenu();
};
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 381c6c75a5..6bfc4d4dee 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -125,6 +125,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
l.descent_caches.clear();
l.char_count = 0;
l.minimum_width = 0;
+ l.maximum_width = 0;
}
int wofs = margin;
@@ -200,7 +201,8 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
#define ENSURE_WIDTH(m_width) \
if (p_mode == PROCESS_CACHE) { \
- l.minimum_width = MAX(l.minimum_width, wofs + m_width); \
+ l.maximum_width = MAX(l.maximum_width, MIN(p_width, wofs + m_width)); \
+ l.minimum_width = MAX(l.minimum_width, m_width); \
} \
if (wofs + m_width > p_width) { \
if (p_mode == PROCESS_CACHE) { \
@@ -247,6 +249,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
int rchar = 0;
int lh = 0;
bool line_is_blank = true;
+ int fh = 0;
while (it) {
@@ -262,14 +265,9 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
const CharType *c = text->text.c_str();
const CharType *cf = c;
- int fh = font->get_height();
int ascent = font->get_ascent();
int descent = font->get_descent();
- line_ascent = MAX(line_ascent, ascent);
- line_descent = MAX(line_descent, descent);
- fh = MAX(fh, line_ascent + line_descent); // various fonts!
-
Color color;
bool underline = false;
@@ -317,8 +315,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
end++;
}
+ CHECK_HEIGHT(fh);
ENSURE_WIDTH(w);
+ line_ascent = MAX(line_ascent, ascent);
+ line_descent = MAX(line_descent, descent);
+ fh = line_ascent + line_descent;
+
if (end && c[end - 1] == ' ') {
if (p_mode == PROCESS_CACHE) {
spaces_size += font->get_char_size(' ').width;
@@ -468,6 +471,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
//set minimums to zero
for (int i = 0; i < table->columns.size(); i++) {
table->columns[i].min_width = 0;
+ table->columns[i].max_width = 0;
table->columns[i].width = 0;
}
//compute minimum width for each cell
@@ -485,6 +489,7 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
_process_line(frame, Point2(), ly, available_width, i, PROCESS_CACHE, cfont, Color());
table->columns[column].min_width = MAX(table->columns[column].min_width, frame->lines[i].minimum_width);
+ table->columns[column].max_width = MAX(table->columns[column].max_width, frame->lines[i].maximum_width);
}
idx++;
}
@@ -497,12 +502,13 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
for (int i = 0; i < table->columns.size(); i++) {
remaining_width -= table->columns[i].min_width;
+ if (table->columns[i].max_width > table->columns[i].min_width)
+ table->columns[i].expand = true;
if (table->columns[i].expand)
total_ratio += table->columns[i].expand_ratio;
}
//assign actual widths
-
for (int i = 0; i < table->columns.size(); i++) {
table->columns[i].width = table->columns[i].min_width;
if (table->columns[i].expand)
@@ -510,6 +516,39 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int &
table->total_width += table->columns[i].width + hseparation;
}
+ //resize to max_width if needed and distribute the remaining space
+ bool table_need_fit = true;
+ while (table_need_fit) {
+ table_need_fit = false;
+ //fit slim
+ for (int i = 0; i < table->columns.size(); i++) {
+ if (!table->columns[i].expand)
+ continue;
+ int dif = table->columns[i].width - table->columns[i].max_width;
+ if (dif > 0) {
+ table_need_fit = true;
+ table->columns[i].width = table->columns[i].max_width;
+ table->total_width -= dif;
+ total_ratio -= table->columns[i].expand_ratio;
+ }
+ }
+ //grow
+ remaining_width = available_width - table->total_width;
+ if (remaining_width > 0 && total_ratio > 0) {
+ for (int i = 0; i < table->columns.size(); i++) {
+ if (table->columns[i].expand) {
+ int dif = table->columns[i].max_width - table->columns[i].width;
+ if (dif > 0) {
+ int slice = table->columns[i].expand_ratio * remaining_width / total_ratio;
+ int incr = MIN(dif, slice);
+ table->columns[i].width += incr;
+ table->total_width += incr;
+ }
+ }
+ }
+ }
+ }
+
//compute caches properly again with the right width
idx = 0;
for (List<Item *>::Element *E = table->subitems.front(); E; E = E->next()) {
@@ -1632,7 +1671,7 @@ Error RichTextLabel::append_bbcode(const String &p_bbcode) {
tag_stack.push_front(tag);
} else if (tag.begins_with("cell=")) {
- int ratio = tag.substr(6, tag.length()).to_int();
+ int ratio = tag.substr(5, tag.length()).to_int();
if (ratio < 1)
ratio = 1;
//use monospace font
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index e7d5e6bb1b..83938cff61 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -87,6 +87,7 @@ private:
int height_accum_cache;
int char_count;
int minimum_width;
+ int maximum_width;
Line() {
from = NULL;
@@ -199,6 +200,7 @@ private:
bool expand;
int expand_ratio;
int min_width;
+ int max_width;
int width;
};
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 95fcda2db3..e1cabd3f88 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -199,54 +199,40 @@ void ScrollBar::_gui_input(Ref<InputEvent> p_event) {
}
}
- Ref<InputEventKey> k = p_event;
+ if (p_event->is_pressed()) {
- if (k.is_valid()) {
+ if (p_event->is_action("ui_left")) {
- if (!k->is_pressed())
- return;
-
- switch (k->get_scancode()) {
-
- case KEY_LEFT: {
-
- if (orientation != HORIZONTAL)
- return;
- set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ if (orientation != HORIZONTAL)
+ return;
+ set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
- } break;
- case KEY_RIGHT: {
+ } else if (p_event->is_action("ui_right")) {
- if (orientation != HORIZONTAL)
- return;
- set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
-
- } break;
- case KEY_UP: {
+ if (orientation != HORIZONTAL)
+ return;
+ set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
- if (orientation != VERTICAL)
- return;
+ } else if (p_event->is_action("ui_up")) {
- set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
+ if (orientation != VERTICAL)
+ return;
- } break;
- case KEY_DOWN: {
+ set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
- if (orientation != VERTICAL)
- return;
- set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
+ } else if (p_event->is_action("ui_down")) {
- } break;
- case KEY_HOME: {
+ if (orientation != VERTICAL)
+ return;
+ set_value(get_value() + (custom_step >= 0 ? custom_step : get_step()));
- set_value(get_min());
+ } else if (p_event->is_action("ui_home")) {
- } break;
- case KEY_END: {
+ set_value(get_min());
- set_value(get_max());
+ } else if (p_event->is_action("ui_end")) {
- } break;
+ set_value(get_max());
}
}
}
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
index a7a1b499c3..46215c9277 100644
--- a/scene/gui/slider.cpp
+++ b/scene/gui/slider.cpp
@@ -118,28 +118,14 @@ void Slider::_gui_input(Ref<InputEvent> p_event) {
return;
set_value(get_value() - (custom_step >= 0 ? custom_step : get_step()));
accept_event();
+ } else if (p_event->is_action("ui_home") && p_event->is_pressed()) {
- } else {
-
- Ref<InputEventKey> k = p_event;
-
- if (!k.is_valid() || !k->is_pressed())
- return;
-
- switch (k->get_scancode()) {
-
- case KEY_HOME: {
-
- set_value(get_min());
- accept_event();
- } break;
- case KEY_END: {
-
- set_value(get_max());
- accept_event();
+ set_value(get_min());
+ accept_event();
+ } else if (p_event->is_action("ui_end") && p_event->is_pressed()) {
- } break;
- }
+ set_value(get_max());
+ accept_event();
}
}
}
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index 0312e58094..6e85ce5eb4 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -474,21 +474,24 @@ void TabContainer::remove_child_notify(Node *p_child) {
Control::remove_child_notify(p_child);
- int tc = get_tab_count();
- if (current == tc - 1) {
- current--;
- if (current < 0)
- current = 0;
- else {
- call_deferred("set_current_tab", current);
- }
- }
+ call_deferred("_update_current_tab");
p_child->disconnect("renamed", this, "_child_renamed_callback");
update();
}
+void TabContainer::_update_current_tab() {
+
+ int tc = get_tab_count();
+ if (current >= tc)
+ current = tc - 1;
+ if (current < 0)
+ current = 0;
+ else
+ set_current_tab(current);
+}
+
void TabContainer::set_tab_align(TabAlign p_align) {
ERR_FAIL_INDEX(p_align, 3);
@@ -664,6 +667,7 @@ void TabContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("_child_renamed_callback"), &TabContainer::_child_renamed_callback);
ClassDB::bind_method(D_METHOD("_on_theme_changed"), &TabContainer::_on_theme_changed);
+ ClassDB::bind_method(D_METHOD("_update_current_tab"), &TabContainer::_update_current_tab);
ADD_SIGNAL(MethodInfo("tab_changed", PropertyInfo(Variant::INT, "tab")));
ADD_SIGNAL(MethodInfo("tab_selected", PropertyInfo(Variant::INT, "tab")));
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index 0ba8c205ea..4bc6e00145 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -62,6 +62,7 @@ private:
Vector<Control *> _get_tabs() const;
int _get_tab_width(int p_index) const;
void _on_theme_changed();
+ void _update_current_tab();
protected:
void _child_renamed_callback();
diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp
index f0e89877cd..dee32aef7a 100644
--- a/scene/gui/tabs.cpp
+++ b/scene/gui/tabs.cpp
@@ -882,4 +882,6 @@ Tabs::Tabs() {
min_width = 0;
scrolling_enabled = true;
+ buttons_visible = false;
+ hover = -1;
}
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index e8454e021f..e214a020d5 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -145,6 +145,7 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
text[p_line].region_info.clear();
+ int ending_color_region = -1;
for (int i = 0; i < len; i++) {
if (!_is_symbol(str[i]))
@@ -184,6 +185,12 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
cri.region = j;
text[p_line].region_info[i] = cri;
i += lr - 1;
+
+ if (ending_color_region == -1 && !cr.line_only) {
+ ending_color_region = j;
+ } else if (ending_color_region == j) {
+ ending_color_region = -1;
+ }
break;
}
@@ -211,10 +218,16 @@ void TextEdit::Text::_update_line_cache(int p_line) const {
cri.region = j;
text[p_line].region_info[i] = cri;
i += lr - 1;
+
+ if (ending_color_region == j) {
+ ending_color_region = -1;
+ }
+
break;
}
}
}
+ text[p_line].ending_color_region = ending_color_region;
}
const Map<int, TextEdit::Text::ColorRegionInfo> &TextEdit::Text::get_color_region_info(int p_line) const {
@@ -619,44 +632,10 @@ void TextEdit::_notification(int p_what) {
Color color = cache.font_color;
color.a *= readonly_alpha;
- int in_region = -1;
-
if (syntax_coloring) {
-
if (cache.background_color.a > 0.01) {
-
VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2i(), get_size()), cache.background_color);
}
- //compute actual region to start (may be inside say, a comment).
- //slow in very large documments :( but ok for source!
-
- for (int i = 0; i < cursor.line_ofs; i++) {
-
- const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(i);
-
- if (in_region >= 0 && color_regions[in_region].line_only) {
- in_region = -1; //reset regions that end at end of line
- }
-
- for (const Map<int, Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) {
-
- const Text::ColorRegionInfo &cri = E->get();
-
- if (in_region == -1) {
-
- if (!cri.end) {
-
- in_region = cri.region;
- }
- } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise
-
- if (cri.end || color_regions[cri.region].eq) {
-
- in_region = -1;
- }
- }
- }
- }
}
int brace_open_match_line = -1;
@@ -776,7 +755,6 @@ void TextEdit::_notification(int p_what) {
j--;
}
if (escaped) {
- j--;
cc = '\\';
continue;
}
@@ -805,7 +783,6 @@ void TextEdit::_notification(int p_what) {
}
}
- int deregion = 0; //force it to clear inrgion
Point2 cursor_pos;
// get the highlighted words
@@ -849,19 +826,12 @@ void TextEdit::_notification(int p_what) {
if (smooth_scroll_enabled)
ofs_y -= ((v_scroll->get_value() - get_line_scroll_pos()) * get_row_height());
- bool prev_is_char = false;
- bool prev_is_number = false;
- bool in_keyword = false;
bool underlined = false;
- bool in_word = false;
- bool in_function_name = false;
- bool in_member_variable = false;
- bool is_hex_notation = false;
- Color keyword_color;
// check if line contains highlighted word
int highlighted_text_col = -1;
int search_text_col = -1;
+ int highlighted_word_col = -1;
if (!search_text.empty())
search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0);
@@ -869,7 +839,11 @@ void TextEdit::_notification(int p_what) {
if (highlighted_text.length() != 0 && highlighted_text != search_text)
highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
- const Map<int, Text::ColorRegionInfo> &cri_map = text.get_color_region_info(line);
+ if (select_identifiers_enabled && highlighted_word.length() != 0) {
+ if (_is_char(highlighted_word[0])) {
+ highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
+ }
+ }
if (text.is_marked(line)) {
@@ -937,170 +911,28 @@ void TextEdit::_notification(int p_what) {
cache.font->draw(ci, Point2(cache.style_normal->get_margin(MARGIN_LEFT) + cache.breakpoint_gutter_width + ofs_x, ofs_y + cache.font->get_ascent()), fc, cache.line_number_color);
}
- //loop through characters in one line
- for (int j = 0; j < str.length(); j++) {
-
- //look for keyword
-
- if (deregion > 0) {
- deregion--;
- if (deregion == 0)
- in_region = -1;
- }
- if (syntax_coloring && deregion == 0) {
-
- color = cache.font_color; //reset
- color.a *= readonly_alpha;
- //find keyword
- bool is_char = _is_text_char(str[j]);
- bool is_symbol = _is_symbol(str[j]);
- bool is_number = _is_number(str[j]);
-
- if (j == 0 && in_region >= 0 && color_regions[in_region].line_only) {
- in_region = -1; //reset regions that end at end of line
- }
-
- // allow ABCDEF in hex notation
- if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) {
- is_number = true;
- } else {
- is_hex_notation = false;
- }
-
- // check for dot or underscore or 'x' for hex notation in floating point number
- if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) {
- is_number = true;
- is_symbol = false;
- is_char = false;
-
- if (str[j] == 'x' && str[j - 1] == '0') {
- is_hex_notation = true;
- }
- }
-
- if (!in_word && _is_char(str[j]) && !is_number) {
- in_word = true;
- }
-
- if ((in_keyword || in_word) && !is_hex_notation) {
- is_number = false;
- }
-
- if (is_symbol && str[j] != '.' && in_word) {
- in_word = false;
- }
-
- if (is_symbol && cri_map.has(j)) {
-
- const Text::ColorRegionInfo &cri = cri_map[j];
-
- if (in_region == -1) {
-
- if (!cri.end) {
-
- in_region = cri.region;
- }
- } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise
- if (cri.end || color_regions[cri.region].eq) {
-
- deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length();
- }
- }
- }
-
- if (!is_char) {
- in_keyword = false;
- underlined = false;
- }
-
- if (in_region == -1 && !in_keyword && is_char && !prev_is_char) {
-
- int to = j;
- while (to < str.length() && _is_text_char(str[to]))
- to++;
-
- uint32_t hash = String::hash(&str[j], to - j);
- StrRange range(&str[j], to - j);
-
- const Color *col = keywords.custom_getptr(range, hash);
-
- if (!col) {
- col = member_keywords.custom_getptr(range, hash);
-
- if (col) {
- for (int k = j - 1; k >= 0; k--) {
- if (str[k] == '.') {
- col = NULL; //member indexing not allowed
- break;
- } else if (str[k] > 32) {
- break;
- }
- }
- }
- }
-
- if (col) {
-
- in_keyword = true;
- keyword_color = *col;
- }
-
- if (select_identifiers_enabled && highlighted_word != String()) {
- if (highlighted_word == range) {
- underlined = true;
- }
- }
- }
-
- if (!in_function_name && in_word && !in_keyword) {
-
- int k = j;
- while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
- k++;
- }
-
- // check for space between name and bracket
- while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
- k++;
- }
-
- if (str[k] == '(') {
- in_function_name = true;
- }
- }
+ //loop through characters in one line
+ Map<int, HighlighterInfo> color_map;
+ if (syntax_coloring) {
+ color_map = _get_line_syntax_highlighting(line);
+ }
- if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) {
- int k = j;
- while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
- k--;
- }
+ // ensure we at least use the font color
+ Color current_color = cache.font_color;
+ if (readonly) {
+ current_color.a *= readonly_alpha;
+ }
+ for (int j = 0; j < str.length(); j++) {
- if (str[k] == '.') {
- in_member_variable = true;
+ if (syntax_coloring) {
+ if (color_map.has(j)) {
+ current_color = color_map[j].color;
+ if (readonly) {
+ current_color.a *= readonly_alpha;
}
}
-
- if (is_symbol) {
- in_function_name = false;
- in_member_variable = false;
- }
-
- if (in_region >= 0)
- color = color_regions[in_region].color;
- else if (in_keyword)
- color = keyword_color;
- else if (in_member_variable)
- color = cache.member_variable_color;
- else if (in_function_name)
- color = cache.function_color;
- else if (is_symbol)
- color = cache.symbol_color;
- else if (is_number)
- color = cache.number_color;
-
- prev_is_char = is_char;
- prev_is_number = is_number;
+ color = current_color;
}
int char_w;
@@ -1207,6 +1039,13 @@ void TextEdit::_notification(int p_what) {
}
}
+ if (highlighted_word_col != -1) {
+ if (j > highlighted_word_col + highlighted_word.length()) {
+ highlighted_word_col = _get_column_pos_of_word(highlighted_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, j);
+ }
+ underlined = (j >= highlighted_word_col && j < highlighted_word_col + highlighted_word.length());
+ }
+
if (brace_matching_enabled) {
if ((brace_open_match_line == line && brace_open_match_column == j) ||
(cursor.column == j && cursor.line == line && (brace_open_matching || brace_open_mismatch))) {
@@ -1513,6 +1352,7 @@ void TextEdit::_notification(int p_what) {
OS::get_singleton()->set_ime_position(get_global_position() + cursor_pos + Point2(0, get_row_height()));
OS::get_singleton()->set_ime_intermediate_text_callback(_ime_text_callback, this);
}
+
} break;
case NOTIFICATION_FOCUS_ENTER: {
@@ -1529,7 +1369,6 @@ void TextEdit::_notification(int p_what) {
if (raised_from_completion) {
VisualServer::get_singleton()->canvas_item_set_z_index(get_canvas_item(), 1);
}
-
} break;
case NOTIFICATION_FOCUS_EXIT: {
@@ -2142,9 +1981,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (completion_index > 0) {
completion_index--;
- completion_current = completion_options[completion_index];
- update();
+ } else {
+ completion_index = completion_options.size() - 1;
}
+ completion_current = completion_options[completion_index];
+ update();
+
accept_event();
return;
}
@@ -2153,9 +1995,12 @@ void TextEdit::_gui_input(const Ref<InputEvent> &p_gui_input) {
if (completion_index < completion_options.size() - 1) {
completion_index++;
- completion_current = completion_options[completion_index];
- update();
+ } else {
+ completion_index = 0;
}
+ completion_current = completion_options[completion_index];
+ update();
+
accept_event();
return;
}
@@ -4145,6 +3990,44 @@ void TextEdit::_update_caches() {
cache.can_fold_icon = get_icon("GuiTreeArrowDown", "EditorIcons");
cache.folded_eol_icon = get_icon("GuiEllipsis", "EditorIcons");
text.set_font(cache.font);
+
+ if (syntax_highlighter) {
+ syntax_highlighter->_update_cache();
+ }
+}
+
+SyntaxHighlighter *TextEdit::_get_syntax_highlighting() {
+ return syntax_highlighter;
+}
+
+void TextEdit::_set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter) {
+ syntax_highlighter = p_syntax_highlighter;
+ if (syntax_highlighter) {
+ syntax_highlighter->set_text_editor(this);
+ syntax_highlighter->_update_cache();
+ }
+ update();
+}
+
+int TextEdit::_get_line_ending_color_region(int p_line) const {
+ if (p_line < 0 || p_line > text.size() - 1) {
+ return -1;
+ }
+ return text.get_line_ending_color_region(p_line);
+}
+
+TextEdit::ColorRegion TextEdit::_get_color_region(int p_region) const {
+ if (p_region < 0 || p_region > color_regions.size()) {
+ return ColorRegion();
+ }
+ return color_regions[p_region];
+}
+
+Map<int, TextEdit::Text::ColorRegionInfo> TextEdit::_get_line_color_region_info(int p_line) const {
+ if (p_line < 0 || p_line > text.size() - 1) {
+ return Map<int, Text::ColorRegionInfo>();
+ }
+ return text.get_color_region_info(p_line);
}
void TextEdit::clear_colors() {
@@ -4160,6 +4043,14 @@ void TextEdit::add_keyword_color(const String &p_keyword, const Color &p_color)
update();
}
+bool TextEdit::has_keyword_color(String p_keyword) const {
+ return keywords.has(p_keyword);
+}
+
+Color TextEdit::get_keyword_color(String p_keyword) const {
+ return keywords[p_keyword];
+}
+
void TextEdit::add_color_region(const String &p_begin_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
color_regions.push_back(ColorRegion(p_begin_key, p_end_key, p_color, p_line_only));
@@ -4172,6 +4063,14 @@ void TextEdit::add_member_keyword(const String &p_keyword, const Color &p_color)
update();
}
+bool TextEdit::has_member_color(String p_member) const {
+ return member_keywords.has(p_member);
+}
+
+Color TextEdit::get_member_color(String p_member) const {
+ return member_keywords[p_member];
+}
+
void TextEdit::clear_member_keywords() {
member_keywords.clear();
update();
@@ -4471,7 +4370,7 @@ bool TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_from_l
ERR_FAIL_INDEX_V(p_from_line, text.size(), false);
ERR_FAIL_INDEX_V(p_from_column, text[p_from_line].length() + 1, false);
- //search through the whole documment, but start by current line
+ //search through the whole document, but start by current line
int line = p_from_line;
int pos = -1;
@@ -5233,7 +5132,7 @@ void TextEdit::_update_completion_candidates() {
} else {
- while (cofs > 0 && l[cofs - 1] > 32 && _is_completable(l[cofs - 1])) {
+ while (cofs > 0 && l[cofs - 1] > 32 && (l[cofs - 1] == '/' || _is_completable(l[cofs - 1]))) {
s = String::chr(l[cofs - 1]) + s;
if (l[cofs - 1] == '\'' || l[cofs - 1] == '"' || l[cofs - 1] == '$')
break;
@@ -5686,6 +5585,8 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_v_scroll_speed"), &TextEdit::get_v_scroll_speed);
ClassDB::bind_method(D_METHOD("add_keyword_color", "keyword", "color"), &TextEdit::add_keyword_color);
+ ClassDB::bind_method(D_METHOD("has_keyword_color", "keyword"), &TextEdit::has_keyword_color);
+ ClassDB::bind_method(D_METHOD("get_keyword_color", "keyword"), &TextEdit::get_keyword_color);
ClassDB::bind_method(D_METHOD("add_color_region", "begin_key", "end_key", "color", "line_only"), &TextEdit::add_color_region, DEFVAL(false));
ClassDB::bind_method(D_METHOD("clear_colors"), &TextEdit::clear_colors);
ClassDB::bind_method(D_METHOD("menu_option", "option"), &TextEdit::menu_option);
@@ -5739,6 +5640,7 @@ TextEdit::TextEdit() {
clear();
wrap = false;
set_focus_mode(FOCUS_ALL);
+ syntax_highlighter = NULL;
_update_caches();
cache.size = Size2(1, 1);
cache.row_height = 1;
@@ -5855,3 +5757,216 @@ TextEdit::TextEdit() {
TextEdit::~TextEdit() {
}
+
+///////////////////////////////////////////////////////////////////////////////
+
+Map<int, TextEdit::HighlighterInfo> TextEdit::_get_line_syntax_highlighting(int p_line) {
+ if (syntax_highlighter != NULL) {
+ return syntax_highlighter->_get_line_syntax_highlighting(p_line);
+ }
+
+ Map<int, HighlighterInfo> color_map;
+
+ bool prev_is_char = false;
+ bool prev_is_number = false;
+ bool in_keyword = false;
+ bool in_word = false;
+ bool in_function_name = false;
+ bool in_member_variable = false;
+ bool is_hex_notation = false;
+ Color keyword_color;
+ Color color;
+
+ int in_region = -1;
+ int deregion = 0;
+ for (int i = 0; i < p_line; i++) {
+ int ending_color_region = text.get_line_ending_color_region(i);
+ if (in_region == -1) {
+ in_region = ending_color_region;
+ } else if (in_region == ending_color_region) {
+ in_region = -1;
+ } else {
+ const Map<int, TextEdit::Text::ColorRegionInfo> &cri_map = text.get_color_region_info(i);
+ for (const Map<int, TextEdit::Text::ColorRegionInfo>::Element *E = cri_map.front(); E; E = E->next()) {
+ const TextEdit::Text::ColorRegionInfo &cri = E->get();
+ if (cri.region == in_region) {
+ in_region = -1;
+ }
+ }
+ }
+ }
+
+ const Map<int, TextEdit::Text::ColorRegionInfo> cri_map = text.get_color_region_info(p_line);
+ const String &str = text[p_line];
+ Color prev_color;
+ for (int j = 0; j < str.length(); j++) {
+ HighlighterInfo highlighter_info;
+
+ if (deregion > 0) {
+ deregion--;
+ if (deregion == 0) {
+ in_region = -1;
+ }
+ }
+
+ if (deregion != 0) {
+ if (color != prev_color) {
+ prev_color = color;
+ highlighter_info.color = color;
+ color_map[j] = highlighter_info;
+ }
+ continue;
+ }
+
+ color = cache.font_color;
+
+ bool is_char = _is_text_char(str[j]);
+ bool is_symbol = _is_symbol(str[j]);
+ bool is_number = _is_number(str[j]);
+
+ // allow ABCDEF in hex notation
+ if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) {
+ is_number = true;
+ } else {
+ is_hex_notation = false;
+ }
+
+ // check for dot or underscore or 'x' for hex notation in floating point number
+ if ((str[j] == '.' || str[j] == 'x' || str[j] == '_') && !in_word && prev_is_number && !is_number) {
+ is_number = true;
+ is_symbol = false;
+ is_char = false;
+
+ if (str[j] == 'x' && str[j - 1] == '0') {
+ is_hex_notation = true;
+ }
+ }
+
+ if (!in_word && _is_char(str[j]) && !is_number) {
+ in_word = true;
+ }
+
+ if ((in_keyword || in_word) && !is_hex_notation) {
+ is_number = false;
+ }
+
+ if (is_symbol && str[j] != '.' && in_word) {
+ in_word = false;
+ }
+
+ if (is_symbol && cri_map.has(j)) {
+ const TextEdit::Text::ColorRegionInfo &cri = cri_map[j];
+
+ if (in_region == -1) {
+ if (!cri.end) {
+ in_region = cri.region;
+ }
+ } else if (in_region == cri.region && !color_regions[cri.region].line_only) { //ignore otherwise
+ if (cri.end || color_regions[cri.region].eq) {
+ deregion = color_regions[cri.region].eq ? color_regions[cri.region].begin_key.length() : color_regions[cri.region].end_key.length();
+ }
+ }
+ }
+
+ if (!is_char) {
+ in_keyword = false;
+ }
+
+ if (in_region == -1 && !in_keyword && is_char && !prev_is_char) {
+
+ int to = j;
+ while (to < str.length() && _is_text_char(str[to]))
+ to++;
+
+ uint32_t hash = String::hash(&str[j], to - j);
+ StrRange range(&str[j], to - j);
+
+ const Color *col = keywords.custom_getptr(range, hash);
+
+ if (!col) {
+ col = member_keywords.custom_getptr(range, hash);
+
+ if (col) {
+ for (int k = j - 1; k >= 0; k--) {
+ if (str[k] == '.') {
+ col = NULL; //member indexing not allowed
+ break;
+ } else if (str[k] > 32) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (col) {
+ in_keyword = true;
+ keyword_color = *col;
+ }
+ }
+
+ if (!in_function_name && in_word && !in_keyword) {
+
+ int k = j;
+ while (k < str.length() && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
+ k++;
+ }
+
+ // check for space between name and bracket
+ while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) {
+ k++;
+ }
+
+ if (str[k] == '(') {
+ in_function_name = true;
+ }
+ }
+
+ if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) {
+ int k = j;
+ while (k > 0 && !_is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') {
+ k--;
+ }
+
+ if (str[k] == '.') {
+ in_member_variable = true;
+ }
+ }
+
+ if (is_symbol) {
+ in_function_name = false;
+ in_member_variable = false;
+ }
+
+ if (in_region >= 0)
+ color = color_regions[in_region].color;
+ else if (in_keyword)
+ color = keyword_color;
+ else if (in_member_variable)
+ color = cache.member_variable_color;
+ else if (in_function_name)
+ color = cache.function_color;
+ else if (is_symbol)
+ color = cache.symbol_color;
+ else if (is_number)
+ color = cache.number_color;
+
+ prev_is_char = is_char;
+ prev_is_number = is_number;
+
+ if (color != prev_color) {
+ prev_color = color;
+ highlighter_info.color = color;
+ color_map[j] = highlighter_info;
+ }
+ }
+
+ return color_map;
+}
+
+void SyntaxHighlighter::set_text_editor(TextEdit *p_text_editor) {
+ text_editor = p_text_editor;
+}
+
+TextEdit *SyntaxHighlighter::get_text_editor() {
+ return text_editor;
+}
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 8ac3b9fce6..2360ce79db 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -36,10 +36,84 @@
#include "scene/gui/scroll_bar.h"
#include "scene/main/timer.h"
+class SyntaxHighlighter;
+
class TextEdit : public Control {
- GDCLASS(TextEdit, Control);
+ GDCLASS(TextEdit, Control)
+
+public:
+ struct HighlighterInfo {
+ Color color;
+ };
+
+ struct ColorRegion {
+
+ Color color;
+ String begin_key;
+ String end_key;
+ bool line_only;
+ bool eq;
+ ColorRegion(const String &p_begin_key = "", const String &p_end_key = "", const Color &p_color = Color(), bool p_line_only = false) {
+ begin_key = p_begin_key;
+ end_key = p_end_key;
+ color = p_color;
+ line_only = p_line_only || p_end_key == "";
+ eq = begin_key == end_key;
+ }
+ };
+
+ class Text {
+ public:
+ struct ColorRegionInfo {
+
+ int region;
+ bool end;
+ };
+
+ struct Line {
+ int width_cache : 24;
+ bool marked : 1;
+ bool breakpoint : 1;
+ bool hidden : 1;
+ int ending_color_region;
+ Map<int, ColorRegionInfo> region_info;
+ String data;
+ };
+
+ private:
+ const Vector<ColorRegion> *color_regions;
+ mutable Vector<Line> text;
+ Ref<Font> font;
+ int indent_size;
+
+ void _update_line_cache(int p_line) const;
+
+ public:
+ void set_indent_size(int p_indent_size);
+ void set_font(const Ref<Font> &p_font);
+ void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; }
+ int get_line_width(int p_line) const;
+ int get_max_width(bool p_exclude_hidden = false) const;
+ const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const;
+ void set(int p_line, const String &p_text);
+ void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; }
+ bool is_marked(int p_line) const { return text[p_line].marked; }
+ void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; }
+ bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
+ void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }
+ bool is_hidden(int p_line) const { return text[p_line].hidden; }
+ int get_line_ending_color_region(int p_line) const { return text[p_line].ending_color_region; }
+ void insert(int p_at, const String &p_text);
+ void remove(int p_at);
+ int size() const { return text.size(); }
+ void clear();
+ void clear_caches();
+ _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; }
+ Text() { indent_size = 4; }
+ };
+private:
struct Cursor {
int last_fit_x;
int line, column; ///< cursor
@@ -115,70 +189,6 @@ class TextEdit : public Control {
Size2 size;
} cache;
- struct ColorRegion {
-
- Color color;
- String begin_key;
- String end_key;
- bool line_only;
- bool eq;
- ColorRegion(const String &p_begin_key = "", const String &p_end_key = "", const Color &p_color = Color(), bool p_line_only = false) {
- begin_key = p_begin_key;
- end_key = p_end_key;
- color = p_color;
- line_only = p_line_only || p_end_key == "";
- eq = begin_key == end_key;
- }
- };
-
- class Text {
- public:
- struct ColorRegionInfo {
-
- int region;
- bool end;
- };
-
- struct Line {
- int width_cache : 24;
- bool marked : 1;
- bool breakpoint : 1;
- bool hidden : 1;
- Map<int, ColorRegionInfo> region_info;
- String data;
- };
-
- private:
- const Vector<ColorRegion> *color_regions;
- mutable Vector<Line> text;
- Ref<Font> font;
- int indent_size;
-
- void _update_line_cache(int p_line) const;
-
- public:
- void set_indent_size(int p_indent_size);
- void set_font(const Ref<Font> &p_font);
- void set_color_regions(const Vector<ColorRegion> *p_regions) { color_regions = p_regions; }
- int get_line_width(int p_line) const;
- int get_max_width(bool p_exclude_hidden = false) const;
- const Map<int, ColorRegionInfo> &get_color_region_info(int p_line) const;
- void set(int p_line, const String &p_text);
- void set_marked(int p_line, bool p_marked) { text[p_line].marked = p_marked; }
- bool is_marked(int p_line) const { return text[p_line].marked; }
- void set_breakpoint(int p_line, bool p_breakpoint) { text[p_line].breakpoint = p_breakpoint; }
- bool is_breakpoint(int p_line) const { return text[p_line].breakpoint; }
- void set_hidden(int p_line, bool p_hidden) { text[p_line].hidden = p_hidden; }
- bool is_hidden(int p_line) const { return text[p_line].hidden; }
- void insert(int p_at, const String &p_text);
- void remove(int p_at);
- int size() const { return text.size(); }
- void clear();
- void clear_caches();
- _FORCE_INLINE_ const String &operator[](int p_line) const { return text[p_line].data; }
- Text() { indent_size = 4; }
- };
-
struct TextOperation {
enum Type {
@@ -209,9 +219,12 @@ class TextEdit : public Control {
void _do_text_op(const TextOperation &p_op, bool p_reverse);
//syntax coloring
+ SyntaxHighlighter *syntax_highlighter;
HashMap<String, Color> keywords;
HashMap<String, Color> member_keywords;
+ Map<int, HighlighterInfo> _get_line_syntax_highlighting(int p_line);
+
Vector<ColorRegion> color_regions;
Set<String> completion_prefixes;
@@ -391,6 +404,13 @@ protected:
static void _bind_methods();
public:
+ SyntaxHighlighter *_get_syntax_highlighting();
+ void _set_syntax_highlighting(SyntaxHighlighter *p_syntax_highlighter);
+
+ int _get_line_ending_color_region(int p_line) const;
+ ColorRegion _get_color_region(int p_region) const;
+ Map<int, Text::ColorRegionInfo> _get_line_color_region_info(int p_line) const;
+
enum MenuItems {
MENU_CUT,
MENU_COPY,
@@ -545,10 +565,15 @@ public:
bool is_insert_mode() const;
void add_keyword_color(const String &p_keyword, const Color &p_color);
+ bool has_keyword_color(String p_keyword) const;
+ Color get_keyword_color(String p_keyword) const;
+
void add_color_region(const String &p_begin_key = String(), const String &p_end_key = String(), const Color &p_color = Color(), bool p_line_only = false);
void clear_colors();
void add_member_keyword(const String &p_keyword, const Color &p_color);
+ bool has_member_color(String p_member) const;
+ Color get_member_color(String p_member) const;
void clear_member_keywords();
int get_v_scroll() const;
@@ -621,4 +646,19 @@ public:
VARIANT_ENUM_CAST(TextEdit::MenuItems);
VARIANT_ENUM_CAST(TextEdit::SearchFlags);
+class SyntaxHighlighter {
+protected:
+ TextEdit *text_editor;
+
+public:
+ virtual void _update_cache() = 0;
+ virtual Map<int, TextEdit::HighlighterInfo> _get_line_syntax_highlighting(int p_line) = 0;
+
+ virtual String get_name() = 0;
+ virtual List<String> get_supported_languages() = 0;
+
+ void set_text_editor(TextEdit *p_text_editor);
+ TextEdit *get_text_editor();
+};
+
#endif // TEXT_EDIT_H
diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp
index 01b00c34ea..82d983184b 100644
--- a/scene/gui/texture_progress.cpp
+++ b/scene/gui/texture_progress.cpp
@@ -106,6 +106,33 @@ Ref<Texture> TextureProgress::get_progress_texture() const {
return progress;
}
+void TextureProgress::set_tint_under(const Color &p_tint) {
+ tint_under = p_tint;
+ update();
+}
+
+Color TextureProgress::get_tint_under() const {
+ return tint_under;
+}
+
+void TextureProgress::set_tint_progress(const Color &p_tint) {
+ tint_progress = p_tint;
+ update();
+}
+
+Color TextureProgress::get_tint_progress() const {
+ return tint_progress;
+}
+
+void TextureProgress::set_tint_over(const Color &p_tint) {
+ tint_over = p_tint;
+ update();
+}
+
+Color TextureProgress::get_tint_over() const {
+ return tint_over;
+}
+
Point2 TextureProgress::unit_val_to_uv(float val) {
if (progress.is_null())
return Point2();
@@ -117,22 +144,45 @@ Point2 TextureProgress::unit_val_to_uv(float val) {
Point2 p = get_relative_center();
- if (val < 0.125)
- return Point2(p.x + (1 - p.x) * val * 8, 0);
- if (val < 0.25)
- return Point2(1, p.y * (val - 0.125) * 8);
- if (val < 0.375)
- return Point2(1, p.y + (1 - p.y) * (val - 0.25) * 8);
- if (val < 0.5)
- return Point2(1 - (1 - p.x) * (val - 0.375) * 8, 1);
- if (val < 0.625)
- return Point2(p.x * (1 - (val - 0.5) * 8), 1);
- if (val < 0.75)
- return Point2(0, 1 - ((1 - p.y) * (val - 0.625) * 8));
- if (val < 0.875)
- return Point2(0, p.y - p.y * (val - 0.75) * 8);
- else
- return Point2(p.x * (val - 0.875) * 8, 0);
+ // Minimal version of Liang-Barsky clipping algorithm
+ float angle = (val * Math_TAU) - Math_PI * 0.5;
+ Point2 dir = Vector2(Math::cos(angle), Math::sin(angle));
+ float t1 = 1.0;
+ float cp;
+ float cq;
+ float cr;
+ float edgeLeft = 0.0;
+ float edgeRight = 1.0;
+ float edgeBottom = 0.0;
+ float edgeTop = 1.0;
+
+ for (int edge = 0; edge < 4; edge++) {
+ if (edge == 0) {
+ if (dir.x > 0)
+ continue;
+ cp = -dir.x;
+ cq = -(edgeLeft - p.x);
+ } else if (edge == 1) {
+ if (dir.x < 0)
+ continue;
+ cp = dir.x;
+ cq = (edgeRight - p.x);
+ } else if (edge == 2) {
+ if (dir.y > 0)
+ continue;
+ cp = -dir.y;
+ cq = -(edgeBottom - p.y);
+ } else if (edge == 3) {
+ if (dir.y < 0)
+ continue;
+ cp = dir.y;
+ cq = (edgeTop - p.y);
+ }
+ cr = cq / cp;
+ if (cr >= 0 && cr < t1)
+ t1 = cr;
+ }
+ return (p + t1 * dir);
}
Point2 TextureProgress::get_relative_center() {
@@ -147,7 +197,7 @@ Point2 TextureProgress::get_relative_center() {
return p;
}
-void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio) {
+void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio, const Color &p_modulate) {
Vector2 texture_size = p_texture->get_size();
Vector2 topleft = Vector2(stretch_margin[MARGIN_LEFT], stretch_margin[MARGIN_TOP]);
Vector2 bottomright = Vector2(stretch_margin[MARGIN_RIGHT], stretch_margin[MARGIN_BOTTOM]);
@@ -158,7 +208,7 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F
if (p_ratio < 1.0) {
// Drawing a partially-filled 9-patch is a little tricky -
// texture is divided by 3 sections toward fill direction,
- // then middle section is streching while the other two aren't.
+ // then middle section is stretching while the other two aren't.
double width_total = 0.0;
double width_texture = 0.0;
@@ -217,7 +267,7 @@ void TextureProgress::draw_nine_patch_stretched(const Ref<Texture> &p_texture, F
}
RID ci = get_canvas_item();
- VS::get_singleton()->canvas_item_add_nine_patch(ci, dst_rect, src_rect, p_texture->get_rid(), topleft, bottomright);
+ VS::get_singleton()->canvas_item_add_nine_patch(ci, dst_rect, src_rect, p_texture->get_rid(), topleft, bottomright, VS::NINE_PATCH_STRETCH, VS::NINE_PATCH_STRETCH, true, p_modulate);
}
void TextureProgress::_notification(int p_what) {
@@ -228,42 +278,42 @@ void TextureProgress::_notification(int p_what) {
if (nine_patch_stretch && (mode == FILL_LEFT_TO_RIGHT || mode == FILL_RIGHT_TO_LEFT || mode == FILL_TOP_TO_BOTTOM || mode == FILL_BOTTOM_TO_TOP)) {
if (under.is_valid()) {
- draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0);
+ draw_nine_patch_stretched(under, FILL_LEFT_TO_RIGHT, 1.0, tint_under);
}
if (progress.is_valid()) {
- draw_nine_patch_stretched(progress, mode, get_as_ratio());
+ draw_nine_patch_stretched(progress, mode, get_as_ratio(), tint_progress);
}
if (over.is_valid()) {
- draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0);
+ draw_nine_patch_stretched(over, FILL_LEFT_TO_RIGHT, 1.0, tint_over);
}
} else {
if (under.is_valid())
- draw_texture(under, Point2());
+ draw_texture(under, Point2(), tint_under);
if (progress.is_valid()) {
Size2 s = progress->get_size();
switch (mode) {
case FILL_LEFT_TO_RIGHT: {
Rect2 region = Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y));
- draw_texture_rect_region(progress, region, region);
+ draw_texture_rect_region(progress, region, region, tint_progress);
} break;
case FILL_RIGHT_TO_LEFT: {
Rect2 region = Rect2(Point2(s.x - s.x * get_as_ratio(), 0), Size2(s.x * get_as_ratio(), s.y));
- draw_texture_rect_region(progress, region, region);
+ draw_texture_rect_region(progress, region, region, tint_progress);
} break;
case FILL_TOP_TO_BOTTOM: {
Rect2 region = Rect2(Point2(), Size2(s.x, s.y * get_as_ratio()));
- draw_texture_rect_region(progress, region, region);
+ draw_texture_rect_region(progress, region, region, tint_progress);
} break;
case FILL_BOTTOM_TO_TOP: {
Rect2 region = Rect2(Point2(0, s.y - s.y * get_as_ratio()), Size2(s.x, s.y * get_as_ratio()));
- draw_texture_rect_region(progress, region, region);
+ draw_texture_rect_region(progress, region, region, tint_progress);
} break;
case FILL_CLOCKWISE:
case FILL_COUNTER_CLOCKWISE: {
float val = get_as_ratio() * rad_max_degrees / 360;
if (val == 1) {
Rect2 region = Rect2(Point2(), s);
- draw_texture_rect_region(progress, region, region);
+ draw_texture_rect_region(progress, region, region, tint_progress);
} else if (val != 0) {
Array pts;
float direction = mode == FILL_CLOCKWISE ? 1 : -1;
@@ -288,7 +338,9 @@ void TextureProgress::_notification(int p_what) {
uvs.push_back(uv);
points.push_back(Point2(uv.x * s.x, uv.y * s.y));
}
- draw_polygon(points, Vector<Color>(), uvs, progress);
+ Vector<Color> colors;
+ colors.push_back(tint_progress);
+ draw_polygon(points, colors, uvs, progress);
}
if (Engine::get_singleton()->is_editor_hint()) {
Point2 p = progress->get_size();
@@ -300,11 +352,11 @@ void TextureProgress::_notification(int p_what) {
}
} break;
default:
- draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)));
+ draw_texture_rect_region(progress, Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), Rect2(Point2(), Size2(s.x * get_as_ratio(), s.y)), tint_progress);
}
}
if (over.is_valid())
- draw_texture(over, Point2());
+ draw_texture(over, Point2(), tint_over);
}
} break;
@@ -366,6 +418,15 @@ void TextureProgress::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_fill_mode", "mode"), &TextureProgress::set_fill_mode);
ClassDB::bind_method(D_METHOD("get_fill_mode"), &TextureProgress::get_fill_mode);
+ ClassDB::bind_method(D_METHOD("set_tint_under", "tint"), &TextureProgress::set_tint_under);
+ ClassDB::bind_method(D_METHOD("get_tint_under"), &TextureProgress::get_tint_under);
+
+ ClassDB::bind_method(D_METHOD("set_tint_progress", "tint"), &TextureProgress::set_tint_progress);
+ ClassDB::bind_method(D_METHOD("get_tint_progress"), &TextureProgress::get_tint_progress);
+
+ ClassDB::bind_method(D_METHOD("set_tint_over", "tint"), &TextureProgress::set_tint_over);
+ ClassDB::bind_method(D_METHOD("get_tint_over"), &TextureProgress::get_tint_over);
+
ClassDB::bind_method(D_METHOD("set_radial_initial_angle", "mode"), &TextureProgress::set_radial_initial_angle);
ClassDB::bind_method(D_METHOD("get_radial_initial_angle"), &TextureProgress::get_radial_initial_angle);
@@ -386,6 +447,10 @@ void TextureProgress::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_over", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_over_texture", "get_over_texture");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture_progress", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), "set_progress_texture", "get_progress_texture");
ADD_PROPERTYNZ(PropertyInfo(Variant::INT, "fill_mode", PROPERTY_HINT_ENUM, "Left to Right,Right to Left,Top to Bottom,Bottom to Top,Clockwise,Counter Clockwise"), "set_fill_mode", "get_fill_mode");
+ ADD_GROUP("Tint", "tint_");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_under", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_under", "get_tint_under");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_over", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_over", "get_tint_over");
+ ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_progress", PROPERTY_HINT_COLOR_NO_ALPHA), "set_tint_progress", "get_tint_progress");
ADD_GROUP("Radial Fill", "radial_");
ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "radial_initial_angle", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_radial_initial_angle", "get_radial_initial_angle");
ADD_PROPERTYNZ(PropertyInfo(Variant::REAL, "radial_fill_degrees", PROPERTY_HINT_RANGE, "0.0,360.0,0.1,slider"), "set_fill_degrees", "get_fill_degrees");
@@ -417,4 +482,6 @@ TextureProgress::TextureProgress() {
stretch_margin[MARGIN_RIGHT] = 0;
stretch_margin[MARGIN_BOTTOM] = 0;
stretch_margin[MARGIN_TOP] = 0;
+
+ tint_under = tint_progress = tint_over = Color(1, 1, 1);
}
diff --git a/scene/gui/texture_progress.h b/scene/gui/texture_progress.h
index 77c3980e29..34158b5db5 100644
--- a/scene/gui/texture_progress.h
+++ b/scene/gui/texture_progress.h
@@ -82,6 +82,15 @@ public:
void set_nine_patch_stretch(bool p_stretch);
bool get_nine_patch_stretch() const;
+ void set_tint_under(const Color &p_tint);
+ Color get_tint_under() const;
+
+ void set_tint_progress(const Color &p_tint);
+ Color get_tint_progress() const;
+
+ void set_tint_over(const Color &p_tint);
+ Color get_tint_over() const;
+
Size2 get_minimum_size() const;
TextureProgress();
@@ -93,10 +102,11 @@ private:
Point2 rad_center_off;
bool nine_patch_stretch;
int stretch_margin[4];
+ Color tint_under, tint_progress, tint_over;
Point2 unit_val_to_uv(float val);
Point2 get_relative_center();
- void draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio);
+ void draw_nine_patch_stretched(const Ref<Texture> &p_texture, FillMode p_mode, double p_ratio, const Color &p_modulate);
};
VARIANT_ENUM_CAST(TextureProgress::FillMode);
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index e12044fca2..e7f63997f2 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -2061,322 +2061,318 @@ void Tree::popup_select(int p_option) {
item_edited(popup_edited_item_col, popup_edited_item);
}
-void Tree::_gui_input(Ref<InputEvent> p_event) {
-
- Ref<InputEventKey> k = p_event;
+void Tree::_go_left() {
+ if (selected_col == 0) {
+ if (selected_item->get_children() != NULL && !selected_item->is_collapsed()) {
+ selected_item->set_collapsed(true);
+ } else {
+ if (columns.size() == 1) { // goto parent with one column
+ TreeItem *parent = selected_item->get_parent();
+ if (selected_item != get_root() && parent && parent->is_selectable(selected_col) && !(hide_root && parent == get_root())) {
+ select_single_item(parent, get_root(), selected_col);
+ }
+ } else if (selected_item->get_prev_visible()) {
+ selected_col = columns.size() - 1;
+ _go_up(); // go to upper column if possible
+ }
+ }
+ } else {
+ if (select_mode == SELECT_MULTI) {
+ selected_col--;
+ emit_signal("cell_selected");
+ } else {
- if (k.is_valid()) {
+ selected_item->select(selected_col - 1);
+ }
+ }
+ update();
+ accept_event();
+ ensure_cursor_is_visible();
+}
- if (!k->is_pressed())
- return;
- if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey())
- return;
- if (!root)
+void Tree::_go_right() {
+ if (selected_col == (columns.size() - 1)) {
+ if (selected_item->get_children() != NULL && selected_item->is_collapsed()) {
+ selected_item->set_collapsed(false);
+ } else if (selected_item->get_next_visible()) {
+ selected_item->select(0);
+ _go_down();
return;
+ }
+ } else {
+ if (select_mode == SELECT_MULTI) {
+ selected_col++;
+ emit_signal("cell_selected");
+ } else {
- if (hide_root && !root->get_next_visible())
+ selected_item->select(selected_col + 1);
+ }
+ }
+ update();
+ ensure_cursor_is_visible();
+ accept_event();
+}
+
+void Tree::_go_up() {
+ TreeItem *prev = NULL;
+ if (!selected_item) {
+ prev = get_last_item();
+ selected_col = 0;
+ } else {
+
+ prev = selected_item->get_prev_visible();
+ if (last_keypress != 0) {
+ //incr search next
+ int col;
+ prev = _search_item_text(prev, incr_search, &col, true, true);
+ if (!prev) {
+ accept_event();
+ return;
+ }
+ }
+ }
+
+ if (select_mode == SELECT_MULTI) {
+
+ if (!prev)
return;
+ selected_item = prev;
+ emit_signal("cell_selected");
+ update();
+ } else {
- switch (k->get_scancode()) {
-#define EXIT_BREAK \
- { \
- if (!cursor_can_exit_tree) accept_event(); \
- break; \
+ int col = selected_col < 0 ? 0 : selected_col;
+ while (prev && !prev->cells[col].selectable)
+ prev = prev->get_prev_visible();
+ if (!prev)
+ return; // do nothing..
+ prev->select(col);
}
- case KEY_RIGHT: {
- bool dobreak = true;
- //TreeItem *next = NULL;
- if (!selected_item)
- break;
- if (select_mode == SELECT_ROW) {
- EXIT_BREAK;
- }
- if (selected_col > (columns.size() - 1)) {
- EXIT_BREAK;
- }
- if (k->get_alt()) {
- selected_item->set_collapsed(false);
- TreeItem *next = selected_item->get_children();
- while (next && next != selected_item->next) {
- next->set_collapsed(false);
- next = next->get_next_visible();
- }
- } else if (selected_col == (columns.size() - 1)) {
- if (selected_item->get_children() != NULL && selected_item->is_collapsed()) {
- selected_item->set_collapsed(false);
- } else {
- selected_col = 0;
- dobreak = false; // fall through to key_down
- }
- } else {
- if (select_mode == SELECT_MULTI) {
- selected_col++;
- emit_signal("cell_selected");
- } else {
+ ensure_cursor_is_visible();
+ accept_event();
+}
- selected_item->select(selected_col + 1);
- }
- }
- update();
- ensure_cursor_is_visible();
+void Tree::_go_down() {
+ TreeItem *next = NULL;
+ if (!selected_item) {
+
+ next = hide_root ? root->get_next_visible() : root;
+ selected_item = 0;
+ } else {
+
+ next = selected_item->get_next_visible();
+
+ if (last_keypress != 0) {
+ //incr search next
+ int col;
+ next = _search_item_text(next, incr_search, &col, true);
+ if (!next) {
accept_event();
- if (dobreak) {
- break;
- }
+ return;
}
- case KEY_DOWN: {
+ }
+ }
- TreeItem *next = NULL;
- if (!selected_item) {
+ if (select_mode == SELECT_MULTI) {
- next = hide_root ? root->get_next_visible() : root;
- selected_item = 0;
- } else {
+ if (!next) {
+ return;
+ }
- next = selected_item->get_next_visible();
+ selected_item = next;
+ emit_signal("cell_selected");
+ update();
+ } else {
- //if (diff < uint64_t(GLOBAL_DEF("gui/incr_search_max_interval_msec",2000))) {
- if (last_keypress != 0) {
- //incr search next
- int col;
- next = _search_item_text(next, incr_search, &col, true);
- if (!next) {
- accept_event();
- return;
- }
- }
- }
+ int col = selected_col < 0 ? 0 : selected_col;
- if (select_mode == SELECT_MULTI) {
+ while (next && !next->cells[col].selectable)
+ next = next->get_next_visible();
+ if (!next) {
+ return; // do nothing..
+ }
+ next->select(col);
+ }
- if (!next)
- EXIT_BREAK;
+ ensure_cursor_is_visible();
+ accept_event();
+}
- selected_item = next;
- emit_signal("cell_selected");
- update();
- } else {
+void Tree::_gui_input(Ref<InputEvent> p_event) {
- int col = selected_col < 0 ? 0 : selected_col;
+ Ref<InputEventKey> k = p_event;
- while (next && !next->cells[col].selectable)
- next = next->get_next_visible();
- if (!next)
- EXIT_BREAK; // do nothing..
- next->select(col);
- }
+ if (p_event->is_action("ui_right") && p_event->is_pressed()) {
- ensure_cursor_is_visible();
- accept_event();
+ if (!cursor_can_exit_tree) accept_event();
- } break;
- case KEY_LEFT: {
- bool dobreak = true;
+ if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) {
+ return;
+ }
+ if (k.is_valid() && k->get_alt()) {
+ selected_item->set_collapsed(false);
+ TreeItem *next = selected_item->get_children();
+ while (next && next != selected_item->next) {
+ next->set_collapsed(false);
+ next = next->get_next_visible();
+ }
+ } else {
+ _go_right();
+ }
+ } else if (p_event->is_action("ui_left") && p_event->is_pressed()) {
- //TreeItem *next = NULL;
- if (!selected_item)
- break;
- if (select_mode == SELECT_ROW) {
- EXIT_BREAK;
- }
- if (selected_col < 0) {
- EXIT_BREAK;
- }
- if (k->get_alt()) {
- selected_item->set_collapsed(true);
- TreeItem *next = selected_item->get_children();
- while (next && next != selected_item->next) {
- next->set_collapsed(true);
- next = next->get_next_visible();
- }
- } else if (selected_col == 0) {
- if (selected_item->get_children() != NULL && !selected_item->is_collapsed()) {
- selected_item->set_collapsed(true);
- } else {
- if (columns.size() == 1) { // goto parent with one column
- TreeItem *parent = selected_item->get_parent();
- if (selected_item != get_root() && parent && parent->is_selectable(selected_col) && !(hide_root && parent == get_root())) {
- select_single_item(parent, get_root(), selected_col);
- }
- } else {
- selected_col = columns.size() - 1;
- dobreak = false; // fall through to key_up
- }
- }
- } else {
- if (select_mode == SELECT_MULTI) {
- selected_col--;
- emit_signal("cell_selected");
- } else {
+ if (!cursor_can_exit_tree) accept_event();
- selected_item->select(selected_col - 1);
- }
- }
- update();
- accept_event();
- ensure_cursor_is_visible();
+ if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) {
+ return;
+ }
- if (dobreak) {
- break;
- }
+ if (k.is_valid() && k->get_alt()) {
+ selected_item->set_collapsed(true);
+ TreeItem *next = selected_item->get_children();
+ while (next && next != selected_item->next) {
+ next->set_collapsed(true);
+ next = next->get_next_visible();
}
- case KEY_UP: {
+ } else {
+ _go_left();
+ }
- TreeItem *prev = NULL;
- if (!selected_item) {
- prev = get_last_item();
- selected_col = 0;
- } else {
+ } else if (p_event->is_action("ui_up") && p_event->is_pressed()) {
- prev = selected_item->get_prev_visible();
- if (last_keypress != 0) {
- //incr search next
- int col;
- prev = _search_item_text(prev, incr_search, &col, true, true);
- if (!prev) {
- accept_event();
- return;
- }
- }
- }
+ if (!cursor_can_exit_tree) accept_event();
- if (select_mode == SELECT_MULTI) {
+ _go_up();
- if (!prev)
- break;
- selected_item = prev;
- emit_signal("cell_selected");
- update();
- } else {
+ } else if (p_event->is_action("ui_down") && p_event->is_pressed()) {
- int col = selected_col < 0 ? 0 : selected_col;
- while (prev && !prev->cells[col].selectable)
- prev = prev->get_prev_visible();
- if (!prev)
- break; // do nothing..
- prev->select(col);
- }
+ if (!cursor_can_exit_tree) accept_event();
- ensure_cursor_is_visible();
- accept_event();
+ _go_down();
- } break;
- case KEY_PAGEDOWN: {
+ } else if (p_event->is_action("ui_page_down") && p_event->is_pressed()) {
- TreeItem *next = NULL;
- if (!selected_item)
- break;
- next = selected_item;
+ if (!cursor_can_exit_tree) accept_event();
- for (int i = 0; i < 10; i++) {
+ TreeItem *next = NULL;
+ if (!selected_item)
+ return;
+ next = selected_item;
- TreeItem *_n = next->get_next_visible();
- if (_n) {
- next = _n;
- } else {
+ for (int i = 0; i < 10; i++) {
- break;
- }
- }
- if (next == selected_item)
- break;
+ TreeItem *_n = next->get_next_visible();
+ if (_n) {
+ next = _n;
+ } else {
- if (select_mode == SELECT_MULTI) {
+ return;
+ }
+ }
+ if (next == selected_item)
+ return;
- selected_item = next;
- emit_signal("cell_selected");
- update();
- } else {
+ if (select_mode == SELECT_MULTI) {
- while (next && !next->cells[selected_col].selectable)
- next = next->get_next_visible();
- if (!next)
- EXIT_BREAK; // do nothing..
- next->select(selected_col);
- }
+ selected_item = next;
+ emit_signal("cell_selected");
+ update();
+ } else {
- ensure_cursor_is_visible();
- } break;
- case KEY_PAGEUP: {
+ while (next && !next->cells[selected_col].selectable)
+ next = next->get_next_visible();
+ if (!next) {
+ return; // do nothing..
+ }
+ next->select(selected_col);
+ }
- TreeItem *prev = NULL;
- if (!selected_item)
- break;
- prev = selected_item;
+ ensure_cursor_is_visible();
+ } else if (p_event->is_action("ui_page_up") && p_event->is_pressed()) {
- for (int i = 0; i < 10; i++) {
+ if (!cursor_can_exit_tree) accept_event();
- TreeItem *_n = prev->get_prev_visible();
- if (_n) {
- prev = _n;
- } else {
+ TreeItem *prev = NULL;
+ if (!selected_item)
+ return;
+ prev = selected_item;
- break;
- }
- }
- if (prev == selected_item)
- break;
+ for (int i = 0; i < 10; i++) {
- if (select_mode == SELECT_MULTI) {
+ TreeItem *_n = prev->get_prev_visible();
+ if (_n) {
+ prev = _n;
+ } else {
- selected_item = prev;
- emit_signal("cell_selected");
- update();
- } else {
+ return;
+ }
+ }
+ if (prev == selected_item)
+ return;
- while (prev && !prev->cells[selected_col].selectable)
- prev = prev->get_prev_visible();
- if (!prev)
- EXIT_BREAK; // do nothing..
- prev->select(selected_col);
- }
+ if (select_mode == SELECT_MULTI) {
- ensure_cursor_is_visible();
+ selected_item = prev;
+ emit_signal("cell_selected");
+ update();
+ } else {
- } break;
- case KEY_F2:
- case KEY_ENTER:
- case KEY_KP_ENTER: {
-
- if (selected_item) {
- //bring up editor if possible
- if (!edit_selected()) {
- emit_signal("item_activated");
- incr_search.clear();
- }
- }
- accept_event();
+ while (prev && !prev->cells[selected_col].selectable)
+ prev = prev->get_prev_visible();
+ if (!prev) {
+ return; // do nothing..
+ }
+ prev->select(selected_col);
+ }
+ ensure_cursor_is_visible();
+ } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) {
- } break;
- case KEY_SPACE: {
- if (select_mode == SELECT_MULTI) {
- if (!selected_item)
- break;
- if (selected_item->is_selected(selected_col)) {
- selected_item->deselect(selected_col);
- emit_signal("multi_selected", selected_item, selected_col, false);
- } else if (selected_item->is_selectable(selected_col)) {
- selected_item->select(selected_col);
- emit_signal("multi_selected", selected_item, selected_col, true);
- }
- }
- accept_event();
+ if (selected_item) {
+ //bring up editor if possible
+ if (!edit_selected()) {
+ emit_signal("item_activated");
+ incr_search.clear();
+ }
+ }
+ accept_event();
+ } else if (p_event->is_action("ui_select") && p_event->is_pressed()) {
- } break;
- default: {
+ if (select_mode == SELECT_MULTI) {
+ if (!selected_item)
+ return;
+ if (selected_item->is_selected(selected_col)) {
+ selected_item->deselect(selected_col);
+ emit_signal("multi_selected", selected_item, selected_col, false);
+ } else if (selected_item->is_selectable(selected_col)) {
+ selected_item->select(selected_col);
+ emit_signal("multi_selected", selected_item, selected_col, true);
+ }
+ }
+ accept_event();
+ }
+
+ if (k.is_valid()) { // Incremental search
+
+ if (!k->is_pressed())
+ return;
+ if (k->get_command() || (k->get_shift() && k->get_unicode() == 0) || k->get_metakey())
+ return;
+ if (!root)
+ return;
- if (k->get_unicode() > 0) {
+ if (hide_root && !root->get_next_visible())
+ return;
- _do_incr_search(String::chr(k->get_unicode()));
- accept_event();
+ if (k->get_unicode() > 0) {
- return;
- } else {
- if (k->get_scancode() != KEY_SHIFT)
- last_keypress = 0;
- }
- } break;
+ _do_incr_search(String::chr(k->get_unicode()));
+ accept_event();
+
+ return;
+ } else {
+ if (k->get_scancode() != KEY_SHIFT)
+ last_keypress = 0;
}
}
@@ -2384,7 +2380,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
if (mm.is_valid()) {
- if (cache.font.is_null()) // avoid a strange case that may fuckup stuff
+ if (cache.font.is_null()) // avoid a strange case that may corrupt stuff
update_cache();
Ref<StyleBox> bg = cache.bg;
@@ -2483,7 +2479,7 @@ void Tree::_gui_input(Ref<InputEvent> p_event) {
Ref<InputEventMouseButton> b = p_event;
if (b.is_valid()) {
- if (cache.font.is_null()) // avoid a strange case that may fuckup stuff
+ if (cache.font.is_null()) // avoid a strange case that may corrupt stuff
update_cache();
if (!b->is_pressed()) {
@@ -3911,6 +3907,8 @@ Tree::Tree() {
cache.click_id = -1;
cache.click_item = NULL;
cache.click_column = 0;
+ cache.hover_cell = -1;
+ cache.hover_index = -1;
last_keypress = 0;
focus_in_id = 0;
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index 2a8546a743..5af66c5faa 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -507,6 +507,10 @@ private:
ValueEvaluator *evaluator;
int _count_selected_items(TreeItem *p_from) const;
+ void _go_left();
+ void _go_right();
+ void _go_down();
+ void _go_up();
protected:
static void _bind_methods();
diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp
index 4eee0126d8..88e1847533 100644
--- a/scene/gui/video_player.cpp
+++ b/scene/gui/video_player.cpp
@@ -29,6 +29,7 @@
/*************************************************************************/
#include "video_player.h"
+#include "scene/scene_string_names.h"
#include "os/os.h"
#include "servers/audio_server.h"
@@ -159,11 +160,7 @@ void VideoPlayer::_notification(int p_notification) {
bus_index = AudioServer::get_singleton()->thread_find_bus_index(bus);
- if (stream.is_null())
- return;
- if (paused)
- return;
- if (!playback->is_playing())
+ if (stream.is_null() || paused || !playback->is_playing())
return;
double audio_time = USEC_TO_SEC(OS::get_singleton()->get_ticks_usec());
@@ -174,7 +171,11 @@ void VideoPlayer::_notification(int p_notification) {
if (delta == 0)
return;
- playback->update(delta);
+ playback->update(delta); // playback->is_playing() returns false in the last video frame
+
+ if (!playback->is_playing()) {
+ emit_signal(SceneStringNames::get_singleton()->finished);
+ }
} break;
@@ -467,6 +468,8 @@ void VideoPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_video_texture"), &VideoPlayer::get_video_texture);
+ ADD_SIGNAL(MethodInfo("finished"));
+
ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_track", PROPERTY_HINT_RANGE, "0,128,1"), "set_audio_track", "get_audio_track");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VideoStream"), "set_stream", "get_stream");
//ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/loop"), "set_loop", "has_loop") ;