summaryrefslogtreecommitdiffstats
path: root/scene/gui
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2014-02-09 22:10:30 -0300
committerJuan Linietsky <reduzio@gmail.com>2014-02-09 22:10:30 -0300
commit0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac (patch)
tree276c4d099e178eb67fbd14f61d77b05e3808e9e3 /scene/gui
parent0e49da1687bc8192ed210947da52c9e5c5f301bb (diff)
downloadredot-engine-0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac.tar.gz
GODOT IS OPEN SOURCE
Diffstat (limited to 'scene/gui')
-rw-r--r--scene/gui/SCsub7
-rw-r--r--scene/gui/base_button.cpp375
-rw-r--r--scene/gui/base_button.h102
-rw-r--r--scene/gui/box_container.cpp290
-rw-r--r--scene/gui/box_container.h76
-rw-r--r--scene/gui/button.cpp245
-rw-r--r--scene/gui/button.h88
-rw-r--r--scene/gui/button_array.cpp504
-rw-r--r--scene/gui/button_array.h123
-rw-r--r--scene/gui/button_group.cpp157
-rw-r--r--scene/gui/button_group.h66
-rw-r--r--scene/gui/center_container.cpp78
-rw-r--r--scene/gui/center_container.h49
-rw-r--r--scene/gui/check_button.cpp72
-rw-r--r--scene/gui/check_button.h52
-rw-r--r--scene/gui/color_picker.cpp384
-rw-r--r--scene/gui/color_picker.h120
-rw-r--r--scene/gui/container.cpp154
-rw-r--r--scene/gui/container.h59
-rw-r--r--scene/gui/control.cpp2886
-rw-r--r--scene/gui/control.h395
-rw-r--r--scene/gui/custom_button.cpp40
-rw-r--r--scene/gui/custom_button.h43
-rw-r--r--scene/gui/dialogs.cpp375
-rw-r--r--scene/gui/dialogs.h157
-rw-r--r--scene/gui/empty_control.cpp59
-rw-r--r--scene/gui/empty_control.h48
-rw-r--r--scene/gui/file_dialog.cpp743
-rw-r--r--scene/gui/file_dialog.h173
-rw-r--r--scene/gui/grid_container.cpp230
-rw-r--r--scene/gui/grid_container.h52
-rw-r--r--scene/gui/label.cpp619
-rw-r--r--scene/gui/label.h136
-rw-r--r--scene/gui/line_edit.cpp821
-rw-r--r--scene/gui/line_edit.h120
-rw-r--r--scene/gui/margin_container.cpp90
-rw-r--r--scene/gui/margin_container.h46
-rw-r--r--scene/gui/menu_button.cpp153
-rw-r--r--scene/gui/menu_button.h61
-rw-r--r--scene/gui/option_button.cpp337
-rw-r--r--scene/gui/option_button.h95
-rw-r--r--scene/gui/panel.cpp52
-rw-r--r--scene/gui/panel.h48
-rw-r--r--scene/gui/panel_container.cpp109
-rw-r--r--scene/gui/panel_container.h48
-rw-r--r--scene/gui/popup.cpp236
-rw-r--r--scene/gui/popup.h87
-rw-r--r--scene/gui/popup_menu.cpp916
-rw-r--r--scene/gui/popup_menu.h142
-rw-r--r--scene/gui/progress_bar.cpp72
-rw-r--r--scene/gui/progress_bar.h47
-rw-r--r--scene/gui/range.cpp290
-rw-r--r--scene/gui/range.h96
-rw-r--r--scene/gui/reference_frame.cpp44
-rw-r--r--scene/gui/reference_frame.h45
-rw-r--r--scene/gui/rich_text_label.cpp1476
-rw-r--r--scene/gui/rich_text_label.h304
-rw-r--r--scene/gui/scroll_bar.cpp602
-rw-r--r--scene/gui/scroll_bar.h109
-rw-r--r--scene/gui/scroll_container.cpp427
-rw-r--r--scene/gui/scroll_container.h92
-rw-r--r--scene/gui/separator.cpp81
-rw-r--r--scene/gui/separator.h75
-rw-r--r--scene/gui/slider.cpp247
-rw-r--r--scene/gui/slider.h91
-rw-r--r--scene/gui/spin_box.cpp199
-rw-r--r--scene/gui/spin_box.h75
-rw-r--r--scene/gui/split_container.cpp453
-rw-r--r--scene/gui/split_container.h99
-rw-r--r--scene/gui/tab_container.cpp640
-rw-r--r--scene/gui/tab_container.h96
-rw-r--r--scene/gui/tabs.cpp293
-rw-r--r--scene/gui/tabs.h80
-rw-r--r--scene/gui/text_edit.cpp2943
-rw-r--r--scene/gui/text_edit.h368
-rw-r--r--scene/gui/texture_button.cpp211
-rw-r--r--scene/gui/texture_button.h73
-rw-r--r--scene/gui/texture_frame.cpp137
-rw-r--r--scene/gui/texture_frame.h65
-rw-r--r--scene/gui/texture_progress.cpp124
-rw-r--r--scene/gui/texture_progress.h62
-rw-r--r--scene/gui/tool_button.cpp33
-rw-r--r--scene/gui/tool_button.h40
-rw-r--r--scene/gui/tree.cpp3185
-rw-r--r--scene/gui/tree.h464
-rw-r--r--scene/gui/video_player.cpp280
-rw-r--r--scene/gui/video_player.h89
87 files changed, 26425 insertions, 0 deletions
diff --git a/scene/gui/SCsub b/scene/gui/SCsub
new file mode 100644
index 0000000000..055d2f2474
--- /dev/null
+++ b/scene/gui/SCsub
@@ -0,0 +1,7 @@
+Import('env')
+
+env.add_source_files(env.scene_sources,"*.cpp")
+
+Export('env')
+
+
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
new file mode 100644
index 0000000000..2e03871063
--- /dev/null
+++ b/scene/gui/base_button.cpp
@@ -0,0 +1,375 @@
+/*************************************************************************/
+/* base_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "base_button.h"
+#include "os/keyboard.h"
+#include "print_string.h"
+#include "button_group.h"
+
+
+void BaseButton::_input_event(InputEvent p_event) {
+
+
+ if (status.disabled) // no interaction with disabled button
+ return;
+
+ switch(p_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ const InputEventMouseButton &b=p_event.mouse_button;
+
+ if ( status.disabled || b.button_index!=1 )
+ return;
+
+ if (status.pressing_button)
+ break;
+
+ if (status.click_on_press) {
+
+ if (b.pressed) {
+
+ if (!toggle_mode) { //mouse press attempt
+
+ pressed();
+ emit_signal("pressed");
+
+ } else {
+
+ status.pressed=!status.pressed;
+ pressed();
+ emit_signal("pressed");
+
+ toggled(status.pressed);
+ emit_signal("toggled",status.pressed);
+
+ }
+
+
+ }
+
+ break;
+ }
+
+ if (b.pressed) {
+
+ status.press_attempt=true;
+ status.pressing_inside=true;
+
+ } else {
+
+
+ if (status.press_attempt &&status.pressing_inside) {
+
+ if (!toggle_mode) { //mouse press attempt
+
+ pressed();
+ emit_signal("pressed");
+
+ } else {
+
+ status.pressed=!status.pressed;
+
+ pressed();
+ emit_signal("pressed");
+
+ toggled(status.pressed);
+ emit_signal("toggled",status.pressed);
+
+ }
+
+ }
+
+ status.press_attempt=false;
+
+ }
+
+ update();
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ if (status.press_attempt && status.pressing_button==0) {
+ bool last_press_inside=status.pressing_inside;
+ status.pressing_inside=has_point(Point2(p_event.mouse_motion.x,p_event.mouse_motion.y));
+ if (last_press_inside!=status.pressing_inside)
+ update();
+ }
+ } break;
+ case InputEvent::JOYSTICK_BUTTON:
+ case InputEvent::KEY: {
+
+
+ if (p_event.is_echo()) {
+ break;
+ }
+
+ if (status.disabled) {
+ break;
+ }
+
+ if (status.press_attempt && status.pressing_button==0) {
+ break;
+ }
+
+ if (p_event.is_action("ui_accept")) {
+
+ if (p_event.is_pressed()) {
+
+ status.pressing_button++;
+ status.press_attempt=true;
+ status.pressing_inside=true;
+
+ } else if (status.press_attempt) {
+
+ if (status.pressing_button)
+ status.pressing_button--;
+
+ if (status.pressing_button)
+ break;
+
+ status.press_attempt=false;
+ status.pressing_inside=false;
+
+ if (!toggle_mode) { //mouse press attempt
+
+ pressed();
+ emit_signal("pressed");
+ } else {
+
+ status.pressed=!status.pressed;
+
+ pressed();
+ emit_signal("pressed");
+
+ toggled(status.pressed);
+ emit_signal("toggled",status.pressed);
+ }
+ }
+
+ accept_event();
+ update();
+
+ }
+ }
+
+ }
+}
+
+void BaseButton::_notification(int p_what) {
+
+
+ if (p_what==NOTIFICATION_MOUSE_ENTER) {
+
+ status.hovering=true;
+ update();
+ }
+
+ if (p_what==NOTIFICATION_MOUSE_EXIT) {
+ status.hovering=false;
+ update();
+ }
+ if (p_what==NOTIFICATION_FOCUS_EXIT) {
+
+ if (status.pressing_button && status.press_attempt) {
+ status.press_attempt=false;
+ status.pressing_button=0;
+ }
+ }
+
+ if (p_what==NOTIFICATION_ENTER_SCENE) {
+
+ CanvasItem *ci=this;
+ while(ci) {
+
+ ButtonGroup *bg = ci->cast_to<ButtonGroup>();
+ if (bg) {
+
+ group=bg;
+ group->_add_button(this);
+ }
+
+ ci=ci->get_parent_item();
+ }
+ }
+
+ if (p_what==NOTIFICATION_EXIT_SCENE) {
+
+ if (group)
+ group->_remove_button(this);
+ }
+
+}
+
+void BaseButton::pressed() {
+
+ if (get_script_instance())
+ get_script_instance()->call("pressed");
+}
+
+void BaseButton::toggled(bool p_pressed) {
+
+ if (get_script_instance())
+ get_script_instance()->call("toggled",p_pressed);
+
+}
+
+
+void BaseButton::set_disabled(bool p_disabled) {
+
+ status.disabled = p_disabled;
+ update();
+ _change_notify("disabled");
+ if (p_disabled)
+ set_focus_mode(FOCUS_NONE);
+ else
+ set_focus_mode(FOCUS_ALL);
+};
+
+bool BaseButton::is_disabled() const {
+
+ return status.disabled;
+};
+
+void BaseButton::set_pressed(bool p_pressed) {
+
+ if (!toggle_mode)
+ return;
+ if (status.pressed==p_pressed)
+ return;
+ _change_notify("pressed");
+ status.pressed=p_pressed;
+ update();
+}
+
+bool BaseButton::is_pressing() const{
+
+ return status.press_attempt;
+}
+
+bool BaseButton::is_pressed() const {
+
+ return toggle_mode?status.pressed:status.press_attempt;
+}
+
+
+BaseButton::DrawMode BaseButton::get_draw_mode() const {
+
+ if (status.disabled) {
+ return DRAW_DISABLED;
+ };
+
+ //print_line("press attempt: "+itos(status.press_attempt)+" hover: "+itos(status.hovering)+" pressed: "+itos(status.pressed));
+ if (status.press_attempt==false && status.hovering && !status.pressed) {
+
+
+ return DRAW_HOVER;
+ } else {
+ /* determine if pressed or not */
+
+ bool pressing;
+ if (status.press_attempt) {
+
+ pressing=status.pressing_inside;
+ if (status.pressed)
+ pressing=!pressing;
+ } else {
+
+ pressing=status.pressed;
+ }
+
+ if (pressing)
+ return DRAW_PRESSED;
+ else
+ return DRAW_NORMAL;
+ }
+
+ return DRAW_NORMAL;
+}
+
+void BaseButton::set_toggle_mode(bool p_on) {
+
+ toggle_mode=p_on;
+}
+
+bool BaseButton::is_toggle_mode() const {
+
+ return toggle_mode;
+}
+
+void BaseButton::set_click_on_press(bool p_click_on_press) {
+
+ status.click_on_press=p_click_on_press;
+}
+
+bool BaseButton::get_click_on_press() const {
+
+ return status.click_on_press;
+}
+
+
+void BaseButton::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&BaseButton::_input_event);
+ ObjectTypeDB::bind_method(_MD("set_pressed","pressed"),&BaseButton::set_pressed);
+ ObjectTypeDB::bind_method(_MD("is_pressed"),&BaseButton::is_pressed);
+ ObjectTypeDB::bind_method(_MD("set_toggle_mode","enabled"),&BaseButton::set_toggle_mode);
+ ObjectTypeDB::bind_method(_MD("is_toggle_mode"),&BaseButton::is_toggle_mode);
+ ObjectTypeDB::bind_method(_MD("set_disabled","disabled"),&BaseButton::set_disabled);
+ ObjectTypeDB::bind_method(_MD("is_disabled"),&BaseButton::is_disabled);
+ ObjectTypeDB::bind_method(_MD("set_click_on_press","enable"),&BaseButton::set_click_on_press);
+ ObjectTypeDB::bind_method(_MD("get_click_on_press"),&BaseButton::get_click_on_press);
+
+ ADD_SIGNAL( MethodInfo("pressed" ) );
+ ADD_SIGNAL( MethodInfo("toggled", PropertyInfo( Variant::BOOL,"pressed") ) );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "disabled"), _SCS("set_disabled"), _SCS("is_disabled"));
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "toggle_mode"), _SCS("set_toggle_mode"), _SCS("is_toggle_mode"));
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "click_on_press"), _SCS("set_click_on_press"), _SCS("get_click_on_press"));
+
+}
+
+BaseButton::BaseButton() {
+
+ toggle_mode=false;
+ status.pressed=false;
+ status.press_attempt=false;
+ status.hovering=false;
+ status.pressing_inside=false;
+ status.disabled = false;
+ status.click_on_press=false;
+ status.pressing_button=0;
+ set_focus_mode( FOCUS_ALL );
+ group=NULL;
+
+
+}
+
+BaseButton::~BaseButton()
+{
+}
+
+
diff --git a/scene/gui/base_button.h b/scene/gui/base_button.h
new file mode 100644
index 0000000000..65563ddc03
--- /dev/null
+++ b/scene/gui/base_button.h
@@ -0,0 +1,102 @@
+/*************************************************************************/
+/* base_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef BASE_BUTTON_H
+#define BASE_BUTTON_H
+
+#include "scene/gui/control.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+
+class ButtonGroup;
+
+class BaseButton : public Control {
+
+ OBJ_TYPE( BaseButton, Control );
+
+ bool toggle_mode;
+
+ struct Status {
+
+ bool pressed;
+ bool hovering;
+ bool press_attempt;
+ bool pressing_inside;
+
+ bool disabled;
+ bool click_on_press;
+ int pressing_button;
+
+ } status;
+
+ ButtonGroup *group;
+
+
+protected:
+
+ enum DrawMode {
+ DRAW_NORMAL,
+ DRAW_PRESSED,
+ DRAW_HOVER,
+ DRAW_DISABLED,
+ };
+
+
+ DrawMode get_draw_mode() const;
+
+ virtual void pressed();
+ virtual void toggled(bool p_pressed);
+ static void _bind_methods();
+ virtual void _input_event(InputEvent p_event);
+ void _notification(int p_what);
+
+public:
+
+ /* Signals */
+
+ bool is_pressed() const; ///< return wether button is pressed (toggled in)
+ bool is_pressing() const; ///< return wether button is pressed (toggled in)
+ void set_pressed(bool p_pressed); ///only works in toggle mode
+ void set_toggle_mode(bool p_on);
+ bool is_toggle_mode() const;
+
+ void set_disabled(bool p_disabled);
+ bool is_disabled() const;
+
+ void set_click_on_press(bool p_click_on_press);
+ bool get_click_on_press() const;
+
+
+ BaseButton();
+ ~BaseButton();
+
+};
+
+#endif
diff --git a/scene/gui/box_container.cpp b/scene/gui/box_container.cpp
new file mode 100644
index 0000000000..216c6d7122
--- /dev/null
+++ b/scene/gui/box_container.cpp
@@ -0,0 +1,290 @@
+/*************************************************************************/
+/* box_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "box_container.h"
+#include "margin_container.h"
+#include "label.h"
+
+struct _MinSizeCache {
+
+ int min_size;
+ bool will_stretch;
+ int final_size;
+};
+
+void BoxContainer::_resort() {
+
+ /** First pass, determine minimum size AND amount of stretchable elements */
+
+
+ Size2i new_size=get_size();;
+
+ int sep=get_constant("separation",vertical?"VBoxContainer":"HBoxContainer");
+
+ bool first=true;
+ int children_count=0;
+ int stretch_min=0;
+ int stretch_avail=0;
+ float stretch_ratio_total=0;
+ Map<Control*,_MinSizeCache> min_size_cache;
+
+ for(int i=0;i<get_child_count();i++) {
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ Size2i size=c->get_combined_minimum_size();
+ _MinSizeCache msc;
+
+ if (vertical) { /* VERTICAL */
+ stretch_min+=size.height;
+ msc.min_size=size.height;
+ msc.will_stretch=c->get_v_size_flags() & SIZE_EXPAND;
+
+ } else { /* HORIZONTAL */
+ stretch_min+=size.width;
+ msc.min_size=size.width;
+ msc.will_stretch=c->get_h_size_flags() & SIZE_EXPAND;
+ }
+
+ if (msc.will_stretch) {
+ stretch_avail+=msc.min_size;
+ stretch_ratio_total+=c->get_stretch_ratio();
+ }
+ msc.final_size=msc.min_size;
+ min_size_cache[c]=msc;
+ children_count++;
+ }
+
+ if (children_count==0)
+ return;
+
+ int stretch_max = (vertical? new_size.height : new_size.width ) - (children_count-1) * sep;
+ int stretch_diff = stretch_max - stretch_min;
+ if (stretch_diff<0) {
+ //avoid negative stretch space
+ stretch_max=stretch_min;
+ stretch_diff=0;
+ }
+
+ stretch_avail+=stretch_diff; //available stretch space.
+ /** Second, pass sucessively to discard elements that can't be stretched, this will run while stretchable
+ elements exist */
+
+
+ while(stretch_ratio_total>0) { // first of all, dont even be here if no stretchable objects exist
+
+ bool refit_successful=true; //assume refit-test will go well
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ ERR_FAIL_COND(!min_size_cache.has(c));
+ _MinSizeCache &msc=min_size_cache[c];
+
+ if (msc.will_stretch) { //wants to stretch
+ //let's see if it can really stretch
+
+ int final_pixel_size=stretch_avail * c->get_stretch_ratio() / stretch_ratio_total;
+ if (final_pixel_size<msc.min_size) {
+ //if available stretching area is too small for widget,
+ //then remove it from stretching area
+ msc.will_stretch=false;
+ stretch_ratio_total-=c->get_stretch_ratio();
+ refit_successful=false;
+ stretch_avail-=msc.min_size;
+ msc.final_size=msc.min_size;
+ break;
+ } else {
+ msc.final_size=final_pixel_size;
+ }
+ }
+ }
+
+ if (refit_successful) //uf refit went well, break
+ break;
+
+ }
+
+
+ /** Final pass, draw and stretch elements **/
+
+
+ int ofs=0;
+
+ first=true;
+ int idx=0;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ _MinSizeCache &msc=min_size_cache[c];
+
+
+ if (first)
+ first=false;
+ else
+ ofs+=sep;
+
+ int from=ofs;
+ int to=ofs+msc.final_size;
+
+
+ if (msc.will_stretch && idx==children_count-1) {
+ //adjust so the last one always fits perfect
+ //compensating for numerical imprecision
+
+ to=vertical?new_size.height:new_size.width;
+
+ }
+
+ int size=to-from;
+
+ Rect2 rect;
+
+ if (vertical) {
+
+ rect=Rect2(0,from,new_size.width,size);
+ } else {
+
+ rect=Rect2(from,0,size,new_size.height);
+
+ }
+
+ fit_child_in_rect(c,rect);
+
+ ofs=to;
+ idx++;
+ }
+
+}
+
+Size2 BoxContainer::get_minimum_size() const {
+
+
+ /* Calculate MINIMUM SIZE */
+
+ Size2i minimum;
+ int sep=get_constant("separation",vertical?"VBoxContainer":"HBoxContainer");
+
+ bool first=true;
+
+ for(int i=0;i<get_child_count();i++) {
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ if (c->is_hidden()) {
+ continue;
+ }
+
+ Size2i size=c->get_combined_minimum_size();
+
+ if (vertical) { /* VERTICAL */
+
+ if ( size.width > minimum.width ) {
+ minimum.width=size.width;
+ }
+
+ minimum.height+=size.height+(first?0:sep);
+
+ } else { /* HORIZONTAL */
+
+ if ( size.height > minimum.height ) {
+ minimum.height=size.height;
+ }
+
+ minimum.width+=size.width+(first?0:sep);
+
+ }
+
+ first=false;
+ }
+
+ return minimum;
+}
+
+void BoxContainer::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_SORT_CHILDREN: {
+
+ _resort();
+ } break;
+ }
+}
+
+void BoxContainer::add_spacer(bool p_begin) {
+
+ Control *c = memnew( Control );
+ if (vertical)
+ c->set_v_size_flags(SIZE_EXPAND_FILL);
+ else
+ c->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ add_child(c);
+ if (p_begin)
+ move_child(c,0);
+}
+
+BoxContainer::BoxContainer(bool p_vertical) {
+
+ vertical=p_vertical;
+// set_ignore_mouse(true);
+ set_stop_mouse(false);
+}
+
+
+MarginContainer* VBoxContainer::add_margin_child(const String& p_label,Control *p_control,bool p_expand) {
+
+ Label *l = memnew( Label );
+ l->set_text(p_label);
+ add_child(l);
+ MarginContainer *mc = memnew( MarginContainer );
+ mc->add_child(p_control);
+ add_child(mc);
+ if (p_expand)
+ mc->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ return mc;
+}
diff --git a/scene/gui/box_container.h b/scene/gui/box_container.h
new file mode 100644
index 0000000000..fb305f0423
--- /dev/null
+++ b/scene/gui/box_container.h
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* box_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef BOX_CONTAINER_H
+#define BOX_CONTAINER_H
+
+#include "scene/gui/container.h"
+
+class BoxContainer : public Container {
+
+ OBJ_TYPE(BoxContainer,Container);
+
+ bool vertical;
+
+ void _resort();
+protected:
+
+ void _notification(int p_what);
+public:
+
+ void add_spacer(bool p_begin=false);
+
+ virtual Size2 get_minimum_size() const;
+
+ BoxContainer(bool p_vertical=false);
+};
+
+
+class HBoxContainer : public BoxContainer {
+
+ OBJ_TYPE(HBoxContainer,BoxContainer);
+
+public:
+
+ HBoxContainer() : BoxContainer(false) {}
+};
+
+
+class MarginContainer;
+class VBoxContainer : public BoxContainer {
+
+ OBJ_TYPE(VBoxContainer,BoxContainer);
+
+public:
+
+ MarginContainer* add_margin_child(const String& p_label,Control *p_control,bool p_expand=false);
+
+ VBoxContainer() : BoxContainer(true) {}
+};
+
+#endif // BOX_CONTAINER_H
diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp
new file mode 100644
index 0000000000..0532ab22da
--- /dev/null
+++ b/scene/gui/button.cpp
@@ -0,0 +1,245 @@
+/*************************************************************************/
+/* button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "button.h"
+#include "servers/visual_server.h"
+#include "print_string.h"
+#include "translation.h"
+
+
+Size2 Button::get_minimum_size() const {
+
+ Size2 minsize=get_font("font")->get_string_size( text );
+ if (clip_text)
+ minsize.width=0;
+
+ Ref<Texture> _icon;
+ if (icon.is_null() && has_icon("icon"))
+ _icon=Control::get_icon("icon");
+ else
+ _icon=icon;
+
+ if (!_icon.is_null()) {
+
+ minsize.height=MAX( minsize.height, _icon->get_height() );
+ minsize.width+=_icon->get_width();
+ if (text!="")
+ minsize.width+=get_constant("hseparation");
+ }
+
+ return get_stylebox("normal" )->get_minimum_size() + minsize;
+
+}
+
+
+void Button::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+ Size2 size=get_size();
+ Color color;
+
+ //print_line(get_text()+": "+itos(is_flat())+" hover "+itos(get_draw_mode()));
+
+ switch( get_draw_mode() ) {
+
+ case DRAW_NORMAL: {
+
+ if (!flat)
+ get_stylebox("normal" )->draw( ci, Rect2(Point2(0,0), size) );
+ color=get_color("font_color");
+ } break;
+ case DRAW_PRESSED: {
+
+ get_stylebox("pressed" )->draw( ci, Rect2(Point2(0,0), size) );
+ if (has_color("font_color_pressed"))
+ color=get_color("font_color_pressed");
+ else
+ color=get_color("font_color");
+
+ } break;
+ case DRAW_HOVER: {
+
+ get_stylebox("hover" )->draw( ci, Rect2(Point2(0,0), size) );
+ color=get_color("font_color_hover");
+
+ } break;
+ case DRAW_DISABLED: {
+
+ get_stylebox("disabled" )->draw( ci, Rect2(Point2(0,0), size) );
+ color=get_color("font_color_disabled");
+
+ } break;
+ }
+ Ref<StyleBox> style = get_stylebox("normal" );
+ Ref<Font> font=get_font("font");
+ Ref<Texture> _icon;
+ if (icon.is_null() && has_icon("icon"))
+ _icon=Control::get_icon("icon");
+ else
+ _icon=icon;
+
+ Point2 icon_ofs = (!_icon.is_null())?Point2( _icon->get_width() + get_constant("hseparation"), 0):Point2();
+ int text_clip=size.width - style->get_minimum_size().width - icon_ofs.width;
+ Point2 text_ofs = (size - style->get_minimum_size() - icon_ofs - font->get_string_size( text ) )/2.0;
+
+ switch(align) {
+ case ALIGN_LEFT: {
+ text_ofs.x = style->get_margin(MARGIN_LEFT) + icon_ofs.x;
+ text_ofs.y+=style->get_offset().y;
+ } break;
+ case ALIGN_CENTER: {
+ text_ofs+=icon_ofs;
+ text_ofs+=style->get_offset();
+ } break;
+ case ALIGN_RIGHT: {
+ text_ofs.x=size.x - style->get_margin(MARGIN_RIGHT) - font->get_string_size( text ).x;
+ text_ofs.y+=style->get_offset().y;
+ } break;
+ }
+
+
+ text_ofs.y+=font->get_ascent();
+ font->draw( ci, text_ofs.floor(), text, color,clip_text?text_clip:-1);
+ if (!_icon.is_null()) {
+
+ _icon->draw(ci,Point2(style->get_offset().x, Math::floor( (size.height-_icon->get_height())/2.0 ) ),is_disabled()?Color(1,1,1,0.4):Color(1,1,1) );
+ }
+
+ if (has_focus()) {
+
+ Ref<StyleBox> style = get_stylebox("focus");
+ style->draw(ci,Rect2(Point2(),size));
+ }
+
+ }
+}
+
+void Button::set_text(const String& p_text) {
+
+ if (text==p_text)
+ return;
+ text=XL_MESSAGE(p_text);
+ update();
+ _change_notify("text");
+ minimum_size_changed();
+}
+String Button::get_text() const {
+
+ return text;
+}
+
+
+void Button::set_icon(const Ref<Texture>& p_icon) {
+
+ if (icon==p_icon)
+ return;
+ icon=p_icon;
+ update();
+ _change_notify("icon");
+ minimum_size_changed();
+}
+
+Ref<Texture> Button::get_icon() const {
+
+ return icon;
+}
+
+void Button::set_flat(bool p_flat) {
+
+ flat=p_flat;
+ update();
+ _change_notify("flat");
+}
+
+bool Button::is_flat() const {
+
+ return flat;
+}
+
+void Button::set_clip_text(bool p_clip_text) {
+
+ clip_text=p_clip_text;
+ update();
+ minimum_size_changed();
+}
+
+bool Button::get_clip_text() const {
+
+ return clip_text;
+}
+
+void Button::set_text_align(TextAlign p_align) {
+
+ align=p_align;
+ update();
+}
+
+Button::TextAlign Button::get_text_align() const {
+
+ return align;
+}
+
+
+void Button::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_text","text"),&Button::set_text);
+ ObjectTypeDB::bind_method(_MD("get_text"),&Button::get_text);
+ ObjectTypeDB::bind_method(_MD("set_button_icon","texture:Texture"),&Button::set_icon);
+ ObjectTypeDB::bind_method(_MD("get_button_icon:Texture"),&Button::get_icon);
+ ObjectTypeDB::bind_method(_MD("set_flat","enabled"),&Button::set_flat);
+ ObjectTypeDB::bind_method(_MD("set_clip_text","enabled"),&Button::set_clip_text);
+ ObjectTypeDB::bind_method(_MD("get_clip_text"),&Button::get_clip_text);
+ ObjectTypeDB::bind_method(_MD("set_text_align","align"),&Button::set_text_align);
+ ObjectTypeDB::bind_method(_MD("get_text_align"),&Button::get_text_align);
+ ObjectTypeDB::bind_method(_MD("is_flat"),&Button::is_flat);
+
+ ADD_PROPERTY( PropertyInfo( Variant::STRING, "text", PROPERTY_HINT_NONE,"",PROPERTY_USAGE_DEFAULT_INTL ), _SCS("set_text"),_SCS("get_text") );
+ ADD_PROPERTY( PropertyInfo( Variant::OBJECT, "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture" ), _SCS("set_button_icon"),_SCS("get_button_icon") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "flat" ), _SCS("set_flat"),_SCS("is_flat") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "clip_text" ), _SCS("set_clip_text"),_SCS("get_clip_text") );
+ ADD_PROPERTY( PropertyInfo( Variant::INT, "align",PROPERTY_HINT_ENUM,"Left,Center,Right" ), _SCS("set_text_align"),_SCS("get_text_align") );
+
+}
+
+Button::Button(const String &p_text) {
+
+ flat=false;
+ clip_text=false;
+ set_stop_mouse(true);
+ set_text(p_text);
+ align=ALIGN_CENTER;
+}
+
+
+Button::~Button()
+{
+}
+
+
diff --git a/scene/gui/button.h b/scene/gui/button.h
new file mode 100644
index 0000000000..cf79e23579
--- /dev/null
+++ b/scene/gui/button.h
@@ -0,0 +1,88 @@
+/*************************************************************************/
+/* button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef BUTTON_H
+#define BUTTON_H
+
+#include "scene/gui/base_button.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class Button : public BaseButton {
+
+ OBJ_TYPE( Button, BaseButton );
+public:
+
+
+ enum TextAlign {
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT
+ };
+
+private:
+
+ bool flat;
+ String text;
+ Ref<Texture> icon;
+ bool clip_text;
+ TextAlign align;
+
+
+
+protected:
+
+ virtual Size2 get_minimum_size() const;
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+//
+ void set_text(const String& p_text);
+ String get_text() const;
+
+ void set_icon(const Ref<Texture>& p_icon);
+ Ref<Texture> get_icon() const;
+
+ void set_flat(bool p_flat);
+ bool is_flat() const;
+
+ void set_clip_text(bool p_clip_text);
+ bool get_clip_text() const;
+
+ void set_text_align(TextAlign p_align);
+ TextAlign get_text_align() const;
+
+ Button(const String& p_text=String());
+ ~Button();
+
+};
+
+
+VARIANT_ENUM_CAST(Button::TextAlign);
+
+#endif
diff --git a/scene/gui/button_array.cpp b/scene/gui/button_array.cpp
new file mode 100644
index 0000000000..e3a2b7b290
--- /dev/null
+++ b/scene/gui/button_array.cpp
@@ -0,0 +1,504 @@
+/*************************************************************************/
+/* button_array.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "button_array.h"
+
+
+bool ButtonArray::_set(const StringName& p_name, const Variant& p_value) {
+
+ String n=String(p_name);
+ if (n.begins_with("button/")) {
+
+ String what = n.get_slice("/",1);
+ if (what=="count") {
+ int new_size=p_value;
+ if (new_size>0 && buttons.size()==0) {
+ selected=0;
+ }
+
+ if (new_size < buttons.size()) {
+ if (selected>=new_size)
+ selected=new_size-1;
+ }
+ buttons.resize(new_size);
+ _change_notify();
+ minimum_size_changed();
+ } else if (what=="align") {
+ set_align(Align(p_value.operator int()));
+ } else if (what=="selected") {
+ set_selected(p_value);
+ } else if (what == "min_button_size") {
+ min_button_size = p_value;
+ } else {
+ int idx=what.to_int();
+ ERR_FAIL_INDEX_V(idx,buttons.size(),false);
+ String f = n.get_slice("/",2);
+ if (f=="text")
+ buttons[idx].text=p_value;
+ else if (f=="icon")
+ buttons[idx].icon=p_value;
+ else
+ return false;
+
+ }
+
+ update();
+ return true;
+ }
+
+ return false;
+
+}
+
+bool ButtonArray::_get(const StringName& p_name,Variant &r_ret) const {
+
+ String n=String(p_name);
+ if (n.begins_with("button/")) {
+
+ String what = n.get_slice("/",1);
+ if (what=="count") {
+ r_ret=buttons.size();
+ } else if (what=="align") {
+ r_ret=get_align();
+ } else if (what=="selected") {
+ r_ret=get_selected();
+ } else if (what == "min_button_size"){
+ r_ret = min_button_size;
+ } else {
+ int idx=what.to_int();
+ ERR_FAIL_INDEX_V(idx,buttons.size(),false);
+ String f = n.get_slice("/",2);
+ if (f=="text")
+ r_ret=buttons[idx].text;
+ else if (f=="icon")
+ r_ret=buttons[idx].icon;
+ else
+ return false;
+
+ }
+
+ return true;
+ }
+
+ return false;
+}
+void ButtonArray::_get_property_list( List<PropertyInfo> *p_list) const {
+
+ p_list->push_back( PropertyInfo( Variant::INT, "button/count",PROPERTY_HINT_RANGE,"0,512,1"));
+ p_list->push_back( PropertyInfo( Variant::INT, "button/min_button_size",PROPERTY_HINT_RANGE,"0,1024,1"));
+ p_list->push_back( PropertyInfo( Variant::INT, "button/align",PROPERTY_HINT_ENUM,"Begin,Center,End,Fill,Expand"));
+ for(int i=0;i<buttons.size();i++) {
+ String base="button/"+itos(i)+"/";
+ p_list->push_back( PropertyInfo( Variant::STRING, base+"text"));
+ p_list->push_back( PropertyInfo( Variant::OBJECT, base+"icon",PROPERTY_HINT_RESOURCE_TYPE,"Texture"));
+ }
+ if (buttons.size()>0) {
+ p_list->push_back( PropertyInfo( Variant::INT, "button/selected",PROPERTY_HINT_RANGE,"0,"+itos(buttons.size()-1)+",1"));
+ }
+
+}
+
+
+Size2 ButtonArray::get_minimum_size() const {
+
+ Ref<StyleBox> style_normal = get_stylebox("normal");
+ Ref<StyleBox> style_selected = get_stylebox("selected");
+ Ref<Font> font_normal = get_font("font");
+ Ref<Font> font_selected = get_font("font_selected");
+ int icon_sep = get_constant("icon_separator");
+ int button_sep = get_constant("button_separator");
+
+ Size2 minsize;
+
+ for(int i=0;i<buttons.size();i++) {
+
+ Ref<StyleBox> sb = i==selected ? style_selected : style_normal;
+ Ref<Font> f = i==selected ? font_selected : font_normal;
+
+ Size2 ms;
+ ms = f->get_string_size(buttons[i].text);
+ if (buttons[i].icon.is_valid()) {
+
+ Size2 bs = buttons[i].icon->get_size();
+ ms.height = MAX(ms.height,bs.height);
+ ms.width+=bs.width+icon_sep;
+ }
+
+ ms+=sb->get_minimum_size();
+
+ buttons[i]._ms_cache=ms[orientation];
+
+ minsize[orientation]+=ms[orientation];
+ if (i>0)
+ minsize[orientation]+=button_sep;
+ minsize[!orientation] = MAX(minsize[!orientation],ms[!orientation]);
+
+ }
+
+ return minsize;
+
+
+}
+
+void ButtonArray::_notification(int p_what) {
+
+ switch(p_what) {
+ case NOTIFICATION_READY:{
+ MethodInfo mi;
+ mi.name="mouse_sub_enter";
+
+ add_user_signal(mi);
+
+ }break;
+ case NOTIFICATION_DRAW: {
+
+ Size2 size=get_size();
+ Size2 minsize=get_combined_minimum_size();
+ Ref<StyleBox> style_normal = get_stylebox("normal");
+ Ref<StyleBox> style_selected = get_stylebox("selected");
+ Ref<StyleBox> style_focus = get_stylebox("focus");
+ Ref<StyleBox> style_hover = get_stylebox("hover");
+ Ref<Font> font_normal = get_font("font");
+ Ref<Font> font_selected = get_font("font_selected");
+ int icon_sep = get_constant("icon_separator");
+ int button_sep = get_constant("button_separator");
+ Color color_normal = get_color("font_color");
+ Color color_selected = get_color("font_color_selected");
+
+ int sep=button_sep;
+ int ofs=0;
+ int expand=0;
+
+ switch(align) {
+ case ALIGN_BEGIN: {
+
+ ofs=0;
+ } break;
+ case ALIGN_CENTER: {
+
+ ofs=Math::floor((size[orientation] - minsize[orientation])/2);
+ } break;
+ case ALIGN_END: {
+
+ ofs=Math::floor((size[orientation] - minsize[orientation]));
+ } break;
+ case ALIGN_FILL: {
+
+ if (buttons.size()>1)
+ sep+=Math::floor((size[orientation]- minsize[orientation])/(buttons.size()-1.0));
+ ofs=0;
+ } break;
+ case ALIGN_EXPAND_FILL: {
+
+ ofs=0;
+ expand=size[orientation] - minsize[orientation];
+ } break;
+
+
+
+ }
+
+ int op_size = orientation==VERTICAL ? size.width : size.height;
+
+
+ for(int i=0;i<buttons.size();i++) {
+
+ int ms = buttons[i]._ms_cache;
+ int s=ms;
+ if (expand>0) {
+ s+=expand/buttons.size();
+ }
+ if(min_button_size != -1 && s < min_button_size){
+ s = min_button_size;
+ }
+
+ Rect2 r;
+ r.pos[orientation]=ofs;
+ r.pos[!orientation]=0;
+ r.size[orientation]=s;
+ r.size[!orientation]=op_size;
+
+ Ref<Font> f;
+ Color c;
+ if (i==selected) {
+ draw_style_box(style_selected,r);
+ f=font_selected;
+ c=color_selected;
+ if (has_focus())
+ draw_style_box(style_focus,r);
+ } else {
+ if (hover==i)
+ draw_style_box(style_hover,r);
+ else
+ draw_style_box(style_normal,r);
+ f=font_normal;
+ c=color_normal;
+ }
+
+ Size2 ssize = f->get_string_size(buttons[i].text);
+ if (buttons[i].icon.is_valid()) {
+
+ ssize.x+=buttons[i].icon->get_width();
+ }
+ Point2 text_ofs=((r.size-ssize)/2.0+Point2(0,f->get_ascent())).floor();
+ if (buttons[i].icon.is_valid()) {
+
+ draw_texture(buttons[i].icon,r.pos+Point2(text_ofs.x,Math::floor((r.size.height-buttons[i].icon->get_height())/2.0)));
+ text_ofs.x+=buttons[i].icon->get_width()+icon_sep;
+
+ }
+ draw_string(f,text_ofs+r.pos,buttons[i].text,c);
+ buttons[i]._pos_cache=ofs;
+ buttons[i]._size_cache=s;
+
+ ofs+=s;
+ ofs+=sep;
+ }
+
+ } break;
+ }
+}
+
+
+void ButtonArray::_input_event(const InputEvent& p_event) {
+
+ if (
+ ( (orientation==HORIZONTAL && p_event.is_action("ui_left") ) ||
+ (orientation==VERTICAL && p_event.is_action("ui_up") ) )
+ && p_event.is_pressed() && selected>0) {
+ set_selected(selected-1);
+ accept_event();
+ emit_signal("button_selected",selected);
+ return;
+
+ }
+
+ if (
+ ( (orientation==HORIZONTAL && p_event.is_action("ui_right") ) ||
+ (orientation==VERTICAL && p_event.is_action("ui_down") ) )
+ && p_event.is_pressed() && selected<(buttons.size()-1)) {
+ set_selected(selected+1);
+ accept_event();
+ emit_signal("button_selected",selected);
+ return;
+
+ }
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON && p_event.mouse_button.pressed && p_event.mouse_button.button_index==BUTTON_LEFT) {
+
+ int ofs = orientation==HORIZONTAL ? p_event.mouse_button.x: p_event.mouse_button.y;
+
+ for(int i=0;i<buttons.size();i++) {
+
+ if (ofs>=buttons[i]._pos_cache && ofs<buttons[i]._pos_cache+buttons[i]._size_cache) {
+
+ set_selected(i);
+ emit_signal("button_selected",i);
+ return;
+ }
+
+ }
+ }
+
+ if (p_event.type==InputEvent::MOUSE_MOTION) {
+
+ int ofs = orientation==HORIZONTAL ? p_event.mouse_motion.x: p_event.mouse_motion.y;
+ int new_hover=-1;
+ for(int i=0;i<buttons.size();i++) {
+
+ if (ofs>=buttons[i]._pos_cache && ofs<buttons[i]._pos_cache+buttons[i]._size_cache) {
+
+ new_hover=i;
+ break;
+ }
+
+ }
+
+ if (new_hover!=hover) {
+ hover=new_hover;
+ emit_signal("mouse_sub_enter");
+ update();
+ }
+ }
+
+
+}
+
+void ButtonArray::set_align(Align p_align) {
+
+ align=p_align;
+ update();
+
+}
+
+ButtonArray::Align ButtonArray::get_align() const {
+
+ return align;
+}
+
+
+void ButtonArray::add_button(const String& p_button) {
+
+ Button button;
+ button.text=p_button;
+ buttons.push_back(button);
+ update();
+
+ if (selected==-1)
+ selected=0;
+
+ minimum_size_changed();
+}
+
+void ButtonArray::add_icon_button(const Ref<Texture>& p_icon,const String& p_button) {
+
+ Button button;
+ button.text=p_button;
+ button.icon=p_icon;
+ buttons.push_back(button);
+ if (selected==-1)
+ selected=0;
+
+ update();
+
+}
+
+void ButtonArray::set_button_text(int p_button, const String& p_text) {
+
+ ERR_FAIL_INDEX(p_button,buttons.size());
+ buttons[p_button].text=p_text;
+ update();
+ minimum_size_changed();
+
+}
+void ButtonArray::set_button_icon(int p_button, const Ref<Texture>& p_icon) {
+
+ ERR_FAIL_INDEX(p_button,buttons.size());
+ buttons[p_button].icon=p_icon;
+ update();
+ minimum_size_changed();
+}
+String ButtonArray::get_button_text(int p_button) const {
+
+ ERR_FAIL_INDEX_V(p_button,buttons.size(),"");
+ return buttons[p_button].text;
+}
+Ref<Texture> ButtonArray::get_button_icon(int p_button) const {
+
+ ERR_FAIL_INDEX_V(p_button,buttons.size(),Ref<Texture>());
+ return buttons[p_button].icon;
+
+}
+
+int ButtonArray::get_selected() const {
+
+ return selected;
+}
+
+int ButtonArray::get_hovered() const {
+
+ return hover;
+}
+
+void ButtonArray::set_selected(int p_selected) {
+
+ ERR_FAIL_INDEX(p_selected,buttons.size());
+ selected=p_selected;
+ update();
+
+}
+
+void ButtonArray::erase_button(int p_button) {
+
+ ERR_FAIL_INDEX(p_button,buttons.size());
+ buttons.remove(p_button);
+ if (p_button>=selected)
+ selected--;
+ if (selected<0)
+ selected=0;
+ if (selected>=buttons.size())
+ selected=buttons.size()-1;
+
+ update();
+}
+
+void ButtonArray::clear(){
+
+ buttons.clear();
+ selected=-1;
+ update();
+}
+
+int ButtonArray::get_button_count() const {
+
+ return buttons.size();
+}
+
+void ButtonArray::get_translatable_strings(List<String> *p_strings) const {
+
+
+ for(int i=0;i<buttons.size();i++)
+ p_strings->push_back(buttons[i].text);
+}
+
+
+void ButtonArray::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("add_button","text"),&ButtonArray::add_button);
+ ObjectTypeDB::bind_method(_MD("add_icon_button","icon","text"),&ButtonArray::add_icon_button,DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("set_button_text","button","text"),&ButtonArray::set_button_text);
+ ObjectTypeDB::bind_method(_MD("set_button_icon","button","icon"),&ButtonArray::set_button_icon);
+ ObjectTypeDB::bind_method(_MD("get_button_text","button"),&ButtonArray::get_button_text);
+ ObjectTypeDB::bind_method(_MD("get_button_icon","button"),&ButtonArray::get_button_icon);
+ ObjectTypeDB::bind_method(_MD("get_button_count"),&ButtonArray::get_button_count);
+ ObjectTypeDB::bind_method(_MD("get_selected"),&ButtonArray::get_selected);
+ ObjectTypeDB::bind_method(_MD("get_hovered"),&ButtonArray::get_hovered);
+ ObjectTypeDB::bind_method(_MD("set_selected","button"),&ButtonArray::set_selected);
+ ObjectTypeDB::bind_method(_MD("erase_button","button"),&ButtonArray::erase_button);
+ ObjectTypeDB::bind_method(_MD("clear"),&ButtonArray::clear);
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&ButtonArray::_input_event);
+
+ BIND_CONSTANT( ALIGN_BEGIN );
+ BIND_CONSTANT( ALIGN_CENTER );
+ BIND_CONSTANT( ALIGN_END );
+ BIND_CONSTANT( ALIGN_FILL );
+ BIND_CONSTANT( ALIGN_EXPAND_FILL );
+
+ ADD_SIGNAL( MethodInfo("button_selected",PropertyInfo(Variant::INT,"button")));
+
+}
+
+ButtonArray::ButtonArray(Orientation p_orientation) {
+
+ orientation=p_orientation;
+ selected=-1;
+ set_focus_mode(FOCUS_ALL);
+ hover=-1;
+ min_button_size = -1;
+}
diff --git a/scene/gui/button_array.h b/scene/gui/button_array.h
new file mode 100644
index 0000000000..f536040039
--- /dev/null
+++ b/scene/gui/button_array.h
@@ -0,0 +1,123 @@
+/*************************************************************************/
+/* button_array.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef BUTTON_ARRAY_H
+#define BUTTON_ARRAY_H
+
+#include "scene/gui/control.h"
+
+class ButtonArray : public Control {
+
+ OBJ_TYPE(ButtonArray, Control);
+public:
+ enum Align {
+ ALIGN_BEGIN,
+ ALIGN_CENTER,
+ ALIGN_END,
+ ALIGN_FILL,
+ ALIGN_EXPAND_FILL
+ };
+private:
+
+ Orientation orientation;
+ Align align;
+
+ struct Button {
+
+ String text;
+ Ref<Texture> icon;
+ mutable int _ms_cache;
+ mutable int _pos_cache;
+ mutable int _size_cache;
+ };
+
+ int selected;
+ int hover;
+ double min_button_size;
+
+ Vector<Button> buttons;
+protected:
+
+ bool _set(const StringName& p_name, const Variant& p_value);
+ bool _get(const StringName& p_name,Variant &r_ret) const;
+ void _get_property_list( List<PropertyInfo> *p_list) const;
+
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+
+ void _input_event(const InputEvent& p_event);
+
+
+ void set_align(Align p_align);
+ Align get_align() const;
+
+ void add_button(const String& p_button);
+ void add_icon_button(const Ref<Texture>& p_icon,const String& p_button="");
+
+ void set_button_text(int p_button, const String& p_text);
+ void set_button_icon(int p_button, const Ref<Texture>& p_icon);
+
+
+ String get_button_text(int p_button) const;
+ Ref<Texture> get_button_icon(int p_button) const;
+
+ int get_selected() const;
+ int get_hovered() const;
+ void set_selected(int p_selected);
+
+ int get_button_count() const;
+
+ void erase_button(int p_button);
+ void clear();
+
+ virtual Size2 get_minimum_size() const;
+
+ virtual void get_translatable_strings(List<String> *p_strings) const;
+
+
+ ButtonArray(Orientation p_orientation=HORIZONTAL);
+};
+
+class HButtonArray : public ButtonArray {
+ OBJ_TYPE(HButtonArray,ButtonArray);
+public:
+
+ HButtonArray() : ButtonArray(HORIZONTAL) {};
+};
+
+class VButtonArray : public ButtonArray {
+ OBJ_TYPE(VButtonArray,ButtonArray);
+public:
+
+ VButtonArray() : ButtonArray(VERTICAL) {};
+};
+
+
+#endif // BUTTON_ARRAY_H
diff --git a/scene/gui/button_group.cpp b/scene/gui/button_group.cpp
new file mode 100644
index 0000000000..65cfd03505
--- /dev/null
+++ b/scene/gui/button_group.cpp
@@ -0,0 +1,157 @@
+/*************************************************************************/
+/* button_group.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "button_group.h"
+#include "base_button.h"
+
+void ButtonGroup::_add_button(BaseButton *p_button) {
+
+ buttons.insert(p_button);
+ p_button->set_toggle_mode(true);
+ p_button->set_click_on_press(true);
+ p_button->connect("pressed",this,"_pressed",make_binds(p_button));
+
+}
+
+void ButtonGroup::_remove_button(BaseButton *p_button){
+
+ buttons.erase(p_button);
+ p_button->disconnect("pressed",this,"_pressed");
+
+}
+
+void ButtonGroup::set_pressed_button(BaseButton *p_button) {
+
+ _pressed(p_button);
+}
+
+void ButtonGroup::_pressed(Object *p_button) {
+
+ ERR_FAIL_NULL(p_button);
+ BaseButton *b=p_button->cast_to<BaseButton>();
+ ERR_FAIL_COND(!b);
+
+ for(Set<BaseButton*>::Element *E=buttons.front();E;E=E->next()) {
+
+ BaseButton *bb=E->get();
+ bb->set_pressed( b==bb );
+ }
+}
+
+Array ButtonGroup::_get_button_list() const {
+
+ List<BaseButton*> b;
+ get_button_list(&b);
+ Array arr;
+ arr.resize(b.size());
+
+ int idx=0;
+
+ for(List<BaseButton*>::Element *E=b.front();E;E=E->next(),idx++) {
+
+ arr[idx]=E->get();
+ }
+
+ return arr;
+}
+
+void ButtonGroup::get_button_list(List<BaseButton*> *p_buttons) const {
+
+ for(Set<BaseButton*>::Element *E=buttons.front();E;E=E->next()) {
+
+ p_buttons->push_back(E->get());
+ }
+}
+
+BaseButton *ButtonGroup::get_pressed_button() const {
+
+ for(Set<BaseButton*>::Element *E=buttons.front();E;E=E->next()) {
+
+ if (E->get()->is_pressed())
+ return E->get();
+ }
+
+ return NULL;
+}
+
+BaseButton *ButtonGroup::get_focused_button() const{
+
+ for(Set<BaseButton*>::Element *E=buttons.front();E;E=E->next()) {
+
+ if (E->get()->has_focus())
+ return E->get();
+ }
+
+ return NULL;
+
+}
+
+int ButtonGroup::get_pressed_button_index() const {
+ //in tree order, this is bizarre
+
+ ERR_FAIL_COND_V(!is_inside_scene(),0);
+
+ BaseButton *pressed = get_pressed_button();
+ if (!pressed)
+ return -1;
+
+ List<BaseButton*> blist;
+ for(Set<BaseButton*>::Element *E=buttons.front();E;E=E->next()) {
+
+ blist.push_back(E->get());
+
+ }
+
+ blist.sort_custom<Node::Comparator>();
+
+ int idx=0;
+ for(List<BaseButton*>::Element *E=blist.front();E;E=E->next()) {
+
+ if (E->get()==pressed)
+ return idx;
+
+ idx++;
+ }
+
+ return -1;
+}
+
+void ButtonGroup::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("get_pressed_button:BaseButton"),&ButtonGroup::get_pressed_button);
+ ObjectTypeDB::bind_method(_MD("get_pressed_button_index"),&ButtonGroup::get_pressed_button_index);
+ ObjectTypeDB::bind_method(_MD("get_focused_button:BaseButton"),&ButtonGroup::get_focused_button);
+ ObjectTypeDB::bind_method(_MD("get_button_list"),&ButtonGroup::_get_button_list);
+ ObjectTypeDB::bind_method(_MD("_pressed"),&ButtonGroup::_pressed);
+ ObjectTypeDB::bind_method(_MD("set_pressed_button","button:BaseButton"),&ButtonGroup::_pressed);
+
+}
+
+ButtonGroup::ButtonGroup()
+{
+}
diff --git a/scene/gui/button_group.h b/scene/gui/button_group.h
new file mode 100644
index 0000000000..e154f609a8
--- /dev/null
+++ b/scene/gui/button_group.h
@@ -0,0 +1,66 @@
+/*************************************************************************/
+/* button_group.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef BUTTON_GROUP_H
+#define BUTTON_GROUP_H
+
+#include "scene/gui/control.h"
+
+
+class BaseButton;
+
+class ButtonGroup : public Control {
+
+ OBJ_TYPE(ButtonGroup,Control);
+
+
+ Set<BaseButton*> buttons;
+
+
+ Array _get_button_list() const;
+ void _pressed(Object *p_button);
+
+protected:
+friend class BaseButton;
+
+ void _add_button(BaseButton *p_button);
+ void _remove_button(BaseButton *p_button);
+
+ static void _bind_methods();
+public:
+
+ void get_button_list(List<BaseButton*> *p_buttons) const;
+ BaseButton *get_pressed_button() const;
+ BaseButton *get_focused_button() const;
+ void set_pressed_button(BaseButton *p_button);
+ int get_pressed_button_index() const;
+
+ ButtonGroup();
+};
+
+#endif // BUTTON_GROUP_H
diff --git a/scene/gui/center_container.cpp b/scene/gui/center_container.cpp
new file mode 100644
index 0000000000..9cedf02371
--- /dev/null
+++ b/scene/gui/center_container.cpp
@@ -0,0 +1,78 @@
+/*************************************************************************/
+/* center_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "center_container.h"
+
+
+Size2 CenterContainer::get_minimum_size() const {
+
+
+ Size2 ms;
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (c->is_hidden())
+ continue;
+ Size2 minsize = c->get_combined_minimum_size();
+ ms.width = MAX(ms.width , minsize.width);
+ ms.height = MAX(ms.height , minsize.height);
+
+
+ }
+
+ return ms;
+
+}
+
+void CenterContainer::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_SORT_CHILDREN) {
+
+ Size2 size = get_size();
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ Size2 minsize = c->get_combined_minimum_size();
+ Point2 ofs = ((size - minsize)/2.0).floor();
+ fit_child_in_rect(c,Rect2(ofs,minsize));
+
+ }
+ }
+}
+
+CenterContainer::CenterContainer()
+{
+}
diff --git a/scene/gui/center_container.h b/scene/gui/center_container.h
new file mode 100644
index 0000000000..458e2e7251
--- /dev/null
+++ b/scene/gui/center_container.h
@@ -0,0 +1,49 @@
+/*************************************************************************/
+/* center_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef CENTER_CONTAINER_H
+#define CENTER_CONTAINER_H
+
+
+#include "scene/gui/container.h"
+
+class CenterContainer : public Container {
+
+ OBJ_TYPE( CenterContainer, Container );
+
+protected:
+
+ void _notification(int p_what);
+public:
+
+ virtual Size2 get_minimum_size() const;
+
+ CenterContainer();
+};
+
+#endif // CENTER_CONTAINER_H
diff --git a/scene/gui/check_button.cpp b/scene/gui/check_button.cpp
new file mode 100644
index 0000000000..10dca67053
--- /dev/null
+++ b/scene/gui/check_button.cpp
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* check_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "check_button.h"
+
+#include "servers/visual_server.h"
+#include "print_string.h"
+
+
+void CheckButton::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+
+ Ref<Texture> on=Control::get_icon("on");
+ Ref<Texture> off=Control::get_icon("off");
+
+ Vector2 ofs;
+ ofs.x = get_size().width - on->get_width();
+ ofs.y = int((get_size().height - on->get_height())/2);
+
+ if (is_pressed())
+ on->draw(ci,ofs);
+ else
+ off->draw(ci,ofs);
+
+
+ }
+}
+
+
+CheckButton::CheckButton() {
+
+ set_toggle_mode(true);
+ set_text_align(ALIGN_LEFT);
+
+
+}
+
+
+CheckButton::~CheckButton()
+{
+}
+
+
+
diff --git a/scene/gui/check_button.h b/scene/gui/check_button.h
new file mode 100644
index 0000000000..a4ebe5b8af
--- /dev/null
+++ b/scene/gui/check_button.h
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* check_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef CHECK_BUTTON_H
+#define CHECK_BUTTON_H
+
+
+#include "scene/gui/button.h"
+/**
+@author Juan Linietsky <reduzio@gmail.com>
+*/
+class CheckButton : public Button {
+
+ OBJ_TYPE( CheckButton, Button );
+
+
+protected:
+ void _notification(int p_what);
+
+public:
+
+ CheckButton();
+ ~CheckButton();
+
+};
+
+#endif
diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp
new file mode 100644
index 0000000000..6d1f2324fc
--- /dev/null
+++ b/scene/gui/color_picker.cpp
@@ -0,0 +1,384 @@
+/*************************************************************************/
+/* color_picker.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "color_picker.h"
+
+
+
+
+void ColorPicker::_notification(int p_what) {
+
+
+ switch(p_what) {
+ case NOTIFICATION_THEME_CHANGED: {
+
+ _update_controls();
+ } break;
+
+/* case NOTIFICATION_DRAW: {
+
+ int w = get_constant("color_width");
+ int h = ms.height;
+ VisualServer::get_singleton()->canvas_item_add_rect(get_canvas_item(),Rect2(0,0,w,h),color);
+
+ } break;*/
+ }
+}
+
+void ColorPicker::_update_controls() {
+
+
+ int cw = get_constant("color_width");
+
+ if (edit_alpha) {
+ values[3]->show();
+ scroll[3]->show();
+ labels[3]->show();
+ } else {
+ values[3]->hide();
+ scroll[3]->hide();
+ labels[3]->hide();
+ }
+
+
+
+}
+
+
+void ColorPicker::set_color(const Color& p_color) {
+
+ color=p_color;
+ _update_color();
+
+}
+
+void ColorPicker::set_edit_alpha(bool p_show) {
+
+ edit_alpha=p_show;
+ _update_controls();
+ _update_color();
+ color_box->update();
+}
+
+bool ColorPicker::is_editing_alpha() const {
+
+ return edit_alpha;
+}
+
+void ColorPicker::_value_changed(double) {
+
+ if (updating)
+ return;
+
+ switch(mode) {
+
+ case MODE_RGB: {
+
+ for(int i=0;i<4;i++) {
+ color.components[i] = scroll[i]->get_val() / 255.0;
+ }
+
+ } break;
+ case MODE_HSV: {
+
+ color.set_hsv( CLAMP(scroll[0]->get_val()/255,0,0.99), scroll[1]->get_val()/255, scroll[2]->get_val()/255 );
+ color.a=scroll[3]->get_val()/255.0;
+
+ } break;
+ case MODE_RAW: {
+
+ for(int i=0;i<4;i++) {
+ color.components[i] = scroll[i]->get_val();
+ }
+
+ } break;
+
+ }
+
+
+ html->set_text(color.to_html(edit_alpha && color.a<1));
+
+ color_box->update();
+
+ emit_signal("color_changed",color);
+
+}
+
+void ColorPicker::_html_entered(const String& p_html) {
+
+ if (updating)
+ return;
+
+ color = Color::html(p_html);
+ _update_color();
+ emit_signal("color_changed",color);
+}
+
+void ColorPicker::_update_color() {
+
+ updating=true;
+
+ switch(mode) {
+
+ case MODE_RAW: {
+
+ for(int i=0;i<4;i++) {
+ scroll[i]->set_step(0.01);
+ scroll[i]->set_val(color.components[i]);
+ }
+ } break;
+ case MODE_RGB: {
+
+ for(int i=0;i<4;i++) {
+ scroll[i]->set_step(1);
+ scroll[i]->set_val(color.components[i]*255);
+ }
+
+ } break;
+ case MODE_HSV: {
+
+ for(int i=0;i<4;i++) {
+ scroll[i]->set_step(1);
+ }
+
+ scroll[0]->set_val( color.get_h()*255 );
+ scroll[1]->set_val( color.get_s()*255 );
+ scroll[2]->set_val( color.get_v()*255 );
+ scroll[3]->set_val(color.a*255);
+
+ } break;
+ }
+
+
+ html->set_text(color.to_html(edit_alpha && color.a<1));
+
+ color_box->update();
+ updating=false;
+}
+
+Color ColorPicker::get_color() const {
+
+ return color;
+}
+
+
+void ColorPicker::set_mode(Mode p_mode) {
+
+ ERR_FAIL_INDEX(p_mode,3);
+ mode=p_mode;
+ if (mode_box->get_selected()!=p_mode)
+ mode_box->select(p_mode);
+
+ _update_controls();
+ _update_color();
+}
+
+ColorPicker::Mode ColorPicker::get_mode() const {
+
+ return mode;
+}
+
+void ColorPicker::_color_box_draw() {
+
+ color_box->draw_rect( Rect2( Point2(), color_box->get_size()), color);
+}
+
+void ColorPicker::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_color","color"),&ColorPicker::set_color);
+ ObjectTypeDB::bind_method(_MD("get_color"),&ColorPicker::get_color);
+ ObjectTypeDB::bind_method(_MD("set_mode","mode"),&ColorPicker::set_mode);
+ ObjectTypeDB::bind_method(_MD("get_mode"),&ColorPicker::get_mode);
+ ObjectTypeDB::bind_method(_MD("set_edit_alpha","show"),&ColorPicker::set_edit_alpha);
+ ObjectTypeDB::bind_method(_MD("is_editing_alpha"),&ColorPicker::is_editing_alpha);
+ ObjectTypeDB::bind_method(_MD("_value_changed"),&ColorPicker::_value_changed);
+ ObjectTypeDB::bind_method(_MD("_html_entered"),&ColorPicker::_html_entered);
+ ObjectTypeDB::bind_method(_MD("_color_box_draw"),&ColorPicker::_color_box_draw);
+
+ ADD_SIGNAL( MethodInfo("color_changed",PropertyInfo(Variant::COLOR,"color")));
+}
+
+
+
+
+ColorPicker::ColorPicker() {
+
+
+ //edit_alpha=false;
+ updating=true;
+ edit_alpha=true;
+
+ VBoxContainer *vbl = memnew( VBoxContainer );
+ add_child(vbl);
+
+ mode_box = memnew( OptionButton );
+ mode_box->add_item("RGB");
+ mode_box->add_item("HSV");
+ mode_box->add_item("RAW");
+ mode_box->connect("item_selected",this,"set_mode");
+
+ color_box=memnew( Control );
+ color_box->set_v_size_flags(SIZE_EXPAND_FILL);
+ vbl->add_child(color_box);
+ color_box->connect("draw",this,"_color_box_draw");
+
+ vbl->add_child(mode_box);
+
+
+ VBoxContainer *vbr = memnew( VBoxContainer );
+ add_child(vbr);
+ vbr->set_h_size_flags(SIZE_EXPAND_FILL);
+
+
+ for(int i=0;i<4;i++) {
+
+ HBoxContainer *hbc = memnew( HBoxContainer );
+
+ labels[i]=memnew( Label );
+ static const char*_lt[4]={"R","G","B","A"};
+ labels[i]->set_text(_lt[i]);
+
+ hbc->add_child(labels[i]);
+
+ scroll[i]=memnew( HSlider );
+ hbc->add_child(scroll[i]);
+
+ values[i]=memnew( SpinBox );
+ scroll[i]->share(values[i]);
+ hbc->add_child(values[i]);
+
+
+ scroll[i]->set_min(0);
+ scroll[i]->set_max(255);
+ scroll[i]->set_step(1);
+ scroll[i]->set_page(0);
+ scroll[i]->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ scroll[i]->connect("value_changed",this,"_value_changed");
+
+ vbr->add_child(hbc);
+
+
+ }
+
+ HBoxContainer *hhb = memnew( HBoxContainer );
+ vbr->add_child(hhb);
+ html_num = memnew( Label );
+ hhb->add_child(html_num);
+
+ html = memnew( LineEdit );
+ hhb->add_child(html);
+ html->connect("text_entered",this,"_html_entered");
+ html_num->set_text("#");
+ html->set_h_size_flags(SIZE_EXPAND_FILL);
+
+
+ mode=MODE_RGB;
+ _update_controls();
+ _update_color();
+ updating=false;
+
+}
+
+
+
+
+/////////////////
+
+
+void ColorPickerButton::_color_changed(const Color& p_color) {
+
+ update();
+ emit_signal("color_changed",p_color);
+}
+
+
+void ColorPickerButton::pressed() {
+
+ Size2 ms = Size2(350, picker->get_combined_minimum_size().height+10);
+ popup->set_pos(get_global_pos()-Size2(0,ms.height));
+ popup->set_size(ms);
+ popup->popup();
+}
+
+void ColorPickerButton::_notification(int p_what) {
+
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ Ref<StyleBox> normal = get_stylebox("normal" );
+ draw_rect(Rect2(normal->get_offset(),get_size()-normal->get_minimum_size()),picker->get_color());
+ }
+}
+
+
+void ColorPickerButton::set_color(const Color& p_color){
+
+
+ picker->set_color(p_color);
+}
+Color ColorPickerButton::get_color() const{
+
+ return picker->get_color();
+}
+
+void ColorPickerButton::set_edit_alpha(bool p_show) {
+
+ picker->set_edit_alpha(p_show);
+}
+
+bool ColorPickerButton::is_editing_alpha() const{
+
+ return picker->is_editing_alpha();
+
+}
+
+void ColorPickerButton::_bind_methods(){
+
+ ObjectTypeDB::bind_method(_MD("set_color","color"),&ColorPickerButton::set_color);
+ ObjectTypeDB::bind_method(_MD("get_color"),&ColorPickerButton::get_color);
+ ObjectTypeDB::bind_method(_MD("set_edit_alpha","show"),&ColorPickerButton::set_edit_alpha);
+ ObjectTypeDB::bind_method(_MD("is_editing_alpha"),&ColorPickerButton::is_editing_alpha);
+ ObjectTypeDB::bind_method(_MD("_color_changed"),&ColorPickerButton::_color_changed);
+
+ ADD_SIGNAL( MethodInfo("color_changed",PropertyInfo(Variant::COLOR,"color")));
+ ADD_PROPERTY( PropertyInfo(Variant::COLOR,"color"),_SCS("set_color"),_SCS("get_color") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"edit_alpha"),_SCS("set_edit_alpha"),_SCS("is_editing_alpha") );
+
+}
+
+ColorPickerButton::ColorPickerButton() {
+
+ popup = memnew( PopupPanel );
+ picker = memnew( ColorPicker );
+ popup->add_child(picker);
+ popup->set_child_rect(picker);
+ picker->connect("color_changed",this,"_color_changed");
+ add_child(popup);
+}
+
diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h
new file mode 100644
index 0000000000..e45b4b131e
--- /dev/null
+++ b/scene/gui/color_picker.h
@@ -0,0 +1,120 @@
+/*************************************************************************/
+/* color_picker.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef COLOR_PICKER_H
+#define COLOR_PICKER_H
+
+#include "scene/gui/slider.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/spin_box.h"
+#include "scene/gui/label.h"
+#include "scene/gui/button.h"
+#include "scene/gui/popup.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/option_button.h"
+
+class ColorPicker : public HBoxContainer {
+
+ OBJ_TYPE(ColorPicker,HBoxContainer);
+public:
+
+ enum Mode {
+ MODE_RGB,
+ MODE_HSV,
+ MODE_RAW
+ };
+private:
+
+ Mode mode;
+
+ OptionButton *mode_box;
+
+ Control *color_box;
+ HSlider *scroll[4];
+ SpinBox *values[4];
+ Label *labels[4];
+ Label *html_num;
+ LineEdit *html;
+ bool edit_alpha;
+ Size2i ms;
+
+ Color color;
+ bool updating;
+
+ void _html_entered(const String& p_html);
+ void _value_changed(double);
+ void _update_controls();
+ void _update_color();
+ void _color_box_draw();
+protected:
+
+ void _notification(int);
+ static void _bind_methods();
+public:
+
+ void set_edit_alpha(bool p_show);
+ bool is_editing_alpha() const;
+
+ void set_color(const Color& p_color);
+ Color get_color() const;
+
+ void set_mode(Mode p_mode);
+ Mode get_mode() const;
+
+
+ ColorPicker();
+};
+
+VARIANT_ENUM_CAST( ColorPicker::Mode );
+
+class ColorPickerButton : public Button {
+
+ OBJ_TYPE(ColorPickerButton,Button);
+
+ PopupPanel *popup;
+ ColorPicker *picker;
+
+ void _color_changed(const Color& p_color);
+ virtual void pressed();
+
+protected:
+
+ void _notification(int);
+ static void _bind_methods();
+public:
+
+ void set_color(const Color& p_color);
+ Color get_color() const;
+
+ void set_edit_alpha(bool p_show);
+ bool is_editing_alpha() const;
+
+ ColorPickerButton();
+};
+
+#endif // COLOR_PICKER_H
diff --git a/scene/gui/container.cpp b/scene/gui/container.cpp
new file mode 100644
index 0000000000..6543a0388d
--- /dev/null
+++ b/scene/gui/container.cpp
@@ -0,0 +1,154 @@
+/*************************************************************************/
+/* container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "container.h"
+#include "scene/scene_string_names.h"
+#include "message_queue.h"
+
+
+void Container::_child_minsize_changed() {
+
+ Size2 ms = get_combined_minimum_size();
+ if (ms.width > get_size().width || ms.height > get_size().height)
+ minimum_size_changed();
+ queue_sort();
+}
+
+void Container::add_child_notify(Node *p_child) {
+
+ Control *control = p_child->cast_to<Control>();
+ if (!control)
+ return;
+
+ control->connect("size_flags_changed",this,"queue_sort");
+ control->connect("minimum_size_changed",this,"_child_minsize_changed");
+ control->connect("visibility_changed",this,"_child_minsize_changed");
+ queue_sort();
+
+}
+
+void Container::remove_child_notify(Node *p_child) {
+
+
+ Control *control = p_child->cast_to<Control>();
+ if (!control)
+ return;
+
+ control->disconnect("size_flags_changed",this,"queue_sort");
+ control->disconnect("minimum_size_changed",this,"_child_minsize_changed");
+ control->disconnect("visibility_changed",this,"_child_minsize_changed");
+ queue_sort();
+}
+
+void Container::_sort_children() {
+
+ if (!is_inside_scene())
+ return;
+
+ notification(NOTIFICATION_SORT_CHILDREN);
+ emit_signal(SceneStringNames::get_singleton()->sort_children);
+ pending_sort=false;
+}
+
+void Container::fit_child_in_rect(Control *p_child,const Rect2& p_rect) {
+
+ ERR_FAIL_COND(p_child->get_parent()!=this);
+
+ Size2 minsize = p_child->get_combined_minimum_size();
+ Rect2 r=p_rect;
+
+ if (!(p_child->get_h_size_flags()&SIZE_FILL)) {
+ r.size.x=minsize.x;
+ r.pos.x += Math::floor((p_rect.size.x - minsize.x)/2);
+ }
+
+ if (!(p_child->get_v_size_flags()&SIZE_FILL)) {
+ r.size.y=minsize.y;
+ r.pos.y += Math::floor((p_rect.size.y - minsize.y)/2);
+ }
+
+ for(int i=0;i<4;i++)
+ p_child->set_anchor(Margin(i),ANCHOR_BEGIN);
+
+ p_child->set_pos(r.pos);
+ p_child->set_size(r.size);
+}
+
+void Container::queue_sort() {
+
+ if (!is_inside_scene())
+ return;
+
+ if (pending_sort)
+ return;
+
+ MessageQueue::get_singleton()->push_call(this,"_sort_children");
+ pending_sort=true;
+}
+
+void Container::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_ENTER_SCENE: {
+ pending_sort=false;
+ queue_sort();
+ } break;
+ case NOTIFICATION_RESIZED: {
+
+ queue_sort();
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+
+ queue_sort();
+ } break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+
+ if (is_visible()) {
+ queue_sort();
+ }
+ } break;
+ }
+}
+
+void Container::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_sort_children"),&Container::_sort_children);
+ ObjectTypeDB::bind_method(_MD("_child_minsize_changed"),&Container::_child_minsize_changed);
+
+ ObjectTypeDB::bind_method(_MD("queue_sort"),&Container::queue_sort);
+ ObjectTypeDB::bind_method(_MD("fit_child_in_rect","child:Control","rect"),&Container::fit_child_in_rect);
+
+ BIND_CONSTANT( NOTIFICATION_SORT_CHILDREN );
+ ADD_SIGNAL(MethodInfo("sort_children"));
+}
+
+Container::Container() {
+
+ pending_sort=false;
+}
diff --git a/scene/gui/container.h b/scene/gui/container.h
new file mode 100644
index 0000000000..841244fb9a
--- /dev/null
+++ b/scene/gui/container.h
@@ -0,0 +1,59 @@
+/*************************************************************************/
+/* container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef CONTAINER_H
+#define CONTAINER_H
+
+#include "scene/gui/control.h"
+
+class Container : public Control {
+
+ OBJ_TYPE(Container,Control);
+
+ bool pending_sort;
+ void _sort_children();
+ void _child_minsize_changed();
+protected:
+
+ void queue_sort();
+ virtual void add_child_notify(Node *p_child);
+ virtual void remove_child_notify(Node *p_child);
+
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+ enum {
+ NOTIFICATION_SORT_CHILDREN=50
+ };
+
+ void fit_child_in_rect(Control *p_child,const Rect2& p_rect);
+
+ Container();
+};
+
+#endif // CONTAINER_H
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
new file mode 100644
index 0000000000..3e44b5af16
--- /dev/null
+++ b/scene/gui/control.cpp
@@ -0,0 +1,2886 @@
+/*************************************************************************/
+/* control.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "control.h"
+#include "servers/visual_server.h"
+#include "scene/main/viewport.h"
+#include "scene/main/canvas_layer.h"
+#include "globals.h"
+
+#include "print_string.h"
+#include "os/keyboard.h"
+#include "os/os.h"
+#include "message_queue.h"
+#include "scene/scene_string_names.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/label.h"
+#include <stdio.h>
+
+
+class TooltipPanel : public Panel {
+
+ OBJ_TYPE(TooltipPanel,Panel)
+public:
+ TooltipPanel() {};
+
+};
+
+class TooltipLabel : public Label {
+
+ OBJ_TYPE(TooltipLabel,Label)
+public:
+ TooltipLabel() {};
+
+};
+
+Control::Window::Window() {
+
+
+ mouse_focus=NULL;
+ mouse_focus_button=-1;
+ key_focus=NULL;
+ mouse_over=NULL;
+ disable_input=false;
+
+ cancelled_input_ID=0;
+ tooltip=NULL;
+ tooltip_popup=NULL;
+ tooltip_label=NULL;
+ subwindow_order_dirty=false;
+}
+
+
+Variant Control::edit_get_state() const {
+
+ return get_rect();
+
+}
+void Control::edit_set_state(const Variant& p_state) {
+
+ Rect2 state=p_state;
+ set_pos(state.pos);
+ set_size(state.size);
+}
+
+void Control::set_custom_minimum_size(const Size2& p_custom) {
+
+ if (p_custom==data.custom_minimum_size)
+ return;
+ data.custom_minimum_size=p_custom;
+ minimum_size_changed();
+}
+
+Size2 Control::get_custom_minimum_size() const{
+
+ return data.custom_minimum_size;
+}
+
+Size2 Control::get_combined_minimum_size() const {
+
+ Size2 minsize = get_minimum_size();
+ minsize.x = MAX(minsize.x,data.custom_minimum_size.x);
+ minsize.y = MAX(minsize.y,data.custom_minimum_size.y);
+ return minsize;
+}
+
+Size2 Control::edit_get_minimum_size() const {
+
+ return get_combined_minimum_size();
+}
+
+void Control::edit_set_rect(const Rect2& p_edit_rect) {
+
+
+ Rect2 new_rect=get_rect();
+
+ new_rect.pos+=p_edit_rect.pos.snapped(Vector2(1,1));
+ new_rect.size=p_edit_rect.size.snapped(Vector2(1,1));
+
+ set_pos(new_rect.pos);
+ set_size(new_rect.size);
+
+}
+
+bool Control::_set(const StringName& p_name, const Variant& p_value) {
+
+
+ String name= p_name;
+ if (!name.begins_with("custom"))
+ return false;
+
+ if (p_value.get_type()==Variant::NIL) {
+
+ if (name.begins_with("custom_icons/")) {
+ String dname = name.get_slice("/",1);
+ data.icon_override.erase(dname);
+ update();
+ } else if (name.begins_with("custom_styles/")) {
+ String dname = name.get_slice("/",1);
+ data.style_override.erase(dname);
+ update();
+ } else if (name.begins_with("custom_fonts/")) {
+ String dname = name.get_slice("/",1);
+ data.font_override.erase(dname);
+ update();
+ } else if (name.begins_with("custom_colors/")) {
+ String dname = name.get_slice("/",1);
+ data.color_override.erase(dname);
+ update();
+ } else if (name.begins_with("custom_constants/")) {
+ String dname = name.get_slice("/",1);
+ data.constant_override.erase(dname);
+ update();
+ } else
+ return false;
+
+ } else {
+ if (name.begins_with("custom_icons/")) {
+ String dname = name.get_slice("/",1);
+ add_icon_override(dname,p_value);
+ } else if (name.begins_with("custom_styles/")) {
+ String dname = name.get_slice("/",1);
+ add_style_override(dname,p_value);
+ } else if (name.begins_with("custom_fonts/")) {
+ String dname = name.get_slice("/",1);
+ add_font_override(dname,p_value);
+ } else if (name.begins_with("custom_colors/")) {
+ String dname = name.get_slice("/",1);
+ add_color_override(dname,p_value);
+ } else if (name.begins_with("custom_constants/")) {
+ String dname = name.get_slice("/",1);
+ add_constant_override(dname,p_value);
+ } else
+ return false;
+ }
+ return true;
+
+}
+
+void Control::_update_minimum_size() {
+
+ if (!is_inside_scene())
+ return;
+
+ data.pending_min_size_update=false;
+ Size2 minsize = get_combined_minimum_size();
+ if (minsize.x > data.size_cache.x ||
+ minsize.y > data.size_cache.y
+ ) {
+ _size_changed();
+ }
+
+ emit_signal(SceneStringNames::get_singleton()->minimum_size_changed);
+
+}
+
+bool Control::_get(const StringName& p_name,Variant &r_ret) const {
+
+
+ String sname=p_name;
+
+ if (!sname.begins_with("custom"))
+ return false;
+
+ if (sname.begins_with("custom_icons/")) {
+ String name = sname.get_slice("/",1);
+
+ r_ret= data.icon_override.has(name)?Variant(data.icon_override[name]):Variant();
+ } else if (sname.begins_with("custom_styles/")) {
+ String name = sname.get_slice("/",1);
+
+ r_ret= data.style_override.has(name)?Variant(data.style_override[name]):Variant();
+ } else if (sname.begins_with("custom_fonts/")) {
+ String name = sname.get_slice("/",1);
+
+ r_ret= data.font_override.has(name)?Variant(data.font_override[name]):Variant();
+ } else if (sname.begins_with("custom_colors/")) {
+ String name = sname.get_slice("/",1);
+ r_ret= data.color_override.has(name)?Variant(data.color_override[name]):Variant();
+ } else if (sname.begins_with("custom_constants/")) {
+ String name = sname.get_slice("/",1);
+
+ r_ret= data.constant_override.has(name)?Variant(data.constant_override[name]):Variant();
+ } else
+ return false;
+
+
+
+ return true;
+
+
+}
+void Control::_get_property_list( List<PropertyInfo> *p_list) const {
+
+ Ref<Theme> theme;
+ if (data.theme.is_valid()) {
+
+ theme=data.theme;
+ } else {
+ theme=Theme::get_default();
+ }
+
+
+ {
+ List<StringName> names;
+ theme->get_icon_list(get_type_name(),&names);
+ for(List<StringName>::Element *E=names.front();E;E=E->next()) {
+
+ uint32_t hint= PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_CHECKABLE;
+ if (data.icon_override.has(E->get()))
+ hint|=PROPERTY_USAGE_STORAGE|PROPERTY_USAGE_CHECKED;
+
+ p_list->push_back( PropertyInfo(Variant::OBJECT,"custom_icons/"+E->get(),PROPERTY_HINT_RESOURCE_TYPE, "Texture",hint) );
+ }
+ }
+ {
+ List<StringName> names;
+ theme->get_stylebox_list(get_type_name(),&names);
+ for(List<StringName>::Element *E=names.front();E;E=E->next()) {
+
+ uint32_t hint= PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_CHECKABLE;
+ if (data.style_override.has(E->get()))
+ hint|=PROPERTY_USAGE_STORAGE|PROPERTY_USAGE_CHECKED;
+
+ p_list->push_back( PropertyInfo(Variant::OBJECT,"custom_styles/"+E->get(),PROPERTY_HINT_RESOURCE_TYPE, "StyleBox",hint) );
+ }
+ }
+ {
+ List<StringName> names;
+ theme->get_font_list(get_type_name(),&names);
+ for(List<StringName>::Element *E=names.front();E;E=E->next()) {
+
+ uint32_t hint= PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_CHECKABLE;
+ if (data.font_override.has(E->get()))
+ hint|=PROPERTY_USAGE_STORAGE|PROPERTY_USAGE_CHECKED;
+
+ p_list->push_back( PropertyInfo(Variant::OBJECT,"custom_fonts/"+E->get(),PROPERTY_HINT_RESOURCE_TYPE, "Font",hint) );
+ }
+ }
+ {
+ List<StringName> names;
+ theme->get_color_list(get_type_name(),&names);
+ for(List<StringName>::Element *E=names.front();E;E=E->next()) {
+
+ uint32_t hint= PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_CHECKABLE;
+ if (data.color_override.has(E->get()))
+ hint|=PROPERTY_USAGE_STORAGE|PROPERTY_USAGE_CHECKED;
+
+ p_list->push_back( PropertyInfo(Variant::COLOR,"custom_colors/"+E->get(),PROPERTY_HINT_NONE, "",hint) );
+ }
+ }
+ {
+ List<StringName> names;
+ theme->get_constant_list(get_type_name(),&names);
+ for(List<StringName>::Element *E=names.front();E;E=E->next()) {
+
+ uint32_t hint= PROPERTY_USAGE_EDITOR|PROPERTY_USAGE_CHECKABLE;
+ if (data.constant_override.has(E->get()))
+ hint|=PROPERTY_USAGE_STORAGE|PROPERTY_USAGE_CHECKED;
+
+ p_list->push_back( PropertyInfo(Variant::INT,"custom_constants/"+E->get(),PROPERTY_HINT_RANGE, "-16384,16384",hint) );
+ }
+ }
+
+
+}
+
+
+Control *Control::get_parent_control() const {
+
+ return data.parent;
+}
+
+void Control::_input_text(const String& p_text) {
+
+ if (!window)
+ return;
+ if (window->key_focus)
+ window->key_focus->call("set_text",p_text);
+
+}
+
+void Control::_gui_input(const InputEvent& p_event) {
+
+ _window_input_event(p_event);
+}
+
+void Control::_resize(const Size2& p_size) {
+
+ _size_changed();
+}
+
+
+
+void Control::_notification(int p_notification) {
+
+
+ switch(p_notification) {
+
+ case NOTIFICATION_ENTER_SCENE: {
+
+ if (data.window==this) {
+
+ window = memnew( Window );
+ add_to_group("_gui_input");
+ add_to_group("windows");
+
+ window->tooltip_timer = memnew( Timer );
+ add_child(window->tooltip_timer);
+ window->tooltip_timer->set_wait_time( GLOBAL_DEF("display/tooltip_delay",0.7));
+ window->tooltip_timer->connect("timeout",this,"_window_show_tooltip");
+ window->tooltip=NULL;
+ window->tooltip_popup = memnew( TooltipPanel );
+ add_child(window->tooltip_popup);
+ window->tooltip_label = memnew( TooltipLabel );
+ window->tooltip_popup->add_child(window->tooltip_label);
+ window->tooltip_popup->set_as_toplevel(true);
+ window->tooltip_popup->hide();
+ window->drag_attempted=false;
+ window->drag_preview=NULL;
+
+ if (get_scene()->is_editor_hint()) {
+
+ Node *n = this;
+ while(n) {
+
+ if (n->has_meta("_editor_disable_input")) {
+ window->disable_input=true;
+ break;
+ }
+ n=n->get_parent();
+ }
+ }
+
+ } else {
+ window=NULL;
+ }
+
+ _size_changed();
+
+ } break;
+ case NOTIFICATION_EXIT_SCENE: {
+
+ if (data.window) {
+
+ if (data.window->window->mouse_focus == this)
+ data.window->window->mouse_focus=NULL;
+ if (data.window->window->key_focus == this)
+ data.window->window->key_focus=NULL;
+ if (data.window->window->mouse_over == this)
+ data.window->window->mouse_over=NULL;
+ if (data.window->window->tooltip == this)
+ data.window->window->tooltip=NULL;
+ }
+
+ if (window) {
+
+ remove_from_group("_gui_input");
+ remove_from_group("windows");
+ if (window->tooltip_timer)
+ memdelete(window->tooltip_timer);
+ window->tooltip_timer=NULL;
+ window->tooltip=NULL;
+ if (window->tooltip_popup)
+ memdelete(window->tooltip_popup);
+ window->tooltip_popup=NULL;
+
+ memdelete(window);
+ window=NULL;
+
+ }
+
+
+
+ } break;
+
+
+ case NOTIFICATION_ENTER_CANVAS: {
+
+ data.window=NULL;
+ data.viewport=NULL;
+ data.parent=NULL;
+
+ Control *_window=this;
+ bool gap=false;
+ bool gap_valid=true;
+ bool window_found=false;
+
+ Node *parent=_window->get_parent();
+ if (parent && parent->cast_to<Control>()) {
+
+ data.parent=parent->cast_to<Control>();
+ }
+
+ Viewport *viewport=NULL;
+
+ parent=this; //meh
+
+ while(parent) {
+
+ Control *c=parent->cast_to<Control>();
+
+ if (!window_found && c) {
+ if (!gap && c!=this) {
+ gap_valid=false;
+ }
+
+ _window = c;
+ }
+
+ CanvasItem *ci =parent->cast_to<CanvasItem>();
+
+ if ((ci && ci->is_set_as_toplevel()) || !ci) {
+ gap=true;
+ }
+
+ if (parent->cast_to<CanvasLayer>()) {
+ window_found=true; //don't go beyond canvas layer
+ }
+
+ viewport =parent->cast_to<Viewport>();
+ if (viewport) {
+ break; //no go beyond viewport either
+ }
+
+ parent=parent->get_parent();
+ }
+
+ data.window=_window;
+ data.viewport=viewport;
+ data.parent_canvas_item=get_parent_item();
+
+ if (data.parent_canvas_item) {
+
+ data.parent_canvas_item->connect("item_rect_changed",this,"_size_changed");
+ } else if (data.viewport) {
+
+ //connect viewport
+ data.viewport->connect("size_changed",this,"_size_changed");
+ } else {
+
+ }
+
+
+ if (gap && gap_valid && data.window!=this) {
+ //is a subwindow, conditions to meet subwindow status are quite complex..
+ data.SI = data.window->window->subwindows.push_back(this);
+ data.window->window->subwindow_order_dirty=true;
+
+ }
+
+
+ } break;
+ case NOTIFICATION_EXIT_CANVAS: {
+
+
+ if (data.parent_canvas_item) {
+
+ data.parent_canvas_item->disconnect("item_rect_changed",this,"_size_changed");
+ data.parent_canvas_item=NULL;
+ } else if (data.viewport) {
+
+ //disconnect viewport
+ data.viewport->disconnect("size_changed",this,"_size_changed");
+ } else {
+
+ }
+
+ if (data.MI) {
+
+ data.window->window->modal_stack.erase(data.MI);
+ data.MI=NULL;
+ }
+
+ if (data.SI) {
+ //erase from subwindows
+ data.window->window->subwindows.erase(data.SI);
+ data.SI=NULL;
+ }
+
+ data.viewport=NULL;
+ data.window=NULL;
+ data.parent=NULL;
+
+ } break;
+
+
+ case NOTIFICATION_PARENTED: {
+
+ Control * parent = get_parent()->cast_to<Control>();
+
+ //make children reference them theme
+ if (parent && data.theme.is_null() && parent->data.theme_owner)
+ _propagate_theme_changed(parent->data.theme_owner);
+
+ } break;
+ case NOTIFICATION_UNPARENTED: {
+
+ //make children unreference the theme
+ if (data.theme.is_null() && data.theme_owner)
+ _propagate_theme_changed(NULL);
+
+ } break;
+ case NOTIFICATION_MOVED_IN_PARENT: {
+ // some parents need to know the order of the childrens to draw (like TabContainer)
+ // update if necesary
+ if (data.parent)
+ data.parent->update();
+ update();
+
+ if (data.SI && data.window) {
+ data.window->window->subwindow_order_dirty=true;
+ }
+
+ } break;
+ case NOTIFICATION_RESIZED: {
+
+ emit_signal(SceneStringNames::get_singleton()->resized);
+ } break;
+ case NOTIFICATION_DRAW: {
+
+ Matrix32 xform;
+ xform.set_origin(get_pos());
+ VisualServer::get_singleton()->canvas_item_set_transform(get_canvas_item(),xform);
+ VisualServer::get_singleton()->canvas_item_set_custom_rect( get_canvas_item(),true, Rect2(Point2(),get_size()));
+ //emit_signal(SceneStringNames::get_singleton()->draw);
+
+ } break;
+ case NOTIFICATION_MOUSE_ENTER: {
+
+ emit_signal(SceneStringNames::get_singleton()->mouse_enter);
+ } break;
+ case NOTIFICATION_MOUSE_EXIT: {
+
+ emit_signal(SceneStringNames::get_singleton()->mouse_exit);
+ } break;
+ case NOTIFICATION_FOCUS_ENTER: {
+
+ emit_signal(SceneStringNames::get_singleton()->focus_enter);
+ update();
+ } break;
+ case NOTIFICATION_FOCUS_EXIT: {
+
+ emit_signal(SceneStringNames::get_singleton()->focus_exit);
+ update();
+
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+
+ update();
+ } break;
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+
+ if (!is_visible()) {
+
+ if (data.window->window->mouse_focus == this) {
+ data.window->window->mouse_focus=NULL;
+ }
+ if (data.window==this) {
+ window->drag_data=Variant();
+ if (window->drag_preview) {
+ memdelete( window->drag_preview);
+ window->drag_preview=NULL;
+ }
+ }
+
+ if (data.window->window->key_focus == this)
+ data.window->window->key_focus=NULL;
+ if (data.window->window->mouse_over == this)
+ data.window->window->mouse_over=NULL;
+ if (data.window->window->tooltip == this)
+ data.window->window->tooltip=NULL;
+ if (data.window->window->tooltip == this)
+ data.window->window->tooltip=NULL;
+
+ _modal_stack_remove();
+ minimum_size_changed();
+
+ //remove key focus
+ //remove modalness
+ } else {
+
+ _size_changed();
+ }
+
+ } break;
+ case SceneMainLoop::NOTIFICATION_WM_UNFOCUS_REQUEST: {
+
+ if (!window)
+ return;
+ if (window->key_focus)
+ window->key_focus->release_focus();
+
+ } break;
+
+
+
+ }
+}
+
+
+Size2 Control::_window_get_pos() const {
+
+ if (data.viewport) {
+
+ Rect2 r = data.viewport->get_visible_rect();
+ return r.pos;
+ } else
+ return Point2();
+
+ //return get_global_transform().get_origin();
+}
+
+
+bool Control::clips_input() const {
+
+ return false;
+}
+bool Control::has_point(const Point2& p_point) const {
+
+ if (get_script_instance()) {
+ Variant v=p_point;
+ const Variant *p=&v;
+ Variant::CallError ce;
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->has_point,&p,1,ce);
+ if (ce.error==Variant::CallError::CALL_OK) {
+ return ret;
+ }
+ }
+ /*if (has_stylebox("mask")) {
+ Ref<StyleBox> mask = get_stylebox("mask");
+ return mask->test_mask(p_point,Rect2(Point2(),get_size()));
+ }*/
+ return Rect2( Point2(), get_size() ).has_point(p_point);
+}
+
+Variant Control::get_drag_data(const Point2& p_point) {
+
+ if (get_script_instance()) {
+ Variant v=p_point;
+ const Variant *p=&v;
+ Variant::CallError ce;
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->get_drag_data,&p,1,ce);
+ if (ce.error==Variant::CallError::CALL_OK)
+ return ret;
+ }
+
+ return Variant();
+}
+
+
+bool Control::can_drop_data(const Point2& p_point,const Variant& p_data) const {
+
+ if (get_script_instance()) {
+ Variant v=p_point;
+ const Variant *p[2]={&v,&p_data};
+ Variant::CallError ce;
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->can_drop_data,p,2,ce);
+ if (ce.error==Variant::CallError::CALL_OK)
+ return ret;
+ }
+
+ return Variant();
+
+}
+void Control::drop_data(const Point2& p_point,const Variant& p_data){
+
+ if (get_script_instance()) {
+ Variant v=p_point;
+ const Variant *p[2]={&v,&p_data};
+ Variant::CallError ce;
+ Variant ret = get_script_instance()->call(SceneStringNames::get_singleton()->drop_data,p,2,ce);
+ if (ce.error==Variant::CallError::CALL_OK)
+ return;
+ }
+}
+
+void Control::force_drag(const Variant& p_data,Control *p_control) {
+
+ ERR_FAIL_COND(!is_inside_scene());
+ ERR_FAIL_COND(!data.window);
+ ERR_FAIL_COND(p_data.get_type()==Variant::NIL);
+
+
+
+ data.window->window->drag_data=p_data;
+ data.window->window->mouse_focus=NULL;
+
+ if (p_control) {
+ data.window->set_drag_preview(p_control);
+ }
+}
+
+void Control::set_drag_preview(Control *p_control) {
+
+ ERR_FAIL_NULL(p_control);
+ ERR_FAIL_COND( !((Object*)p_control)->cast_to<Control>());
+ ERR_FAIL_COND(!is_inside_scene() || !data.window);
+ ERR_FAIL_COND(p_control->is_inside_scene());
+ ERR_FAIL_COND(p_control->get_parent()!=NULL);
+
+ if (data.window->window->drag_preview) {
+ memdelete(data.window->window->drag_preview);
+ }
+ p_control->set_as_toplevel(true);
+ p_control->set_pos(data.window->window->last_mouse_pos);
+ data.window->add_child(p_control);
+ if (data.window->window->drag_preview) {
+ memdelete( data.window->window->drag_preview );
+ }
+
+ data.window->window->drag_preview=p_control;
+
+}
+
+
+Control* Control::_find_next_visible_control_at_pos(Node* p_node,const Point2& p_global,Matrix32& r_xform) const {
+
+ return NULL;
+}
+
+Control* Control::_find_control_at_pos(CanvasItem* p_node,const Point2& p_global,const Matrix32& p_xform,Matrix32& r_inv_xform) {
+
+ if (p_node->cast_to<Viewport>())
+ return NULL;
+
+ Control *c=p_node->cast_to<Control>();
+
+ if (c) {
+ // print_line("at "+String(c->get_path())+" POS "+c->get_pos()+" bt "+p_xform);
+ }
+
+ if (c==data.window) {
+ //try subwindows first!!
+
+ c->_window_sort_subwindows(); // sort them
+
+ for (List<Control*>::Element *E=c->window->subwindows.back();E;E=E->prev()) {
+
+ Control *sw = E->get();
+ if (!sw->is_visible())
+ continue;
+
+ Matrix32 xform;
+ CanvasItem *pci = sw->get_parent_item();
+ if (pci)
+ xform=pci->get_global_transform();
+
+ Control *ret = _find_control_at_pos(sw,p_global,xform,r_inv_xform);
+ if (ret)
+ return ret;
+
+ }
+ }
+
+ if (p_node->is_hidden()) {
+ //return _find_next_visible_control_at_pos(p_node,p_global,r_inv_xform);
+ return NULL; //canvas item hidden, discard
+ }
+
+ Matrix32 matrix = p_xform * p_node->get_transform();
+
+ if (!c || !c->clips_input() || c->has_point(matrix.affine_inverse().xform(p_global))) {
+
+ for(int i=p_node->get_child_count()-1;i>=0;i--) {
+
+ if (p_node==data.window->window->tooltip_popup)
+ continue;
+
+ CanvasItem *ci = p_node->get_child(i)->cast_to<CanvasItem>();
+ if (!ci || ci->is_set_as_toplevel())
+ continue;
+
+ Control *ret=_find_control_at_pos(ci,p_global,matrix,r_inv_xform);;
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (!c)
+ return NULL;
+
+ matrix.affine_invert();
+
+ //conditions for considering this as a valid control for return
+ if (!c->data.ignore_mouse && c->has_point(matrix.xform(p_global)) && (!window->drag_preview || (c!=window->drag_preview && !window->drag_preview->is_a_parent_of(c)))) {
+ r_inv_xform=matrix;
+ return c;
+ } else
+ return NULL;
+}
+
+void Control::_window_cancel_input_ID(int p_input) {
+
+ window->cancelled_input_ID=(unsigned int)p_input;
+}
+
+void Control::_window_remove_focus() {
+
+ if (window->key_focus) {
+
+ Node *f=window->key_focus;
+ window->key_focus=NULL;
+ f->notification( NOTIFICATION_FOCUS_EXIT,true );
+
+ }
+}
+
+bool Control::window_has_modal_stack() const {
+
+
+ if (!data.window)
+ return false;
+ return data.window->window->modal_stack.size();
+}
+
+void Control::_window_cancel_tooltip() {
+
+ window->tooltip=NULL;
+ if (window->tooltip_timer)
+ window->tooltip_timer->stop();
+ if (window->tooltip_popup)
+ window->tooltip_popup->hide();
+
+}
+
+void Control::_window_show_tooltip() {
+
+ if (!window->tooltip) {
+ return;
+ }
+
+ String tooltip = window->tooltip->get_tooltip( window->tooltip->get_global_transform().xform_inv(window->tooltip_pos) );
+ if (tooltip.length()==0)
+ return; // bye
+
+
+ if (!window->tooltip_label) {
+ return;
+ }
+ Ref<StyleBox> ttp = get_stylebox("panel","TooltipPanel");
+
+ window->tooltip_label->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,ttp->get_margin(MARGIN_LEFT));
+ window->tooltip_label->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,ttp->get_margin(MARGIN_TOP));
+ window->tooltip_label->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,ttp->get_margin(MARGIN_RIGHT));
+ window->tooltip_label->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,ttp->get_margin(MARGIN_BOTTOM));
+ window->tooltip_label->set_text(tooltip);
+ Rect2 r(window->tooltip_pos+Point2(10,10),window->tooltip_label->get_combined_minimum_size()+ttp->get_minimum_size());
+ Rect2 vr = get_viewport_rect();
+ if (r.size.x+r.pos.x>vr.size.x)
+ r.pos.x=vr.size.x-r.size.x;
+ else if (r.pos.x<0)
+ r.pos.x=0;
+
+ if (r.size.y+r.pos.y>vr.size.y)
+ r.pos.y=vr.size.y-r.size.y;
+ else if (r.pos.y<0)
+ r.pos.y=0;
+
+ window->tooltip_popup->set_pos(r.pos);
+ window->tooltip_popup->set_size(r.size);
+
+ window->tooltip_popup->raise();
+
+ window->tooltip_popup->show();
+}
+
+
+void Control::_window_call_input(Control *p_control,const InputEvent& p_input) {
+
+
+ while(p_control) {
+
+ p_control->call_multilevel(SceneStringNames::get_singleton()->_input_event,p_input);
+ if (window->key_event_accepted)
+ break;
+ p_control->emit_signal(SceneStringNames::get_singleton()->input_event,p_input);
+ if (p_control->is_set_as_toplevel()) {
+ break;
+ }
+ if (window->key_event_accepted)
+ break;
+ if (p_control->data.stop_mouse && (p_input.type==InputEvent::MOUSE_BUTTON || p_input.type==InputEvent::MOUSE_MOTION))
+ break;
+ p_control=p_control->data.parent;
+ }
+}
+
+void Control::_window_input_event(InputEvent p_event) {
+
+
+
+ if (!window)
+ return;
+
+ if (window->disable_input)
+ return;
+
+ if (p_event.ID==window->cancelled_input_ID) {
+ return;
+ }
+ if (!is_visible()) {
+ return; //simple and plain
+ }
+ switch(p_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+
+ window->key_event_accepted=false;
+
+ Point2 mpos =(get_viewport_transform()).affine_inverse().xform(Point2(p_event.mouse_button.x,p_event.mouse_button.y));
+
+ if (p_event.mouse_button.pressed) {
+
+
+
+ Size2 pos = mpos - _window_get_pos();
+ if (window->mouse_focus && p_event.mouse_button.button_index!=window->mouse_focus_button) {
+
+ //do not steal mouse focus and stuff
+
+ } else {
+
+
+ _window_sort_modal_stack();
+ while (!window->modal_stack.empty()) {
+
+ Control *top = window->modal_stack.back()->get();
+ if (!top->has_point(top->get_global_transform().affine_inverse().xform(pos))) {
+
+ if (top->data.modal_exclusive) {
+ //cancel event, sorry, modal exclusive EATS UP ALL
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ get_scene()->set_input_as_handled();
+ return; // no one gets the event if exclusive NO ONE
+ }
+
+ top->notification(NOTIFICATION_MODAL_CLOSE);
+ top->_modal_stack_remove();
+ top->hide();
+ } else {
+ break;
+ }
+ }
+
+
+
+ Matrix32 parent_xform;
+
+ if (data.parent_canvas_item)
+ parent_xform=data.parent_canvas_item->get_global_transform();
+
+
+
+ window->mouse_focus = _find_control_at_pos(this,pos,parent_xform,window->focus_inv_xform);
+ //print_line("has mf "+itos(window->mouse_focus!=NULL));
+ window->mouse_focus_button=p_event.mouse_button.button_index;
+
+ if (!window->mouse_focus) {
+ break;
+ }
+
+ if (p_event.mouse_button.button_index==BUTTON_LEFT) {
+ window->drag_accum=Vector2();
+ window->drag_attempted=false;
+ window->drag_data=Variant();
+ }
+
+
+ }
+
+ p_event.mouse_button.global_x = pos.x;
+ p_event.mouse_button.global_y = pos.y;
+
+ pos = window->focus_inv_xform.xform(pos);
+ p_event.mouse_button.x = pos.x;
+ p_event.mouse_button.y = pos.y;
+
+#ifdef DEBUG_ENABLED
+ if (ScriptDebugger::get_singleton()) {
+
+ Array arr;
+ arr.push_back(window->mouse_focus->get_path());
+ arr.push_back(window->mouse_focus->get_type());
+ ScriptDebugger::get_singleton()->send_message("click_ctrl",arr);
+ }
+
+ /*if (bool(GLOBAL_DEF("debug/print_clicked_control",false))) {
+
+ print_line(String(window->mouse_focus->get_path())+" - "+pos);
+ }*/
+#endif
+
+ if (window->mouse_focus->get_focus_mode()!=FOCUS_NONE && window->mouse_focus!=window->key_focus && p_event.mouse_button.button_index==BUTTON_LEFT) {
+ // also get keyboard focus
+ window->mouse_focus->grab_focus();
+ }
+
+
+ if (window->mouse_focus->can_process()) {
+ _window_call_input(window->mouse_focus,p_event);
+ }
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ get_scene()->set_input_as_handled();
+
+ } else {
+
+ if (window->drag_preview && p_event.mouse_button.button_index==BUTTON_LEFT) {
+ memdelete( window->drag_preview );
+ window->drag_preview=NULL;
+ }
+
+ if (!window->mouse_focus) {
+
+ if (window->mouse_over && window->drag_data.get_type()!=Variant::NIL && p_event.mouse_button.button_index==BUTTON_LEFT) {
+
+ Size2 pos = mpos - _window_get_pos();
+ pos = window->focus_inv_xform.xform(pos);
+ window->mouse_over->drop_data(pos,window->drag_data);
+ window->drag_data=Variant();
+ //change mouse accordingly
+ }
+
+ break;
+ }
+
+ Size2 pos = mpos - _window_get_pos();
+ p_event.mouse_button.global_x = pos.x;
+ p_event.mouse_button.global_y = pos.y;
+ pos = window->focus_inv_xform.xform(pos);
+ p_event.mouse_button.x = pos.x;
+ p_event.mouse_button.y = pos.y;
+
+ if (window->mouse_focus->can_process()) {
+ _window_call_input(window->mouse_focus,p_event);
+ }
+
+ if (p_event.mouse_button.button_index==window->mouse_focus_button) {
+ window->mouse_focus=NULL;
+ window->mouse_focus_button=-1;
+ }
+
+ if (window->drag_data.get_type()!=Variant::NIL && p_event.mouse_button.button_index==BUTTON_LEFT) {
+ window->drag_data=Variant(); //always clear
+ }
+
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ get_scene()->set_input_as_handled();
+
+ }
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ window->key_event_accepted=false;
+
+ Matrix32 localizer = (get_viewport_transform()).affine_inverse();
+ Size2 pos = localizer.xform(Size2(p_event.mouse_motion.x,p_event.mouse_motion.y)) - _window_get_pos();
+ Vector2 speed = localizer.basis_xform(Point2(p_event.mouse_motion.speed_x,p_event.mouse_motion.speed_y));
+ Vector2 rel = localizer.basis_xform(Point2(p_event.mouse_motion.relative_x,p_event.mouse_motion.relative_y));
+
+ window->last_mouse_pos=pos;
+
+ Control *over = NULL;
+
+ Matrix32 parent_xform;
+ if (data.parent_canvas_item)
+ parent_xform=data.parent_canvas_item->get_global_transform();
+
+ // D&D
+ if (!window->drag_attempted && window->mouse_focus && p_event.mouse_motion.button_mask&BUTTON_MASK_LEFT) {
+
+ window->drag_accum+=rel;
+ float len = window->drag_accum.length();
+ if (len>10) {
+ window->drag_data=window->mouse_focus->get_drag_data(window->focus_inv_xform.xform(pos)-window->drag_accum);
+ if (window->drag_data.get_type()!=Variant::NIL) {
+
+ window->mouse_focus=NULL;
+ }
+ window->drag_attempted=true;
+ }
+ }
+
+
+ if (window->mouse_focus) {
+ over=window->mouse_focus;
+ //recompute focus_inv_xform again here
+
+ } else {
+
+ over = _find_control_at_pos(this,pos,parent_xform,window->focus_inv_xform);
+ }
+
+ if (window->drag_data.get_type()==Variant::NIL && over && !window->modal_stack.empty()) {
+
+ Control *top = window->modal_stack.back()->get();
+ if (over!=top && !top->is_a_parent_of(over)) {
+
+ break; // don't send motion event to anything below modal stack top
+ }
+ }
+
+ if (over!=window->mouse_over) {
+
+ if (window->mouse_over)
+ window->mouse_over->notification(NOTIFICATION_MOUSE_EXIT);
+
+ if (over)
+ over->notification(NOTIFICATION_MOUSE_ENTER);
+
+ }
+
+ window->mouse_over=over;
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_tooltip");
+
+ if (window->drag_preview) {
+ window->drag_preview->set_pos(pos);
+ }
+
+ if (!over) {
+ OS::get_singleton()->set_cursor_shape(OS::CURSOR_ARROW);
+ break;
+ }
+
+ p_event.mouse_motion.global_x = pos.x;
+ p_event.mouse_motion.global_y = pos.y;
+ p_event.mouse_motion.speed_x=speed.x;
+ p_event.mouse_motion.speed_y=speed.y;
+ p_event.mouse_motion.relative_x=rel.x;
+ p_event.mouse_motion.relative_y=rel.y;
+
+ if (p_event.mouse_motion.button_mask==0 && window->tooltip_timer) {
+ //nothing pressed
+
+ bool can_tooltip=true;
+
+ if (!window->modal_stack.empty()) {
+ if (window->modal_stack.back()->get()!=over && !window->modal_stack.back()->get()->is_a_parent_of(over))
+ can_tooltip=false;
+
+ }
+
+
+ if (can_tooltip) {
+
+ window->tooltip=over;
+ window->tooltip_pos=(parent_xform * get_transform()).affine_inverse().xform(pos);
+ window->tooltip_timer->start();
+ }
+ }
+
+
+ pos = window->focus_inv_xform.xform(pos);
+
+
+ p_event.mouse_motion.x = pos.x;
+ p_event.mouse_motion.y = pos.y;
+
+
+ CursorShape cursor_shape = over->get_cursor_shape(pos);
+ OS::get_singleton()->set_cursor_shape( (OS::CursorShape)cursor_shape );
+
+
+ if (over->can_process()) {
+ _window_call_input(over,p_event);
+ }
+
+
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ get_scene()->set_input_as_handled();
+
+
+ if (window->drag_data.get_type()!=Variant::NIL && p_event.mouse_motion.button_mask&BUTTON_MASK_LEFT) {
+
+ /*bool can_drop =*/ over->can_drop_data(pos,window->drag_data);
+ //change mouse accordingly i guess
+ }
+
+ } break;
+ case InputEvent::ACTION:
+ case InputEvent::JOYSTICK_BUTTON:
+ case InputEvent::KEY: {
+
+ if (window->key_focus) {
+
+ window->key_event_accepted=false;
+ if (window->key_focus->can_process()) {
+ window->key_focus->call_multilevel("_input_event",p_event);
+ if (window->key_focus) //maybe lost it
+ window->key_focus->emit_signal(SceneStringNames::get_singleton()->input_event,p_event);
+ }
+
+
+ if (window->key_event_accepted) {
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ break;
+ }
+ }
+
+
+ if (p_event.is_pressed() && p_event.is_action("ui_cancel") && !window->modal_stack.empty()) {
+
+ _window_sort_modal_stack();
+ Control *top = window->modal_stack.back()->get();
+ if (!top->data.modal_exclusive) {
+
+ top->notification(NOTIFICATION_MODAL_CLOSE);
+ top->_modal_stack_remove();
+ top->hide();
+ }
+ }
+
+
+ Control * from = window->key_focus ? window->key_focus : NULL; //hmm
+
+ //keyboard focus
+ //if (from && p_event.key.pressed && !p_event.key.mod.alt && !p_event.key.mod.meta && !p_event.key.mod.command) {
+
+ if (from && p_event.is_pressed()) {
+ Control * next=NULL;
+
+ if (p_event.is_action("ui_focus_next")) {
+
+ next = from->find_next_valid_focus();
+ }
+
+ if (p_event.is_action("ui_focus_prev")) {
+
+ next = from->find_prev_valid_focus();
+ }
+
+ if (p_event.is_action("ui_up")) {
+
+ next = from->_get_focus_neighbour(MARGIN_TOP);
+ }
+
+ if (p_event.is_action("ui_left")) {
+
+ next = from->_get_focus_neighbour(MARGIN_LEFT);
+ }
+
+ if (p_event.is_action("ui_right")) {
+
+ next = from->_get_focus_neighbour(MARGIN_RIGHT);
+ }
+
+ if (p_event.is_action("ui_down")) {
+
+ next = from->_get_focus_neighbour(MARGIN_BOTTOM);
+ }
+
+
+ if (next) {
+ next->grab_focus();
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_cancel_input_ID",p_event.ID);
+ }
+ }
+
+ } break;
+ }
+}
+
+Control *Control::get_window() const {
+
+ return data.window;
+}
+
+bool Control::is_window() const {
+
+ return (is_inside_scene() && window);
+}
+
+
+Size2 Control::get_minimum_size() const {
+
+ ScriptInstance *si = const_cast<Control*>(this)->get_script_instance();
+ if (si) {
+
+ Variant::CallError ce;
+ Variant s = si->call(SceneStringNames::get_singleton()->get_minimum_size,NULL,0,ce);
+ if (ce.error==Variant::CallError::CALL_OK)
+ return s;
+ }
+ return Size2();
+}
+
+
+Ref<Texture> Control::get_icon(const StringName& p_name,const StringName& p_type) const {
+
+ const Ref<Texture>* tex = data.icon_override.getptr(p_name);
+ if (tex)
+ return *tex;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_icon(p_name, type ) )
+ return data.theme_owner->data.theme->get_icon(p_name, type );
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->get_icon( p_name, type );
+
+}
+
+Ref<StyleBox> Control::get_stylebox(const StringName& p_name,const StringName& p_type) const {
+
+
+ const Ref<StyleBox>* style = data.style_override.getptr(p_name);
+
+
+ if (style)
+ return *style;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_stylebox(p_name, type ) )
+ return data.theme_owner->data.theme->get_stylebox(p_name, type );
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+ }
+
+ return Theme::get_default()->get_stylebox( p_name, type );
+
+}
+Ref<Font> Control::get_font(const StringName& p_name,const StringName& p_type) const {
+
+ const Ref<Font>* font = data.font_override.getptr(p_name);
+ if (font)
+ return *font;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_font(p_name, type ) )
+ return data.theme_owner->data.theme->get_font(p_name, type );
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->get_font( p_name, type );
+
+}
+Color Control::get_color(const StringName& p_name,const StringName& p_type) const {
+
+ const Color* color = data.color_override.getptr(p_name);
+ if (color)
+ return *color;
+
+ StringName type = p_type?p_type:get_type_name();
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_color(p_name, type ) )
+ return data.theme_owner->data.theme->get_color(p_name, type );
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->get_color( p_name, type );
+
+}
+
+int Control::get_constant(const StringName& p_name,const StringName& p_type) const {
+
+ const int* constant = data.constant_override.getptr(p_name);
+ if (constant)
+ return *constant;
+
+ StringName type = p_type?p_type:get_type_name();
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_constant(p_name, type ) )
+ return data.theme_owner->data.theme->get_constant(p_name, type );
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->get_constant( p_name, type );
+
+
+}
+
+
+bool Control::has_icon(const StringName& p_name,const StringName& p_type) const {
+
+ const Ref<Texture>* tex = data.icon_override.getptr(p_name);
+ if (tex)
+ return true;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_icon(p_name, type ) )
+ return true;
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->has_icon( p_name, type );
+
+}
+bool Control::has_stylebox(const StringName& p_name,const StringName& p_type) const {
+
+
+ const Ref<StyleBox>* style = data.style_override.getptr(p_name);
+
+ if (style)
+ return true;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_stylebox(p_name, type ) )
+ return true;
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->has_stylebox( p_name, type );
+
+}
+bool Control::has_font(const StringName& p_name,const StringName& p_type) const {
+
+ const Ref<Font>* font = data.font_override.getptr(p_name);
+ if (font)
+ return true;
+
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_font(p_name, type ) )
+ return true;
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->has_font( p_name, type );
+
+}
+bool Control::has_color(const StringName& p_name,const StringName& p_type) const {
+
+ const Color* color = data.color_override.getptr(p_name);
+ if (color)
+ return true;
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_color(p_name, type ) )
+ return true;
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->has_color( p_name, type );
+
+}
+
+bool Control::has_constant(const StringName& p_name,const StringName& p_type) const {
+
+ const int* constant = data.constant_override.getptr(p_name);
+ if (constant)
+ return true;
+
+
+ StringName type = p_type?p_type:get_type_name();
+
+ // try with custom themes
+ Control *theme_owner = data.theme_owner;
+
+ while(theme_owner) {
+
+ if (theme_owner->data.theme->has_constant(p_name, type ) )
+ return true;
+ Control *parent = theme_owner->get_parent()?theme_owner->get_parent()->cast_to<Control>():NULL;
+
+ if (parent)
+ theme_owner=parent->data.theme_owner;
+ else
+ theme_owner=NULL;
+
+ }
+
+ return Theme::get_default()->has_constant( p_name, type );
+}
+
+Size2 Control::get_parent_area_size() const {
+
+ ERR_FAIL_COND_V(!is_inside_scene(),Size2());
+
+ Size2 parent_size;
+
+ if (data.parent_canvas_item) {
+
+ parent_size=data.parent_canvas_item->get_item_rect().size;
+ } else if (data.viewport) {
+
+ parent_size=data.viewport->get_visible_rect().size;
+ }
+ return parent_size;
+
+}
+
+void Control::_size_changed() {
+
+ if (!is_inside_scene())
+ return;
+
+ Size2 parent_size = get_parent_area_size();
+
+ float margin_pos[4];
+
+ for(int i=0;i<4;i++) {
+
+ float area = parent_size[i&1];
+ switch(data.anchor[i]) {
+
+ case ANCHOR_BEGIN: {
+
+ margin_pos[i]=data.margin[i];
+ } break;
+ case ANCHOR_END: {
+
+ margin_pos[i]=area-data.margin[i];
+ } break;
+ case ANCHOR_RATIO: {
+
+ margin_pos[i]=area*data.margin[i];
+ } break;
+ }
+ }
+
+ Point2 new_pos_cache=Point2(margin_pos[0],margin_pos[1]).floor();
+ Size2 new_size_cache=Point2(margin_pos[2],margin_pos[3]).floor()-new_pos_cache;
+ Size2 minimum_size=get_combined_minimum_size();
+
+ new_size_cache.x = MAX( minimum_size.x, new_size_cache.x );
+ new_size_cache.y = MAX( minimum_size.y, new_size_cache.y );
+
+
+ if (new_pos_cache == data.pos_cache && new_size_cache == data.size_cache)
+ return; // did not change, don't emit signal
+
+ data.pos_cache=new_pos_cache;
+ data.size_cache=new_size_cache;
+
+ notification(NOTIFICATION_RESIZED);
+ item_rect_changed();
+ _change_notify_margins();
+ _notify_transform();
+}
+
+float Control::_get_parent_range(int p_idx) const {
+
+ if (!is_inside_scene()) {
+
+ return 1.0;
+
+ } if (data.parent_canvas_item) {
+
+ return data.parent_canvas_item->get_item_rect().size[p_idx&1];
+ } else if (data.viewport) {
+ return data.viewport->get_visible_rect().size[p_idx&1];
+ }
+
+ return 1.0;
+}
+
+
+float Control::_get_range(int p_idx) const {
+
+ p_idx&=1;
+
+ float parent_range = _get_parent_range( p_idx );
+ float from = _a2s( data.margin[p_idx], data.anchor[p_idx], parent_range );
+ float to = _a2s( data.margin[p_idx+2], data.anchor[p_idx+2], parent_range );
+
+ return to-from;
+}
+
+float Control::_s2a(float p_val, AnchorType p_anchor,float p_range) const {
+
+ switch(p_anchor) {
+
+ case ANCHOR_BEGIN: {
+ return p_val;
+ } break;
+ case ANCHOR_END: {
+ return p_range-p_val;
+ } break;
+ case ANCHOR_RATIO: {
+ return p_val/p_range;
+ } break;
+ }
+
+ return 0;
+}
+
+
+float Control::_a2s(float p_val, AnchorType p_anchor,float p_range) const {
+
+ switch(p_anchor) {
+
+ case ANCHOR_BEGIN: {
+ return Math::floor(p_val);
+ } break;
+ case ANCHOR_END: {
+ return Math::floor(p_range-p_val);
+ } break;
+ case ANCHOR_RATIO: {
+ return Math::floor(p_range*p_val);
+ } break;
+ }
+ return 0;
+}
+
+
+void Control::set_anchor(Margin p_margin,AnchorType p_anchor) {
+
+ if (!is_inside_scene()) {
+
+ data.anchor[p_margin]=p_anchor;
+ } else {
+ float pr = _get_parent_range(p_margin);
+ float s = _a2s( data.margin[p_margin], data.anchor[p_margin], pr );
+ data.anchor[p_margin]=p_anchor;
+ data.margin[p_margin] = _s2a( s, p_anchor, pr );
+ }
+ _change_notify();
+}
+
+void Control::set_anchor_and_margin(Margin p_margin,AnchorType p_anchor, float p_pos) {
+
+ set_anchor(p_margin,p_anchor);
+ set_margin(p_margin,p_pos);
+}
+
+
+Control::AnchorType Control::get_anchor(Margin p_margin) const {
+
+ return data.anchor[p_margin];
+}
+
+
+
+
+
+void Control::_change_notify_margins() {
+
+ // this avoids sending the whole object data again on a change
+ _change_notify("margin/left");
+ _change_notify("margin/top");
+ _change_notify("margin/right");
+ _change_notify("margin/bottom");
+ _change_notify("rect/pos");
+ _change_notify("rect/size");
+
+}
+
+
+void Control::set_margin(Margin p_margin,float p_value) {
+
+ data.margin[p_margin]=p_value;
+ _size_changed();
+
+}
+
+void Control::set_begin(const Size2& p_point) {
+
+ data.margin[0]=p_point.x;
+ data.margin[1]=p_point.y;
+ _size_changed();
+}
+
+void Control::set_end(const Size2& p_point) {
+
+ data.margin[2]=p_point.x;
+ data.margin[3]=p_point.y;
+ _size_changed();
+}
+
+float Control::get_margin(Margin p_margin) const {
+
+ return data.margin[p_margin];
+}
+
+Size2 Control::get_begin() const {
+
+ return Size2( data.margin[0], data.margin[1] );
+}
+Size2 Control::get_end() const {
+
+ return Size2( data.margin[2], data.margin[3] );
+}
+
+Point2 Control::get_global_pos() const {
+
+ return get_global_transform().get_origin();
+}
+
+void Control::set_global_pos(const Point2& p_point) {
+
+ Matrix32 inv;
+
+ if (data.parent_canvas_item) {
+
+ inv = data.parent_canvas_item->get_global_transform().affine_inverse();
+ }
+
+ set_pos(inv.xform(p_point));
+}
+
+void Control::set_pos(const Size2& p_point) {
+
+ float pw = _get_parent_range(0);
+ float ph = _get_parent_range(1);
+
+ float x = _a2s( data.margin[0], data.anchor[0], pw );
+ float y = _a2s( data.margin[1], data.anchor[1], ph );
+ float x2 = _a2s( data.margin[2], data.anchor[2], pw );
+ float y2 = _a2s( data.margin[3], data.anchor[3], ph );
+
+ Size2 ret = Size2(x2-x,y2-y);
+ Size2 min = get_combined_minimum_size();
+
+ Size2 size = Size2(MAX( min.width, ret.width),MAX( min.height, ret.height));
+ float w=size.x;
+ float h=size.y;
+
+ x=p_point.x;
+ y=p_point.y;
+
+ data.margin[0] = _s2a( x, data.anchor[0], pw );
+ data.margin[1] = _s2a( y, data.anchor[1], ph );
+ data.margin[2] = _s2a( x+w, data.anchor[2], pw );
+ data.margin[3] = _s2a( y+h, data.anchor[3], ph );
+
+ _size_changed();
+}
+
+void Control::set_size(const Size2& p_size) {
+
+ Size2 new_size=p_size;
+ Size2 min=get_combined_minimum_size();
+ if (new_size.x<min.x)
+ new_size.x=min.x;
+ if (new_size.y<min.y)
+ new_size.y=min.y;
+
+ float pw = _get_parent_range(0);
+ float ph = _get_parent_range(1);
+
+ float x = _a2s( data.margin[0], data.anchor[0], pw );
+ float y = _a2s( data.margin[1], data.anchor[1], ph );
+
+ float w=new_size.width;
+ float h=new_size.height;
+
+ data.margin[2] = _s2a( x+w, data.anchor[2], pw );
+ data.margin[3] = _s2a( y+h, data.anchor[3], ph );
+
+ _size_changed();
+}
+
+
+Size2 Control::get_pos() const {
+
+ return data.pos_cache;
+}
+
+Size2 Control::get_size() const {
+
+ return data.size_cache;
+}
+
+Rect2 Control::get_global_rect() const {
+
+ return Rect2( get_global_pos(), get_size() );
+}
+
+Rect2 Control::get_window_rect() const {
+
+ Rect2 gr = get_global_rect();
+ if (data.viewport)
+ gr.pos+=data.viewport->get_visible_rect().pos;
+ return gr;
+}
+
+
+Rect2 Control::get_rect() const {
+
+ return Rect2(get_pos(),get_size());
+}
+
+Rect2 Control::get_item_rect() const {
+
+ return Rect2(Point2(),get_size());
+}
+
+void Control::set_area_as_parent_rect(int p_margin) {
+
+ data.anchor[MARGIN_LEFT]=ANCHOR_BEGIN;
+ data.anchor[MARGIN_TOP]=ANCHOR_BEGIN;
+ data.anchor[MARGIN_RIGHT]=ANCHOR_END;
+ data.anchor[MARGIN_BOTTOM]=ANCHOR_END;
+ for(int i=0;i<4;i++)
+ data.margin[i]=p_margin;
+
+ _size_changed();
+
+}
+
+void Control::add_icon_override(const StringName& p_name, const Ref<Texture>& p_icon) {
+
+ ERR_FAIL_COND(p_icon.is_null());
+ data.icon_override[p_name]=p_icon;
+ notification(NOTIFICATION_THEME_CHANGED);
+ update();
+
+}
+void Control::add_style_override(const StringName& p_name, const Ref<StyleBox>& p_style) {
+
+ ERR_FAIL_COND(p_style.is_null());
+ data.style_override[p_name]=p_style;
+ notification(NOTIFICATION_THEME_CHANGED);
+ update();
+}
+
+
+void Control::add_font_override(const StringName& p_name, const Ref<Font>& p_font) {
+
+ ERR_FAIL_COND(p_font.is_null());
+ data.font_override[p_name]=p_font;
+ notification(NOTIFICATION_THEME_CHANGED);
+ update();
+}
+void Control::add_color_override(const StringName& p_name, const Color& p_color) {
+
+ data.color_override[p_name]=p_color;
+ notification(NOTIFICATION_THEME_CHANGED);
+ update();
+}
+void Control::add_constant_override(const StringName& p_name, int p_constant) {
+
+ data.constant_override[p_name]=p_constant;
+ notification(NOTIFICATION_THEME_CHANGED);
+ update();
+}
+
+void Control::set_focus_mode(FocusMode p_focus_mode) {
+
+ if (is_inside_scene() && p_focus_mode == FOCUS_NONE && data.focus_mode!=FOCUS_NONE && has_focus())
+ release_focus();
+
+ data.focus_mode=p_focus_mode;
+
+}
+
+static Control *_next_control(Control *p_from) {
+
+ if (p_from->is_set_as_toplevel())
+ return NULL; // can't go above
+
+ Control *parent = p_from->get_parent()?p_from->get_parent()->cast_to<Control>():NULL;
+
+ if (!parent) {
+
+ return NULL;
+ }
+
+
+ int next = p_from->get_position_in_parent();
+ ERR_FAIL_INDEX_V(next,parent->get_child_count(),NULL);
+ for(int i=(next+1);i<parent->get_child_count();i++) {
+
+ Control *c = parent->get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible() || c->is_set_as_toplevel())
+ continue;
+
+ return c;
+ }
+
+ //no next in parent, try the same in parent
+ return _next_control(parent);
+}
+
+Control *Control::find_next_valid_focus() const {
+
+ Control *from = const_cast<Control*>(this);
+
+ while(true) {
+
+
+ // find next child
+
+ Control *next_child=NULL;
+
+
+ for(int i=0;i<from->get_child_count();i++) {
+
+ Control *c = from->get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible() || c->is_set_as_toplevel()) {
+ continue;
+ }
+
+ next_child=c;
+ break;
+ }
+
+ if (next_child) {
+
+ from = next_child;
+ } else {
+
+ next_child=_next_control(from);
+ if (!next_child) { //nothing else.. go up and find either window or subwindow
+ next_child=const_cast<Control*>(this);
+ while(next_child && !next_child->is_set_as_toplevel()) {
+ if (next_child->get_parent()) {
+ next_child=next_child->get_parent()->cast_to<Control>();
+ } else
+ next_child=NULL;
+
+ }
+
+ if (!next_child) {
+ next_child=get_window();
+ }
+ }
+
+ }
+
+
+ if (next_child==this) // no next control->
+ return (get_focus_mode()==FOCUS_ALL)?next_child:NULL;
+
+ if (next_child->get_focus_mode()==FOCUS_ALL)
+ return next_child;
+
+ from = next_child;
+ }
+
+ return NULL;
+
+
+}
+
+
+
+
+static Control *_prev_control(Control *p_from) {
+
+
+ Control *child=NULL;
+ for(int i=p_from->get_child_count()-1;i>=0;i--) {
+
+ Control *c = p_from->get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible() || c->is_set_as_toplevel())
+ continue;
+
+ child=c;
+ break;
+ }
+
+ if (!child)
+ return p_from;
+
+ //no prev in parent, try the same in parent
+ return _prev_control(child);
+}
+
+Control *Control::find_prev_valid_focus() const {
+ Control *from = const_cast<Control*>(this);
+
+ while(true) {
+
+
+ // find prev child
+
+
+ Control *prev_child = NULL;
+
+
+
+ if ( from->is_set_as_toplevel() || !from->get_parent() || !from->get_parent()->cast_to<Control>()) {
+
+ //find last of the childs
+
+ prev_child=_prev_control(from);
+
+ } else {
+
+ for(int i=(from->get_position_in_parent()-1);i>=0;i--) {
+
+
+ Control *c = from->get_parent()->get_child(i)->cast_to<Control>();
+
+ if (!c || !c->is_visible() || c->is_set_as_toplevel()) {
+ continue;
+ }
+
+ prev_child=c;
+ break;
+
+ }
+
+ if (!prev_child) {
+
+
+
+ prev_child = from->get_parent()->cast_to<Control>();
+ } else {
+
+
+
+ prev_child = _prev_control(prev_child);
+ }
+ }
+
+
+
+
+ if (prev_child==this) // no prev control->
+ return (get_focus_mode()==FOCUS_ALL)?prev_child:NULL;
+
+ if (prev_child->get_focus_mode()==FOCUS_ALL)
+ return prev_child;
+
+ from = prev_child;
+
+ }
+
+ return NULL;
+
+ return NULL;
+}
+
+
+Control::FocusMode Control::get_focus_mode() const {
+
+ return data.focus_mode;
+}
+bool Control::has_focus() const {
+
+ return (data.window && data.window->window->key_focus==this);
+}
+
+void Control::grab_focus() {
+
+ ERR_FAIL_COND(!is_inside_scene());
+ ERR_FAIL_COND(!data.window);
+
+ if (data.focus_mode==FOCUS_NONE)
+ return;
+
+ //no need for change
+ if (data.window->window->key_focus && data.window->window->key_focus==this)
+ return;
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_window_remove_focus");
+ data.window->window->key_focus=this;
+ notification(NOTIFICATION_FOCUS_ENTER);
+#ifdef DEBUG_ENABLED
+ if (GLOBAL_DEF("debug/print_clicked_control", false)) {
+ print_line(String(get_path())+" - focus");
+ };
+#endif
+ update();
+
+}
+
+void Control::release_focus() {
+
+ ERR_FAIL_COND(!is_inside_scene());
+ ERR_FAIL_COND(!data.window);
+
+ if (!has_focus())
+ return;
+
+ get_scene()->call_group(SceneMainLoop::GROUP_CALL_REALTIME,"windows","_window_remove_focus");
+ //data.window->window->key_focus=this;
+ //notification(NOTIFICATION_FOCUS_ENTER);
+ update();
+
+}
+
+bool Control::is_toplevel_control() const {
+
+ return is_inside_scene() && (!data.parent_canvas_item && !window && is_set_as_toplevel());
+}
+
+void Control::show_modal(bool p_exclusive) {
+
+ ERR_FAIL_COND(!is_inside_scene());
+ ERR_FAIL_COND(!data.SI && data.window!=this);
+ ERR_FAIL_COND(!data.window);
+
+ if (is_visible())
+ hide();
+
+ ERR_FAIL_COND( data.MI );
+ show();
+ raise();
+
+ data.window->window->modal_stack.push_back(this);
+ data.MI = data.window->window->modal_stack.back();
+ data.modal_exclusive=p_exclusive;
+ if (data.window->window->key_focus)
+ data.modal_prev_focus_owner = data.window->window->key_focus->get_instance_ID();
+ else
+ data.modal_prev_focus_owner=0;
+
+}
+
+void Control::_window_sort_subwindows() {
+
+ if (!window->subwindow_order_dirty)
+ return;
+
+ window->modal_stack.sort_custom<CComparator>();
+ window->subwindow_order_dirty=false;
+
+}
+
+void Control::_window_sort_modal_stack() {
+
+ window->modal_stack.sort_custom<CComparator>();
+}
+
+void Control::_modal_stack_remove() {
+
+
+ List<Control*>::Element *next=NULL; //transfer the focus stack to the next
+
+
+ if (data.window && data.MI) {
+
+ next = data.MI->next();
+
+
+ data.window->window->modal_stack.erase(data.MI);
+ data.MI=NULL;
+ }
+
+ if (data.modal_prev_focus_owner) {
+
+ if (!next) { //top of stack
+
+ Object *pfo = ObjectDB::get_instance(data.modal_prev_focus_owner);
+ Control *pfoc = pfo->cast_to<Control>();
+ if (!pfoc)
+ return;
+
+ if (!pfoc->is_inside_scene() || !pfoc->is_visible())
+ return;
+ pfoc->grab_focus();
+ } else {
+
+ next->get()->data.modal_prev_focus_owner=data.modal_prev_focus_owner;
+ }
+
+ data.modal_prev_focus_owner=0;
+ }
+}
+
+void Control::_propagate_theme_changed(Control *p_owner) {
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *child = get_child(i)->cast_to<Control>();
+ if (child && child->data.theme.is_null()) //has no theme, propagate
+ child->_propagate_theme_changed(p_owner);
+ }
+
+ data.theme_owner=p_owner;
+ _notification(NOTIFICATION_THEME_CHANGED);
+ update();
+}
+
+void Control::set_theme(const Ref<Theme>& p_theme) {
+
+ data.theme=p_theme;
+ if (!p_theme.is_null()) {
+
+ _propagate_theme_changed(this);
+ } else {
+
+ Control *parent = get_parent()?get_parent()->cast_to<Control>():NULL;
+ if (parent && parent->data.theme_owner) {
+ _propagate_theme_changed(parent->data.theme_owner);
+ } else {
+
+ _propagate_theme_changed(NULL);
+ }
+
+ }
+
+
+}
+
+void Control::_window_accept_event() {
+
+ window->key_event_accepted=true;
+ if (is_inside_scene())
+ get_scene()->set_input_as_handled();
+
+}
+void Control::accept_event() {
+
+ if (is_inside_scene() && get_window())
+ get_window()->_window_accept_event();
+
+}
+
+Ref<Theme> Control::get_theme() const {
+
+ return data.theme;
+}
+
+void Control::set_tooltip(const String& p_tooltip) {
+
+ data.tooltip=p_tooltip;
+}
+String Control::get_tooltip(const Point2& p_pos) const {
+
+ return data.tooltip;
+}
+
+void Control::set_default_cursor_shape(CursorShape p_shape) {
+
+ data.default_cursor=p_shape;
+}
+
+Control::CursorShape Control::get_default_cursor_shape() const {
+
+ return data.default_cursor;
+}
+Control::CursorShape Control::get_cursor_shape(const Point2& p_pos) const {
+
+ return data.default_cursor;
+}
+
+Matrix32 Control::get_transform() const {
+
+ Matrix32 xf;
+ xf.set_origin(get_pos());
+ return xf;
+}
+
+String Control::_get_tooltip() const {
+
+ return data.tooltip;
+}
+
+void Control::set_focus_neighbour(Margin p_margin, const NodePath &p_neighbour) {
+
+ ERR_FAIL_INDEX(p_margin,4);
+ data.focus_neighbour[p_margin]=p_neighbour;
+}
+
+NodePath Control::get_focus_neighbour(Margin p_margin) const {
+
+ ERR_FAIL_INDEX_V(p_margin,4,NodePath());
+ return data.focus_neighbour[p_margin];
+}
+
+#define MAX_NEIGHBOUR_SEARCH_COUNT 512
+
+Control *Control::_get_focus_neighbour(Margin p_margin,int p_count) {
+
+ if (p_count >= MAX_NEIGHBOUR_SEARCH_COUNT)
+ return NULL;
+ if (!data.focus_neighbour[p_margin].is_empty()) {
+
+ Control *c=NULL;
+ Node * n = get_node(data.focus_neighbour[p_margin]);
+ if (n) {
+ c=n->cast_to<Control>();
+
+ if (!c) {
+
+ ERR_EXPLAIN("Next focus node is not a control: "+n->get_name());
+ ERR_FAIL_V(NULL);
+ }
+ } else {
+ return NULL;
+ }
+ bool valid=true;
+ if (c->is_hidden())
+ valid=false;
+ if (c->get_focus_mode()==FOCUS_NONE)
+ valid=false;
+ if (valid)
+ return c;
+
+ c=c->_get_focus_neighbour(p_margin,p_count+1);
+ return c;
+ }
+
+
+ float dist=1e7;
+ Control * result=NULL;
+
+ Point2 points[4];
+
+ Matrix32 xform = get_global_transform();
+ Rect2 rect = get_item_rect();
+
+ points[0]=xform.xform(rect.pos);
+ points[1]=xform.xform(rect.pos + Point2(rect.size.x, 0));
+ points[2]=xform.xform(rect.pos + rect.size);
+ points[3]=xform.xform(rect.pos + Point2(0, rect.size.y));
+
+ const Vector2 dir[4]={
+ Vector2(-1,0),
+ Vector2(0,-1),
+ Vector2(1,0),
+ Vector2(0,1)
+ };
+
+ Vector2 vdir=dir[p_margin];
+
+ float maxd=-1e7;
+
+ for(int i=0;i<4;i++) {
+
+ float d = vdir.dot(points[i]);
+ if (d>maxd)
+ maxd=d;
+ }
+
+ Node *base=this;
+
+ while (base) {
+
+ Control *c = base->cast_to<Control>();
+ if (c) {
+ if (c->data.SI)
+ break;
+ if (c==data.window)
+ break;
+ }
+ base=base->get_parent();
+ }
+
+ if (!base)
+ return NULL;
+
+ _window_find_focus_neighbour(vdir,base,points,maxd,dist,&result);
+
+ return result;
+
+}
+
+void Control::_window_find_focus_neighbour(const Vector2& p_dir, Node *p_at,const Point2* p_points,float p_min ,float &r_closest_dist,Control **r_closest) {
+
+ if (p_at->cast_to<Viewport>())
+ return; //bye
+
+ Control *c = p_at->cast_to<Control>();
+
+ if (c && c !=this && c->get_focus_mode()==FOCUS_ALL && c->is_visible()) {
+
+ Point2 points[4];
+
+ Matrix32 xform = c->get_global_transform();
+ Rect2 rect = c->get_item_rect();
+
+ points[0]=xform.xform(rect.pos);
+ points[1]=xform.xform(rect.pos + Point2(rect.size.x, 0));
+ points[2]=xform.xform(rect.pos + rect.size);
+ points[3]=xform.xform(rect.pos + Point2(0, rect.size.y));
+
+
+ float min=1e7;
+
+ for(int i=0;i<4;i++) {
+
+ float d = p_dir.dot(points[i]);
+ if (d < min)
+ min =d;
+ }
+
+
+ if (min>(p_min-CMP_EPSILON)) {
+
+ for(int i=0;i<4;i++) {
+
+ Vector2 la=p_points[i];
+ Vector2 lb=p_points[(i+1)%4];
+
+ for(int j=0;j<4;j++) {
+
+ Vector2 fa=points[j];
+ Vector2 fb=points[(j+1)%4];
+
+ Vector2 pa,pb;
+ float d=Geometry::get_closest_points_between_segments(la,lb,fa,fb,pa,pb);
+ //float d = Geometry::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0));
+ if (d<r_closest_dist) {
+ r_closest_dist=d;
+ *r_closest=c;
+ }
+ }
+ }
+ }
+
+ }
+
+ for(int i=0;i<p_at->get_child_count();i++) {
+
+ Node *child=p_at->get_child(i);
+ Control *childc = child->cast_to<Control>();
+ if (childc && childc->data.SI)
+ continue; //subwindow, ignore
+ _window_find_focus_neighbour(p_dir,p_at->get_child(i),p_points,p_min,r_closest_dist,r_closest);
+ }
+}
+
+void Control::set_h_size_flags(int p_flags) {
+
+ if (data.h_size_flags==p_flags)
+ return;
+ data.h_size_flags=p_flags;
+ emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+}
+
+int Control::get_h_size_flags() const{
+ return data.h_size_flags;
+}
+void Control::set_v_size_flags(int p_flags) {
+
+ if (data.v_size_flags==p_flags)
+ return;
+ data.v_size_flags=p_flags;
+ emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+}
+
+
+void Control::set_stretch_ratio(float p_ratio) {
+
+ if (data.expand==p_ratio)
+ return;
+
+ data.expand=p_ratio;
+ emit_signal(SceneStringNames::get_singleton()->size_flags_changed);
+}
+
+float Control::get_stretch_ratio() const {
+
+ return data.expand;
+}
+
+
+void Control::grab_click_focus() {
+
+ ERR_FAIL_COND(!is_inside_scene());
+
+ if (data.window && data.window->window->mouse_focus) {
+
+ Window *w=data.window->window;
+ if (w->mouse_focus==this)
+ return;
+ InputEvent ie;
+ ie.type=InputEvent::MOUSE_BUTTON;
+ InputEventMouseButton &mb=ie.mouse_button;
+
+ //send unclic
+
+ Point2 click =w->mouse_focus->get_global_transform().affine_inverse().xform(w->last_mouse_pos);
+ mb.x=click.x;
+ mb.y=click.y;
+ mb.button_index=w->mouse_focus_button;
+ mb.pressed=false;
+ w->mouse_focus->call_deferred("_input_event",ie);
+
+
+ w->mouse_focus=this;
+ w->focus_inv_xform=w->mouse_focus->get_global_transform().affine_inverse();
+ click =w->mouse_focus->get_global_transform().affine_inverse().xform(w->last_mouse_pos);
+ mb.x=click.x;
+ mb.y=click.y;
+ mb.button_index=w->mouse_focus_button;
+ mb.pressed=true;
+ w->mouse_focus->call_deferred("_input_event",ie);
+
+ }
+}
+
+void Control::minimum_size_changed() {
+
+ if (!is_inside_scene())
+ return;
+
+ if (data.pending_min_size_update)
+ return;
+
+
+ data.pending_min_size_update=true;
+ MessageQueue::get_singleton()->push_call(this,"_update_minimum_size");
+
+ if (!is_toplevel_control()) {
+ Control *pc = get_parent_control();
+ if (pc)
+ pc->minimum_size_changed();
+ }
+}
+
+int Control::get_v_size_flags() const{
+ return data.v_size_flags;
+}
+
+void Control::set_ignore_mouse(bool p_ignore) {
+
+ data.ignore_mouse=p_ignore;
+}
+
+bool Control::is_ignoring_mouse() const {
+
+ return data.ignore_mouse;
+}
+
+void Control::set_stop_mouse(bool p_stop) {
+
+ data.stop_mouse=p_stop;
+}
+
+bool Control::is_stopping_mouse() const {
+
+ return data.stop_mouse;
+}
+
+Control *Control::get_focus_owner() const {
+
+ ERR_FAIL_COND_V(!is_inside_scene(),NULL);
+ ERR_FAIL_COND_V(!window,NULL);
+ return window->key_focus;
+}
+
+void Control::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_window_input_event"),&Control::_window_input_event);
+ ObjectTypeDB::bind_method(_MD("_gui_input"),&Control::_gui_input);
+ ObjectTypeDB::bind_method(_MD("_input_text"),&Control::_input_text);
+// ObjectTypeDB::bind_method(_MD("_window_resize_event"),&Control::_window_resize_event);
+ ObjectTypeDB::bind_method(_MD("_window_remove_focus"),&Control::_window_remove_focus);
+ ObjectTypeDB::bind_method(_MD("_cancel_input_ID"),&Control::_window_cancel_input_ID);
+ ObjectTypeDB::bind_method(_MD("_cancel_tooltip"),&Control::_window_cancel_tooltip);
+ ObjectTypeDB::bind_method(_MD("_window_show_tooltip"),&Control::_window_show_tooltip);
+ ObjectTypeDB::bind_method(_MD("_size_changed"),&Control::_size_changed);
+ ObjectTypeDB::bind_method(_MD("_update_minimum_size"),&Control::_update_minimum_size);
+
+ ObjectTypeDB::bind_method(_MD("accept_event"),&Control::accept_event);
+ ObjectTypeDB::bind_method(_MD("get_minimum_size"),&Control::get_minimum_size);
+ ObjectTypeDB::bind_method(_MD("get_combined_minimum_size"),&Control::get_combined_minimum_size);
+ ObjectTypeDB::bind_method(_MD("is_window"),&Control::is_window);
+ ObjectTypeDB::bind_method(_MD("get_window"),&Control::get_window);
+ ObjectTypeDB::bind_method(_MD("set_anchor","margin","anchor_mode"),&Control::set_anchor);
+ ObjectTypeDB::bind_method(_MD("get_anchor","margin"),&Control::get_anchor);
+ ObjectTypeDB::bind_method(_MD("set_margin","margin","offset"),&Control::set_margin);
+ ObjectTypeDB::bind_method(_MD("set_anchor_and_margin","margin","anchor_mode","offset"),&Control::set_anchor_and_margin);
+ ObjectTypeDB::bind_method(_MD("set_begin","pos"),&Control::set_begin);
+ ObjectTypeDB::bind_method(_MD("set_end","pos"),&Control::set_end);
+ ObjectTypeDB::bind_method(_MD("set_pos","pos"),&Control::set_pos);
+ ObjectTypeDB::bind_method(_MD("set_size","size"),&Control::set_size);
+ ObjectTypeDB::bind_method(_MD("set_custom_minimum_size","size"),&Control::set_custom_minimum_size);
+ ObjectTypeDB::bind_method(_MD("set_global_pos","pos"),&Control::set_global_pos);
+ ObjectTypeDB::bind_method(_MD("get_margin","margin"),&Control::get_margin);
+ ObjectTypeDB::bind_method(_MD("get_begin"),&Control::get_begin);
+ ObjectTypeDB::bind_method(_MD("get_end"),&Control::get_end);
+ ObjectTypeDB::bind_method(_MD("get_pos"),&Control::get_pos);
+ ObjectTypeDB::bind_method(_MD("get_size"),&Control::get_size);
+ ObjectTypeDB::bind_method(_MD("get_custom_minimum_size"),&Control::get_custom_minimum_size);
+ ObjectTypeDB::bind_method(_MD("get_parent_area_size"),&Control::get_size);
+ ObjectTypeDB::bind_method(_MD("get_global_pos"),&Control::get_global_pos);
+ ObjectTypeDB::bind_method(_MD("get_rect"),&Control::get_rect);
+ ObjectTypeDB::bind_method(_MD("get_global_rect"),&Control::get_global_rect);
+ ObjectTypeDB::bind_method(_MD("set_area_as_parent_rect","margin"),&Control::set_area_as_parent_rect,DEFVAL(0));
+ ObjectTypeDB::bind_method(_MD("show_modal","exclusive"),&Control::show_modal,DEFVAL(false));
+ ObjectTypeDB::bind_method(_MD("set_focus_mode","mode"),&Control::set_focus_mode);
+ ObjectTypeDB::bind_method(_MD("has_focus"),&Control::has_focus);
+ ObjectTypeDB::bind_method(_MD("grab_focus"),&Control::grab_focus);
+ ObjectTypeDB::bind_method(_MD("release_focus"),&Control::release_focus);
+ ObjectTypeDB::bind_method(_MD("get_focus_owner:Control"),&Control::get_focus_owner);
+
+ ObjectTypeDB::bind_method(_MD("set_h_size_flags","flags"),&Control::set_h_size_flags);
+ ObjectTypeDB::bind_method(_MD("get_h_size_flags"),&Control::get_h_size_flags);
+
+ ObjectTypeDB::bind_method(_MD("set_stretch_ratio","ratio"),&Control::set_stretch_ratio);
+ ObjectTypeDB::bind_method(_MD("get_stretch_ratio"),&Control::get_stretch_ratio);
+
+ ObjectTypeDB::bind_method(_MD("set_v_size_flags","flags"),&Control::set_v_size_flags);
+ ObjectTypeDB::bind_method(_MD("get_v_size_flags"),&Control::get_v_size_flags);
+
+ ObjectTypeDB::bind_method(_MD("set_theme","theme:Theme"),&Control::set_theme);
+ ObjectTypeDB::bind_method(_MD("get_theme:Theme"),&Control::get_theme);
+
+ ObjectTypeDB::bind_method(_MD("add_icon_override","name","texture:Texture"),&Control::add_icon_override);
+ ObjectTypeDB::bind_method(_MD("add_style_override","name","stylebox:StyleBox"),&Control::add_style_override);
+ ObjectTypeDB::bind_method(_MD("add_font_override","name","font:Font"),&Control::add_font_override);
+ ObjectTypeDB::bind_method(_MD("add_color_override","name","color"),&Control::add_color_override);
+ ObjectTypeDB::bind_method(_MD("add_constant_override","name","constant"),&Control::add_constant_override);
+
+ ObjectTypeDB::bind_method(_MD("get_icon:Texture","name","type"),&Control::get_icon,DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("get_stylebox:StyleBox","name","type"),&Control::get_stylebox,DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("get_font:Font","name","type"),&Control::get_font,DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("get_color","name","type"),&Control::get_color,DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("get_constant","name","type"),&Control::get_constant,DEFVAL(""));
+
+
+ ObjectTypeDB::bind_method(_MD("get_parent_control:Control"),&Control::get_parent_control);
+
+ ObjectTypeDB::bind_method(_MD("set_tooltip","tooltip"),&Control::set_tooltip);
+ ObjectTypeDB::bind_method(_MD("get_tooltip","atpos"),&Control::get_tooltip,DEFVAL(Point2()));
+ ObjectTypeDB::bind_method(_MD("_get_tooltip"),&Control::_get_tooltip);
+
+ ObjectTypeDB::bind_method(_MD("set_default_cursor_shape","shape"),&Control::set_default_cursor_shape);
+ ObjectTypeDB::bind_method(_MD("get_default_cursor_shape"),&Control::get_default_cursor_shape);
+ ObjectTypeDB::bind_method(_MD("get_cursor_shape","pos"),&Control::get_cursor_shape,DEFVAL(Point2()));
+
+ ObjectTypeDB::bind_method(_MD("set_focus_neighbour","margin","neighbour"),&Control::set_focus_neighbour);
+ ObjectTypeDB::bind_method(_MD("get_focus_neighbour","margin"),&Control::get_focus_neighbour);
+
+ ObjectTypeDB::bind_method(_MD("set_ignore_mouse","ignore"),&Control::set_ignore_mouse);
+ ObjectTypeDB::bind_method(_MD("is_ignoring_mouse"),&Control::is_ignoring_mouse);
+
+ ObjectTypeDB::bind_method(_MD("force_drag","data","preview"),&Control::force_drag);
+
+ ObjectTypeDB::bind_method(_MD("set_stop_mouse","stop"),&Control::set_stop_mouse);
+ ObjectTypeDB::bind_method(_MD("is_stopping_mouse"),&Control::is_stopping_mouse);
+
+ ObjectTypeDB::bind_method(_MD("grab_click_focus"),&Control::grab_click_focus);
+
+ ObjectTypeDB::bind_method(_MD("set_drag_preview","control:Control"),&Control::set_drag_preview);
+
+ BIND_VMETHOD(MethodInfo("_input_event",PropertyInfo(Variant::INPUT_EVENT,"event")));
+ BIND_VMETHOD(MethodInfo(Variant::VECTOR2,"get_minimum_size"));
+ BIND_VMETHOD(MethodInfo(Variant::OBJECT,"get_drag_data",PropertyInfo(Variant::VECTOR2,"pos")));
+ BIND_VMETHOD(MethodInfo(Variant::BOOL,"can_drop_data",PropertyInfo(Variant::VECTOR2,"pos"),PropertyInfo(Variant::NIL,"data")));
+ BIND_VMETHOD(MethodInfo("drop_data",PropertyInfo(Variant::VECTOR2,"pos"),PropertyInfo(Variant::NIL,"data")));
+
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"anchor/left", PROPERTY_HINT_ENUM, "Begin,End,Ratio"), _SCS("set_anchor"),_SCS("get_anchor"), MARGIN_LEFT );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"anchor/top", PROPERTY_HINT_ENUM, "Begin,End,Ratio"), _SCS("set_anchor"),_SCS("get_anchor"), MARGIN_TOP );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"anchor/right", PROPERTY_HINT_ENUM, "Begin,End,Ratio"), _SCS("set_anchor"),_SCS("get_anchor"), MARGIN_RIGHT );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"anchor/bottom", PROPERTY_HINT_ENUM, "Begin,End,Ratio"), _SCS("set_anchor"),_SCS("get_anchor"), MARGIN_BOTTOM );
+
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"margin/left", PROPERTY_HINT_RANGE, "-4096,4096"), _SCS("set_margin"),_SCS("get_margin"), MARGIN_LEFT );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"margin/top", PROPERTY_HINT_RANGE, "-4096,4096"), _SCS("set_margin"),_SCS("get_margin"), MARGIN_TOP );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"margin/right", PROPERTY_HINT_RANGE, "-4096,4096"), _SCS("set_margin"),_SCS("get_margin"), MARGIN_RIGHT );
+ ADD_PROPERTYINZ( PropertyInfo(Variant::INT,"margin/bottom", PROPERTY_HINT_RANGE, "-4096,4096"), _SCS("set_margin"),_SCS("get_margin"), MARGIN_BOTTOM );
+
+ ADD_PROPERTYNZ( PropertyInfo(Variant::VECTOR2,"rect/pos", PROPERTY_HINT_NONE, "",PROPERTY_USAGE_EDITOR), _SCS("set_pos"),_SCS("get_pos") );
+ ADD_PROPERTYNZ( PropertyInfo(Variant::VECTOR2,"rect/size", PROPERTY_HINT_NONE, "",PROPERTY_USAGE_EDITOR), _SCS("set_size"),_SCS("get_size") );
+ ADD_PROPERTYNZ( PropertyInfo(Variant::VECTOR2,"rect/min_size"), _SCS("set_custom_minimum_size"),_SCS("get_custom_minimum_size") );
+ ADD_PROPERTYNZ( PropertyInfo(Variant::STRING,"hint/tooltip", PROPERTY_HINT_MULTILINE_TEXT), _SCS("set_tooltip"),_SCS("_get_tooltip") );
+ ADD_PROPERTYI( PropertyInfo(Variant::NODE_PATH,"focus_neighbour/left" ), _SCS("set_focus_neighbour"),_SCS("get_focus_neighbour"),MARGIN_LEFT );
+ ADD_PROPERTYI( PropertyInfo(Variant::NODE_PATH,"focus_neighbour/top" ), _SCS("set_focus_neighbour"),_SCS("get_focus_neighbour"),MARGIN_TOP );
+ ADD_PROPERTYI( PropertyInfo(Variant::NODE_PATH,"focus_neighbour/right" ), _SCS("set_focus_neighbour"),_SCS("get_focus_neighbour"),MARGIN_RIGHT );
+ ADD_PROPERTYI( PropertyInfo(Variant::NODE_PATH,"focus_neighbour/bottom" ), _SCS("set_focus_neighbour"),_SCS("get_focus_neighbour"),MARGIN_BOTTOM );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"focus/ignore_mouse"), _SCS("set_ignore_mouse"),_SCS("is_ignoring_mouse") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL,"focus/stop_mouse"), _SCS("set_stop_mouse"),_SCS("is_stopping_mouse") );
+
+ ADD_PROPERTYNZ( PropertyInfo(Variant::INT,"size_flags/horizontal", PROPERTY_HINT_FLAGS, "Expand,Fill"), _SCS("set_h_size_flags"),_SCS("get_h_size_flags") );
+ ADD_PROPERTYNZ( PropertyInfo(Variant::INT,"size_flags/vertical", PROPERTY_HINT_FLAGS, "Expand,Fill"), _SCS("set_v_size_flags"),_SCS("get_v_size_flags") );
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"size_flags/stretch_ratio", PROPERTY_HINT_RANGE, "1,128,0.01"), _SCS("set_stretch_ratio"),_SCS("get_stretch_ratio") );
+
+ BIND_CONSTANT( ANCHOR_BEGIN );
+ BIND_CONSTANT( ANCHOR_END );
+ BIND_CONSTANT( ANCHOR_RATIO );
+ BIND_CONSTANT( FOCUS_NONE );
+ BIND_CONSTANT( FOCUS_CLICK );
+ BIND_CONSTANT( FOCUS_ALL );
+
+
+ BIND_CONSTANT( NOTIFICATION_RESIZED );
+ BIND_CONSTANT( NOTIFICATION_MOUSE_ENTER );
+ BIND_CONSTANT( NOTIFICATION_MOUSE_EXIT );
+ BIND_CONSTANT( NOTIFICATION_FOCUS_ENTER );
+ BIND_CONSTANT( NOTIFICATION_FOCUS_EXIT );
+ BIND_CONSTANT( NOTIFICATION_THEME_CHANGED );
+ BIND_CONSTANT( NOTIFICATION_MODAL_CLOSE );
+
+ BIND_CONSTANT( CURSOR_ARROW );
+ BIND_CONSTANT( CURSOR_IBEAM );
+ BIND_CONSTANT( CURSOR_POINTING_HAND );
+ BIND_CONSTANT( CURSOR_CROSS );
+ BIND_CONSTANT( CURSOR_WAIT );
+ BIND_CONSTANT( CURSOR_BUSY );
+ BIND_CONSTANT( CURSOR_DRAG );
+ BIND_CONSTANT( CURSOR_CAN_DROP );
+ BIND_CONSTANT( CURSOR_FORBIDDEN );
+ BIND_CONSTANT( CURSOR_VSIZE );
+ BIND_CONSTANT( CURSOR_HSIZE );
+ BIND_CONSTANT( CURSOR_BDIAGSIZE );
+ BIND_CONSTANT( CURSOR_FDIAGSIZE );
+ BIND_CONSTANT( CURSOR_MOVE );
+ BIND_CONSTANT( CURSOR_VSPLIT );
+ BIND_CONSTANT( CURSOR_HSPLIT );
+ BIND_CONSTANT( CURSOR_HELP );
+
+ BIND_CONSTANT( SIZE_EXPAND );
+ BIND_CONSTANT( SIZE_FILL );
+ BIND_CONSTANT( SIZE_EXPAND_FILL );
+
+ ADD_SIGNAL( MethodInfo("resized") );
+ ADD_SIGNAL( MethodInfo("input_event") );
+ ADD_SIGNAL( MethodInfo("mouse_enter") );
+ ADD_SIGNAL( MethodInfo("mouse_exit") );
+ ADD_SIGNAL( MethodInfo("focus_enter") );
+ ADD_SIGNAL( MethodInfo("focus_exit") );
+ ADD_SIGNAL( MethodInfo("size_flags_changed") );
+ ADD_SIGNAL( MethodInfo("minimum_size_changed") );
+
+
+}
+Control::Control() {
+
+ data.parent=NULL;
+ data.window=NULL;
+ data.viewport=NULL;
+ data.ignore_mouse=false;
+ data.stop_mouse=true;
+ window=NULL;
+
+ data.SI=NULL;
+ data.MI=NULL;
+ data.modal=false;
+ data.theme_owner=NULL;
+ data.modal_exclusive=false;
+ data.default_cursor = CURSOR_ARROW;
+ data.h_size_flags=SIZE_FILL;
+ data.v_size_flags=SIZE_FILL;
+ data.expand=1;
+ data.pending_min_size_update=false;
+
+
+ for (int i=0;i<4;i++) {
+ data.anchor[i]=ANCHOR_BEGIN;
+ data.margin[i]=0;
+ }
+ data.focus_mode=FOCUS_NONE;
+ data.modal_prev_focus_owner=0;
+
+
+
+
+
+}
+
+
+Control::~Control()
+{
+}
+
+
diff --git a/scene/gui/control.h b/scene/gui/control.h
new file mode 100644
index 0000000000..a5d302105f
--- /dev/null
+++ b/scene/gui/control.h
@@ -0,0 +1,395 @@
+/*************************************************************************/
+/* control.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef CONTROL_H
+#define CONTROL_H
+
+#include "scene/main/node.h"
+#include "scene/resources/theme.h"
+#include "scene/main/timer.h"
+#include "scene/2d/canvas_item.h"
+#include "math_2d.h"
+#include "rid.h"
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+class Viewport;
+class Label;
+class Panel;
+
+class Control : public CanvasItem {
+
+ OBJ_TYPE( Control, CanvasItem );
+ OBJ_CATEGORY("GUI Nodes");
+
+public:
+
+ enum AnchorType {
+ ANCHOR_BEGIN,
+ ANCHOR_END,
+ ANCHOR_RATIO
+ };
+
+ enum FocusMode {
+ FOCUS_NONE,
+ FOCUS_CLICK,
+ FOCUS_ALL
+ };
+
+ enum SizeFlags {
+
+ SIZE_EXPAND=1,
+ SIZE_FILL=2,
+ SIZE_EXPAND_FILL=SIZE_EXPAND|SIZE_FILL
+
+ };
+
+ enum CursorShape {
+ CURSOR_ARROW,
+ CURSOR_IBEAM,
+ CURSOR_POINTING_HAND,
+ CURSOR_CROSS,
+ CURSOR_WAIT,
+ CURSOR_BUSY,
+ CURSOR_DRAG,
+ CURSOR_CAN_DROP,
+ CURSOR_FORBIDDEN,
+ CURSOR_VSIZE,
+ CURSOR_HSIZE,
+ CURSOR_BDIAGSIZE,
+ CURSOR_FDIAGSIZE,
+ CURSOR_MOVE,
+ CURSOR_VSPLIT,
+ CURSOR_HSPLIT,
+ CURSOR_HELP,
+ CURSOR_MAX
+ };
+
+private:
+
+ struct CComparator {
+
+ bool operator()(const Control* p_a, const Control* p_b) const { return p_b->is_greater_than(p_a); }
+ };
+
+ struct Data {
+
+ Point2 pos_cache;
+ Size2 size_cache;
+
+ float margin[4];
+ AnchorType anchor[4];
+ FocusMode focus_mode;
+
+ bool pending_resize;
+
+ int h_size_flags;
+ int v_size_flags;
+ float expand;
+ bool pending_min_size_update;
+ Point2 custom_minimum_size;
+
+ bool ignore_mouse;
+ bool stop_mouse;
+
+ Control *parent;
+ Control *window;
+ bool modal;
+ bool modal_exclusive;
+ Ref<Theme> theme;
+ Control *theme_owner;
+ String tooltip;
+ CursorShape default_cursor;
+
+ List<Control*>::Element *MI; //modal item
+ List<Control*>::Element *SI;
+
+ CanvasItem *parent_canvas_item;
+
+ Viewport *viewport;
+
+ ObjectID modal_prev_focus_owner;
+
+ NodePath focus_neighbour[4];
+
+ HashMap<StringName, Ref<Texture>, StringNameHasher > icon_override;
+ HashMap<StringName, Ref<StyleBox>, StringNameHasher > style_override;
+ HashMap<StringName, Ref<Font>, StringNameHasher > font_override;
+ HashMap<StringName, Color, StringNameHasher > color_override;
+ HashMap<StringName, int, StringNameHasher > constant_override;
+ } data;
+
+ struct Window {
+ // info used when this is a window
+
+ bool key_event_accepted;
+ Control *mouse_focus;
+ int mouse_focus_button;
+ Control *key_focus;
+ Control *mouse_over;
+ Control *tooltip;
+ Panel *tooltip_popup;
+ Label *tooltip_label;
+ Point2 tooltip_pos;
+ Point2 last_mouse_pos;
+ Point2 drag_accum;
+ bool drag_attempted;
+ Variant drag_data;
+ Control *drag_preview;
+ Timer *tooltip_timer;
+ List<Control*> modal_stack;
+ unsigned int cancelled_input_ID;
+ Matrix32 focus_inv_xform;
+ bool subwindow_order_dirty;
+ List<Control*> subwindows;
+ bool disable_input;
+
+ Window();
+ };
+
+ Window *window;
+
+ // used internally
+ Control* _find_next_visible_control_at_pos(Node* p_node,const Point2& p_global,Matrix32& r_xform) const;
+ Control* _find_control_at_pos(CanvasItem* p_node,const Point2& p_pos,const Matrix32& p_xform,Matrix32& r_inv_xform);
+
+
+ void _window_sort_subwindows();
+ void _window_accept_event();
+ void _window_remove_focus();
+ void _window_cancel_input_ID(int p_input);
+ void _window_sort_modal_stack();
+ void _window_find_focus_neighbour(const Vector2& p_dir, Node *p_at, const Point2* p_points ,float p_min,float &r_closest_dist,Control **r_closest);
+ Control *_get_focus_neighbour(Margin p_margin,int p_count=0);
+ void _window_call_input(Control *p_control,const InputEvent& p_input);
+
+ float _get_parent_range(int p_idx) const;
+ float _get_range(int p_idx) const;
+ Point2 _window_get_pos() const;
+ float _s2a(float p_val, AnchorType p_anchor,float p_range) const;
+ float _a2s(float p_val, AnchorType p_anchor,float p_range) const;
+ void _modal_stack_remove();
+ void _propagate_theme_changed(Control *p_owner);
+
+ void _change_notify_margins();
+ void _window_cancel_tooltip();
+ void _window_show_tooltip();
+ void _update_minimum_size();
+
+ void _update_scroll();
+ void _gui_input(const InputEvent& p_event); //used by scene main loop
+ void _input_text(const String& p_text);
+ void _resize(const Size2& p_size);
+
+ void _size_changed();
+ String _get_tooltip() const;
+
+
+protected:
+ bool window_has_modal_stack() const;
+
+ virtual void _window_input_event(InputEvent p_event);
+
+ bool _set(const StringName& p_name, const Variant& p_value);
+ bool _get(const StringName& p_name,Variant &r_ret) const;
+ void _get_property_list( List<PropertyInfo> *p_list) const;
+
+ void _notification(int p_notification);
+
+
+ static void _bind_methods();
+
+ //bind helpers
+
+public:
+
+ enum {
+
+/* NOTIFICATION_DRAW=30,
+ NOTIFICATION_VISIBILITY_CHANGED=38*/
+ NOTIFICATION_RESIZED=40,
+ NOTIFICATION_MOUSE_ENTER=41,
+ NOTIFICATION_MOUSE_EXIT=42,
+ NOTIFICATION_FOCUS_ENTER=43,
+ NOTIFICATION_FOCUS_EXIT=44,
+ NOTIFICATION_THEME_CHANGED=45,
+ NOTIFICATION_MODAL_CLOSE=46,
+
+
+ };
+
+ virtual Variant edit_get_state() const;
+ virtual void edit_set_state(const Variant& p_state);
+ virtual void edit_set_rect(const Rect2& p_edit_rect);
+ virtual Size2 edit_get_minimum_size() const;
+
+ void accept_event();
+
+ virtual Size2 get_minimum_size() const;
+ virtual Size2 get_combined_minimum_size() const;
+ virtual bool has_point(const Point2& p_point) const;
+ virtual bool clips_input() const;
+ virtual Variant get_drag_data(const Point2& p_point);
+ virtual bool can_drop_data(const Point2& p_point,const Variant& p_data) const;
+ virtual void drop_data(const Point2& p_point,const Variant& p_data);
+ void set_drag_preview(Control *p_control);
+ void force_drag(const Variant& p_data,Control *p_control);
+
+ void set_custom_minimum_size(const Size2& p_custom);
+ Size2 get_custom_minimum_size() const;
+
+ bool is_window() const;
+ Control *get_window() const;
+ Control *get_parent_control() const;
+
+
+
+ /* POSITIONING */
+
+ void set_anchor(Margin p_margin,AnchorType p_anchor);
+ void set_anchor_and_margin(Margin p_margin,AnchorType p_anchor, float p_pos);
+
+ AnchorType get_anchor(Margin p_margin) const;
+
+ void set_margin(Margin p_margin,float p_value);
+
+ void set_begin(const Point2& p_point); // helper
+ void set_end(const Point2& p_point); // helper
+
+
+
+ float get_margin(Margin p_margin) const;
+ Point2 get_begin() const;
+ Point2 get_end() const;
+
+ void set_pos(const Point2& p_point);
+ void set_size(const Size2& p_size);
+ void set_global_pos(const Point2& p_point);
+
+ Point2 get_pos() const;
+ Point2 get_global_pos() const;
+ Size2 get_size() const;
+ Rect2 get_rect() const;
+ Rect2 get_global_rect() const;
+ Rect2 get_window_rect() const; ///< use with care, as it blocks waiting for the visual server
+
+ void set_area_as_parent_rect(int p_margin=0);
+
+ void show_modal(bool p_exclusive=false);
+
+ void set_theme(const Ref<Theme>& p_theme);
+ Ref<Theme> get_theme() const;
+
+ void set_h_size_flags(int p_flags);
+ int get_h_size_flags() const;
+
+ void set_v_size_flags(int p_flags);
+ int get_v_size_flags() const;
+
+ void set_stretch_ratio(float p_ratio);
+ float get_stretch_ratio() const;
+
+ void minimum_size_changed();
+
+ /* FOCUS */
+
+ void set_focus_mode(FocusMode p_focus_mode);
+ FocusMode get_focus_mode() const;
+ bool has_focus() const;
+ void grab_focus();
+ void release_focus();
+
+ Control *find_next_valid_focus() const;
+ Control *find_prev_valid_focus() const;
+
+ void set_focus_neighbour(Margin p_margin, const NodePath &p_neighbour);
+ NodePath get_focus_neighbour(Margin p_margin) const;
+
+ Control *get_focus_owner() const;
+
+ void set_ignore_mouse(bool p_ignore);
+ bool is_ignoring_mouse() const;
+
+ void set_stop_mouse(bool p_stop);
+ bool is_stopping_mouse() const;
+
+ /* SKINNING */
+
+ void add_icon_override(const StringName& p_name, const Ref<Texture>& p_icon);
+ void add_style_override(const StringName& p_name, const Ref<StyleBox>& p_style);
+ void add_font_override(const StringName& p_name, const Ref<Font>& p_font);
+ void add_color_override(const StringName& p_name, const Color& p_color);
+ void add_constant_override(const StringName& p_name, int p_constant);
+
+ Ref<Texture> get_icon(const StringName& p_name,const StringName& p_type=StringName()) const;
+ Ref<StyleBox> get_stylebox(const StringName& p_name,const StringName& p_type=StringName()) const;
+ Ref<Font> get_font(const StringName& p_name,const StringName& p_type=StringName()) const;
+ Color get_color(const StringName& p_name,const StringName& p_type=StringName()) const;
+ int get_constant(const StringName& p_name,const StringName& p_type=StringName()) const;
+
+ bool has_icon(const StringName& p_name,const StringName& p_type=StringName()) const;
+ bool has_stylebox(const StringName& p_name,const StringName& p_type=StringName()) const;
+ bool has_font(const StringName& p_name,const StringName& p_type=StringName()) const;
+ bool has_color(const StringName& p_name,const StringName& p_type=StringName()) const;
+ bool has_constant(const StringName& p_name,const StringName& p_type=StringName()) const;
+
+ /* TOOLTIP */
+
+ void set_tooltip(const String& p_tooltip);
+ virtual String get_tooltip(const Point2& p_pos) const;
+
+ /* CURSOR */
+
+ void set_default_cursor_shape(CursorShape p_shape);
+ CursorShape get_default_cursor_shape() const;
+ virtual CursorShape get_cursor_shape(const Point2& p_pos=Point2i()) const;
+
+ virtual Rect2 get_item_rect() const;
+ virtual Matrix32 get_transform() const;
+
+ bool is_toplevel_control() const;
+
+ Size2 get_parent_area_size() const;
+
+ void grab_click_focus();
+
+
+
+ Control();
+ ~Control();
+
+};
+
+VARIANT_ENUM_CAST(Control::AnchorType);
+VARIANT_ENUM_CAST(Control::FocusMode);
+VARIANT_ENUM_CAST(Control::SizeFlags);
+VARIANT_ENUM_CAST(Control::CursorShape);
+
+#endif
diff --git a/scene/gui/custom_button.cpp b/scene/gui/custom_button.cpp
new file mode 100644
index 0000000000..ed3f01e5fa
--- /dev/null
+++ b/scene/gui/custom_button.cpp
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* custom_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "custom_button.h"
+
+CustomButton::CustomButton()
+{
+}
+
+
+CustomButton::~CustomButton()
+{
+}
+
+
diff --git a/scene/gui/custom_button.h b/scene/gui/custom_button.h
new file mode 100644
index 0000000000..1f0f0470ed
--- /dev/null
+++ b/scene/gui/custom_button.h
@@ -0,0 +1,43 @@
+/*************************************************************************/
+/* custom_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef CUSTOM_BUTTON_H
+#define CUSTOM_BUTTON_H
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class CustomButton{
+public:
+ CustomButton();
+
+ ~CustomButton();
+
+};
+
+#endif
diff --git a/scene/gui/dialogs.cpp b/scene/gui/dialogs.cpp
new file mode 100644
index 0000000000..ac0ded03ab
--- /dev/null
+++ b/scene/gui/dialogs.cpp
@@ -0,0 +1,375 @@
+/*************************************************************************/
+/* dialogs.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "dialogs.h"
+#include "print_string.h"
+#include "line_edit.h"
+#include "translation.h"
+
+void WindowDialog::_post_popup() {
+
+ dragging=false; //just in case
+}
+
+bool WindowDialog::has_point(const Point2& p_point) const {
+
+
+ int extra = get_constant("titlebar_height","WindowDialog");
+ Rect2 r( Point2(), get_size() );
+ r.pos.y-=extra;
+ r.size.y+=extra;
+ return r.has_point(p_point);
+
+}
+
+void WindowDialog::_input_event(const InputEvent& p_event) {
+
+ if (p_event.type == InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index==BUTTON_LEFT) {
+
+ if (p_event.mouse_button.pressed && p_event.mouse_button.y < 0)
+ dragging=true;
+ else if (dragging && !p_event.mouse_button.pressed)
+ dragging=false;
+ }
+
+
+ if (p_event.type == InputEvent::MOUSE_MOTION && dragging) {
+
+ Point2 rel( p_event.mouse_motion.relative_x, p_event.mouse_motion.relative_y );
+ Point2 pos = get_pos();
+ Size2 size = get_size();
+
+ pos+=rel;
+
+ if (pos.y<0)
+ pos.y=0;
+
+ set_pos(pos);
+ }
+}
+
+void WindowDialog::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_DRAW: {
+
+ RID ci = get_canvas_item();
+ Size2 s = get_size();
+ Ref<StyleBox> st = get_stylebox("panel","WindowDialog");
+ st->draw(ci,Rect2(Point2(),s));
+ int th = get_constant("title_height","WindowDialog");
+ Color tc = get_color("title_color","WindowDialog");
+ Ref<Font> font = get_font("title_font","WindowDialog");
+ int ofs = (s.width-font->get_string_size(title).width)/2;
+ //int ofs = st->get_margin(MARGIN_LEFT);
+ draw_string(font,Point2(ofs,-th+font->get_ascent()),title,tc,s.width - st->get_minimum_size().width);
+
+
+ } break;
+ case NOTIFICATION_THEME_CHANGED:
+ case NOTIFICATION_ENTER_SCENE: {
+
+ close_button->set_normal_texture( get_icon("close","WindowDialog"));
+ close_button->set_pressed_texture( get_icon("close","WindowDialog"));
+ close_button->set_hover_texture( get_icon("close_hilite","WindowDialog"));
+ close_button->set_anchor(MARGIN_LEFT,ANCHOR_END);
+ close_button->set_begin( Point2( get_constant("close_h_ofs","WindowDialog"), -get_constant("close_v_ofs","WindowDialog") ));
+
+ } break;
+ }
+
+}
+
+void WindowDialog::_closed() {
+
+ _close_pressed();
+ hide();
+}
+
+void WindowDialog::set_title(const String& p_title) {
+
+ title=XL_MESSAGE(p_title);
+ update();
+}
+
+String WindowDialog::get_title() const {
+
+ return title;
+}
+
+
+TextureButton *WindowDialog::get_close_button() {
+
+
+ return close_button;
+}
+
+void WindowDialog::_bind_methods() {
+
+ ObjectTypeDB::bind_method( _MD("_input_event"),&WindowDialog::_input_event);
+ ObjectTypeDB::bind_method( _MD("set_title","title"),&WindowDialog::set_title);
+ ObjectTypeDB::bind_method( _MD("get_title"),&WindowDialog::get_title);
+ ObjectTypeDB::bind_method( _MD("_closed"),&WindowDialog::_closed);
+ ObjectTypeDB::bind_method( _MD("get_close_button:TextureButton"),&WindowDialog::get_close_button);
+
+ ADD_PROPERTY( PropertyInfo(Variant::STRING,"window/title",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_DEFAULT_INTL),_SCS("set_title"),_SCS("get_title"));
+}
+
+WindowDialog::WindowDialog() {
+
+ //title="Hello!";
+ dragging=false;
+ close_button = memnew( TextureButton );
+ add_child(close_button);
+ close_button->connect("pressed",this,"_closed");
+
+}
+
+WindowDialog::~WindowDialog(){
+
+
+}
+
+
+void PopupDialog::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+ get_stylebox("panel","PopupMenu")->draw(ci,Rect2(Point2(),get_size()));
+ }
+}
+
+PopupDialog::PopupDialog() {
+
+
+}
+
+PopupDialog::~PopupDialog() {
+
+
+}
+
+
+//
+
+
+void AcceptDialog::_post_popup() {
+
+ WindowDialog::_post_popup();
+ get_ok()->grab_focus();
+
+}
+
+void AcceptDialog::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_MODAL_CLOSE) {
+
+ cancel_pressed();
+ } if (p_what==NOTIFICATION_DRAW) {
+
+
+
+
+ }
+}
+
+void AcceptDialog::_builtin_text_entered(const String& p_text) {
+
+ _ok_pressed();
+}
+
+void AcceptDialog::_ok_pressed() {
+
+ if (hide_on_ok)
+ hide();
+ ok_pressed();
+ emit_signal("confirmed");
+
+}
+void AcceptDialog::_close_pressed() {
+
+ cancel_pressed();
+}
+
+String AcceptDialog::get_text() const {
+
+ return label->get_text();
+}
+void AcceptDialog::set_text(String p_text) {
+
+ label->set_text(p_text);
+}
+
+void AcceptDialog::set_hide_on_ok(bool p_hide) {
+
+ hide_on_ok=p_hide;
+}
+
+bool AcceptDialog::get_hide_on_ok() const {
+
+ return hide_on_ok;
+}
+
+
+void AcceptDialog::register_text_enter(Node *p_line_edit) {
+
+ ERR_FAIL_NULL(p_line_edit);
+ p_line_edit->connect("text_entered", this,"_builtin_text_entered");
+}
+
+void AcceptDialog::set_child_rect(Control *p_child) {
+
+ ERR_FAIL_COND(p_child->get_parent()!=this);
+
+ p_child->set_area_as_parent_rect(get_constant("margin","Dialogs"));
+ p_child->set_margin(MARGIN_BOTTOM, get_constant("button_margin","Dialogs")+10);
+}
+
+void AcceptDialog::_custom_action(const String& p_action) {
+
+ emit_signal("custom_action",p_action);
+ custom_action(p_action);
+}
+
+Button* AcceptDialog::add_button(const String& p_text,bool p_right,const String& p_action) {
+
+
+ Button *button = memnew( Button );
+ button->set_text(p_text);
+ if (p_right) {
+ hbc->add_child(button);
+ hbc->add_spacer();
+ } else {
+
+ hbc->add_child(button);
+ hbc->move_child(button,0);
+ hbc->add_spacer(true);
+ }
+
+ if (p_action!="") {
+ button->connect("pressed",this,"_custom_action",make_binds(p_action));
+ }
+
+ return button;
+}
+
+Button* AcceptDialog::add_cancel(const String &p_cancel) {
+
+ String c = p_cancel;
+ if (p_cancel=="")
+ c="Cancel";
+ Button *b = swap_ok_cancel ? add_button("Cancel",true) : add_button("Cancel");
+ b->connect("pressed",this,"_closed");
+ return b;
+}
+
+void AcceptDialog::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_ok"),&AcceptDialog::_ok_pressed);
+ ObjectTypeDB::bind_method(_MD("get_ok"),&AcceptDialog::get_ok);
+ ObjectTypeDB::bind_method(_MD("get_label"),&AcceptDialog::get_label);
+ ObjectTypeDB::bind_method(_MD("set_hide_on_ok","enabled"),&AcceptDialog::set_hide_on_ok);
+ ObjectTypeDB::bind_method(_MD("get_hide_on_ok"),&AcceptDialog::get_hide_on_ok);
+ ObjectTypeDB::bind_method(_MD("add_button:Button","text","right","action"),&AcceptDialog::add_cancel,DEFVAL(false),DEFVAL(""));
+ ObjectTypeDB::bind_method(_MD("add_cancel:Button","name"),&AcceptDialog::add_cancel);
+ ObjectTypeDB::bind_method(_MD("_builtin_text_entered"),&AcceptDialog::_builtin_text_entered);
+ ObjectTypeDB::bind_method(_MD("register_text_enter:LineEdit","line_edit"),&AcceptDialog::register_text_enter);
+ ObjectTypeDB::bind_method(_MD("_custom_action"),&AcceptDialog::_custom_action);
+ ObjectTypeDB::bind_method(_MD("set_text","text"),&AcceptDialog::set_text);
+ ObjectTypeDB::bind_method(_MD("get_text"),&AcceptDialog::get_text);
+
+ ADD_SIGNAL( MethodInfo("confirmed") );
+ ADD_SIGNAL( MethodInfo("custom_action",PropertyInfo(Variant::STRING,"action")) );
+
+
+}
+
+
+bool AcceptDialog::swap_ok_cancel=false;
+void AcceptDialog::set_swap_ok_cancel(bool p_swap) {
+
+ swap_ok_cancel=p_swap;
+}
+
+AcceptDialog::AcceptDialog() {
+
+ int margin = get_constant("margin","Dialogs");
+ int button_margin = get_constant("button_margin","Dialogs");
+
+
+ label = memnew( Label );
+ label->set_anchor(MARGIN_RIGHT,ANCHOR_END);
+ label->set_anchor(MARGIN_BOTTOM,ANCHOR_END);
+ label->set_begin( Point2( margin, margin) );
+ label->set_end( Point2( margin, button_margin) );
+ label->set_autowrap(true);
+ add_child(label);
+
+ hbc = memnew( HBoxContainer );
+ hbc->set_area_as_parent_rect(margin);
+ hbc->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,button_margin);
+ add_child(hbc);
+
+ hbc->add_spacer();
+ ok = memnew( Button );
+ ok->set_text("OK");
+ hbc->add_child(ok);
+ hbc->add_spacer();
+ //add_child(ok);
+
+
+ ok->connect("pressed", this,"_ok");
+ set_as_toplevel(true);
+
+ hide_on_ok=true;
+ set_title("Alert!");
+}
+
+
+AcceptDialog::~AcceptDialog()
+{
+}
+
+
+void ConfirmationDialog::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("get_cancel:Button"),&ConfirmationDialog::get_cancel);
+}
+
+Button *ConfirmationDialog::get_cancel() {
+
+ return cancel;
+}
+
+ConfirmationDialog::ConfirmationDialog() {
+
+ set_title("Please Confirm...");
+ cancel = add_cancel();
+}
diff --git a/scene/gui/dialogs.h b/scene/gui/dialogs.h
new file mode 100644
index 0000000000..e547d5f2af
--- /dev/null
+++ b/scene/gui/dialogs.h
@@ -0,0 +1,157 @@
+/*************************************************************************/
+/* dialogs.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef DIALOGS_H
+#define DIALOGS_H
+
+#include "scene/gui/label.h"
+#include "scene/gui/button.h"
+#include "scene/gui/texture_button.h"
+#include "scene/gui/panel.h"
+#include "scene/gui/popup.h"
+#include "box_container.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+
+class WindowDialog : public Popup {
+
+ OBJ_TYPE(WindowDialog,Popup);
+
+ TextureButton *close_button;
+ String title;
+ bool dragging;
+
+ void _input_event(const InputEvent& p_event);
+ void _closed();
+protected:
+ virtual void _post_popup();
+
+ virtual void _close_pressed() {}
+ virtual bool has_point(const Point2& p_point) const;
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+
+ TextureButton *get_close_button();
+
+ void set_title(const String& p_title);
+ String get_title() const;
+
+ WindowDialog();
+ ~WindowDialog();
+
+};
+
+class PopupDialog : public Popup {
+
+ OBJ_TYPE(PopupDialog,Popup);
+
+protected:
+ void _notification(int p_what);
+public:
+
+ PopupDialog();
+ ~PopupDialog();
+
+};
+
+
+class LineEdit;
+
+class AcceptDialog : public WindowDialog {
+
+ OBJ_TYPE(AcceptDialog,WindowDialog);
+
+ HBoxContainer *hbc;
+ Label *label;
+ Button *ok;
+// Button *cancel; no more cancel (there is X on tht titlebar)
+ bool hide_on_ok;
+
+
+ void _custom_action(const String& p_action);
+ void _ok_pressed();
+ void _close_pressed();
+ void _builtin_text_entered(const String& p_text);
+
+ static bool swap_ok_cancel;
+
+
+
+
+protected:
+
+ virtual void _post_popup();
+ void _notification(int p_what);
+ static void _bind_methods();
+ virtual void ok_pressed() {}
+ virtual void cancel_pressed() {}
+ virtual void custom_action(const String&) {}
+public:
+
+ Label *get_label() { return label; }
+ static void set_swap_ok_cancel(bool p_swap);
+
+
+ void register_text_enter(Node *p_line_edit);
+
+ Button *get_ok() { return ok; }
+ Button* add_button(const String& p_text,bool p_right=false,const String& p_action="");
+ Button* add_cancel(const String &p_cancel="");
+
+
+ void set_hide_on_ok(bool p_hide);
+ bool get_hide_on_ok() const;
+
+ void set_text(String p_text);
+ String get_text() const;
+
+ void set_child_rect(Control *p_child);
+
+ AcceptDialog();
+ ~AcceptDialog();
+
+};
+
+
+class ConfirmationDialog : public AcceptDialog {
+
+ OBJ_TYPE(ConfirmationDialog,AcceptDialog);
+ Button *cancel;
+protected:
+ static void _bind_methods();
+public:
+
+ Button *get_cancel();
+ ConfirmationDialog();
+
+};
+
+#endif
diff --git a/scene/gui/empty_control.cpp b/scene/gui/empty_control.cpp
new file mode 100644
index 0000000000..1e377b2b73
--- /dev/null
+++ b/scene/gui/empty_control.cpp
@@ -0,0 +1,59 @@
+/*************************************************************************/
+/* empty_control.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "empty_control.h"
+
+Size2 EmptyControl::get_minimum_size() const {
+
+ return minsize;
+}
+
+void EmptyControl::set_minsize(const Size2& p_size) {
+
+ minsize=p_size;
+ minimum_size_changed();
+}
+
+Size2 EmptyControl::get_minsize() const {
+
+ return minsize;
+}
+
+
+void EmptyControl::_bind_methods() {
+
+
+ ObjectTypeDB::bind_method(_MD("set_minsize","minsize"),&EmptyControl::set_minsize);
+ ObjectTypeDB::bind_method(_MD("get_minsize"),&EmptyControl::get_minsize);
+
+ ADD_PROPERTY( PropertyInfo(Variant::VECTOR2,"minsize"), _SCS("set_minsize"),_SCS("get_minsize") );
+}
+
+EmptyControl::EmptyControl()
+{
+}
diff --git a/scene/gui/empty_control.h b/scene/gui/empty_control.h
new file mode 100644
index 0000000000..993af45ac4
--- /dev/null
+++ b/scene/gui/empty_control.h
@@ -0,0 +1,48 @@
+/*************************************************************************/
+/* empty_control.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef EMPTY_CONTROL_H
+#define EMPTY_CONTROL_H
+
+#include "scene/gui/control.h"
+
+class EmptyControl : public Control {
+
+ OBJ_TYPE(EmptyControl,Control);
+ Size2 minsize;
+protected:
+ static void _bind_methods();
+public:
+ virtual Size2 get_minimum_size() const;
+ void set_minsize(const Size2& p_size);
+ Size2 get_minsize() const;
+
+ EmptyControl();
+};
+
+#endif // EMPTY_CONTROL_H
diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp
new file mode 100644
index 0000000000..5035fe5214
--- /dev/null
+++ b/scene/gui/file_dialog.cpp
@@ -0,0 +1,743 @@
+/*************************************************************************/
+/* file_dialog.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "file_dialog.h"
+#include "scene/gui/label.h"
+#include "print_string.h"
+
+
+FileDialog::GetIconFunc FileDialog::get_icon_func=NULL;
+FileDialog::GetIconFunc FileDialog::get_large_icon_func=NULL;
+
+FileDialog::RegisterFunc FileDialog::register_func=NULL;
+FileDialog::RegisterFunc FileDialog::unregister_func=NULL;
+
+
+VBoxContainer *FileDialog::get_vbox() {
+ return vbox;
+
+}
+
+void FileDialog::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ //RID ci = get_canvas_item();
+ //get_stylebox("panel","PopupMenu")->draw(ci,Rect2(Point2(),get_size()));
+ }
+}
+
+void FileDialog::set_enable_multiple_selection(bool p_enable) {
+
+ tree->set_select_mode(p_enable?Tree::SELECT_MULTI : Tree::SELECT_SINGLE);
+};
+
+Vector<String> FileDialog::get_selected_files() const {
+
+ Vector<String> list;
+
+ TreeItem* item = tree->get_root();
+ while ( (item = tree->get_next_selected(item)) ) {
+
+ list.push_back(dir_access->get_current_dir().plus_file(item->get_text(0)));
+ };
+
+ return list;
+};
+
+void FileDialog::update_dir() {
+
+ dir->set_text(dir_access->get_current_dir());
+}
+
+void FileDialog::_dir_entered(String p_dir) {
+
+
+ dir_access->change_dir(p_dir);
+ file->set_text("");
+ invalidate();
+ update_dir();
+}
+
+void FileDialog::_file_entered(const String& p_file) {
+
+ _action_pressed();
+}
+
+void FileDialog::_save_confirm_pressed() {
+
+ String f=dir_access->get_current_dir().plus_file(file->get_text());
+ emit_signal("file_selected",f);
+ hide();
+}
+
+void FileDialog::_post_popup() {
+
+ ConfirmationDialog::_post_popup();
+ if (invalidated) {
+ update_file_list();
+ invalidated=false;
+ }
+ if (mode==MODE_SAVE_FILE)
+ file->grab_focus();
+ else
+ tree->grab_focus();
+
+}
+
+void FileDialog::_action_pressed() {
+
+ if (mode==MODE_OPEN_FILES) {
+
+ TreeItem *ti=tree->get_next_selected(NULL);
+ String fbase=dir_access->get_current_dir();
+
+ DVector<String> files;
+ while(ti) {
+
+ files.push_back( fbase.plus_file(ti->get_text(0)) );
+ ti=tree->get_next_selected(ti);
+ }
+
+ if (files.size()) {
+ emit_signal("files_selected",files);
+ hide();
+ }
+
+ return;
+ }
+
+ String f=dir_access->get_current_dir().plus_file(file->get_text());
+
+ if (mode==MODE_OPEN_FILE && dir_access->file_exists(f)) {
+ emit_signal("file_selected",f);
+ hide();
+ }
+
+ if (mode==MODE_OPEN_DIR) {
+
+
+ String path=dir_access->get_current_dir();
+ /*if (tree->get_selected()) {
+ Dictionary d = tree->get_selected()->get_metadata(0);
+ if (d["dir"]) {
+ path=path+"/"+String(d["name"]);
+ }
+ }*/
+ path=path.replace("\\","/");
+ emit_signal("dir_selected",path);
+ hide();
+ }
+
+ if (mode==MODE_SAVE_FILE) {
+
+ if (dir_access->file_exists(f)) {
+ confirm_save->set_text("File Exists, Overwrite?");
+ confirm_save->popup_centered(Size2(200,80));
+ } else {
+
+ emit_signal("file_selected",f);
+ hide();
+ }
+ }
+}
+
+void FileDialog::_cancel_pressed() {
+
+ file->set_text("");
+ invalidate();
+ hide();
+}
+
+void FileDialog::_tree_selected() {
+
+ TreeItem *ti=tree->get_selected();
+ if (!ti)
+ return;
+ Dictionary d=ti->get_metadata(0);
+
+ if (!d["dir"]) {
+
+ file->set_text(d["name"]);
+ }
+
+}
+
+void FileDialog::_tree_dc_selected() {
+
+
+ TreeItem *ti=tree->get_selected();
+ if (!ti)
+ return;
+
+ Dictionary d=ti->get_metadata(0);
+
+ if (d["dir"]) {
+
+ dir_access->change_dir(d["name"]);
+ if (mode==MODE_OPEN_FILE || mode==MODE_OPEN_FILES || mode==MODE_OPEN_DIR)
+ file->set_text("");
+ call_deferred("_update_file_list");
+ call_deferred("_update_dir");
+ } else {
+
+ _action_pressed();
+ }
+}
+
+void FileDialog::update_file_list() {
+
+ tree->clear();
+ dir_access->list_dir_begin();
+
+ TreeItem *root = tree->create_item();
+ Ref<Texture> folder = get_icon("folder");
+ List<String> files;
+ List<String> dirs;
+
+ bool isdir;
+ String item;
+ while ((item=dir_access->get_next(&isdir))!="") {
+
+ if (!isdir)
+ files.push_back(item);
+ else
+ dirs.push_back(item);
+ }
+
+ dirs.sort_custom<NoCaseComparator>();
+ files.sort_custom<NoCaseComparator>();
+
+ while(!dirs.empty()) {
+
+ if (dirs.front()->get()!=".") {
+ TreeItem *ti=tree->create_item(root);
+ ti->set_text(0,dirs.front()->get()+"/");
+ ti->set_icon(0,folder);
+ Dictionary d;
+ d["name"]=dirs.front()->get();
+ d["dir"]=true;
+ ti->set_metadata(0,d);
+ }
+ dirs.pop_front();
+
+ }
+
+ dirs.clear();
+
+ List<String> patterns;
+ // build filter
+ if (filter->get_selected()==filter->get_item_count()-1) {
+
+ // match all
+ } else if (filters.size()>1 && filter->get_selected()==0) {
+ // match all filters
+ for (int i=0;i<filters.size();i++) {
+
+ String f=filters[i].get_slice(";",0);
+ for (int j=0;j<f.get_slice_count(",");j++) {
+
+ patterns.push_back(f.get_slice(",",j).strip_edges());
+ }
+ }
+ } else {
+ int idx=filter->get_selected();
+ if (filters.size()>1)
+ idx--;
+
+ if (idx>=0 && idx<filters.size()) {
+
+ String f=filters[idx].get_slice(";",0);
+ for (int j=0;j<f.get_slice_count(",");j++) {
+
+ patterns.push_back(f.get_slice(",",j).strip_edges());
+ }
+ }
+ }
+
+
+ String base_dir = dir_access->get_current_dir();
+
+
+ while(!files.empty()) {
+
+ bool match=patterns.empty();
+
+ for(List<String>::Element *E=patterns.front();E;E=E->next()) {
+
+ if (files.front()->get().matchn(E->get())) {
+
+ match=true;
+ break;
+ }
+ }
+
+ if (match) {
+ TreeItem *ti=tree->create_item(root);
+ ti->set_text(0,files.front()->get());
+
+ if (get_icon_func) {
+
+ Ref<Texture> icon = get_icon_func(base_dir.plus_file(files.front()->get()));
+ ti->set_icon(0,icon);
+ }
+
+ if (mode==MODE_OPEN_DIR) {
+ ti->set_custom_color(0,get_color("files_disabled"));
+ ti->set_selectable(0,false);
+ }
+ Dictionary d;
+ d["name"]=files.front()->get();
+ d["dir"]=false;
+ ti->set_metadata(0,d);
+
+ if (file->get_text()==files.front()->get())
+ ti->select(0);
+ }
+
+ files.pop_front();
+ }
+
+ if (tree->get_root() && tree->get_root()->get_children())
+ tree->get_root()->get_children()->select(0);
+
+ files.clear();
+
+}
+
+void FileDialog::_filter_selected(int) {
+
+ update_file_list();
+}
+
+void FileDialog::update_filters() {
+
+ filter->clear();
+
+ if (filters.size()>1) {
+ String all_filters;
+
+ const int max_filters=5;
+
+ for(int i=0;i<MIN( max_filters, filters.size()) ;i++) {
+ String flt=filters[i].get_slice(";",0);
+ if (i>0)
+ all_filters+=",";
+ all_filters+=flt;
+ }
+
+ if (max_filters<filters.size())
+ all_filters+=", ...";
+
+ filter->add_item("All Recognized ( "+all_filters+" )");
+ }
+ for(int i=0;i<filters.size();i++) {
+
+ String flt=filters[i].get_slice(";",0).strip_edges();
+ String desc=filters[i].get_slice(";",1).strip_edges();
+ if (desc.length())
+ filter->add_item(desc+" ( "+flt+" )");
+ else
+ filter->add_item("( "+flt+" )");
+ }
+
+ filter->add_item("All Files (*)");
+
+}
+
+void FileDialog::clear_filters() {
+
+ filters.clear();
+ update_filters();
+ invalidate();
+}
+void FileDialog::add_filter(const String& p_filter) {
+
+ filters.push_back(p_filter);
+ update_filters();
+ invalidate();
+
+}
+
+String FileDialog::get_current_dir() const {
+
+ return dir->get_text();
+}
+String FileDialog::get_current_file() const {
+
+ return file->get_text();
+}
+String FileDialog::get_current_path() const {
+
+ return dir->get_text().plus_file(file->get_text());
+}
+void FileDialog::set_current_dir(const String& p_dir) {
+
+ dir_access->change_dir(p_dir);
+ update_dir();
+ invalidate();
+
+}
+void FileDialog::set_current_file(const String& p_file) {
+
+ file->set_text(p_file);
+ update_dir();
+ invalidate();
+ int lp = p_file.find_last(".");
+ if (lp!=-1) {
+ file->select(0,lp);
+ file->grab_focus();
+ }
+
+
+}
+void FileDialog::set_current_path(const String& p_path) {
+
+ if (!p_path.size())
+ return;
+ int pos=MAX( p_path.find_last("/"), p_path.find_last("\\") );
+ if (pos==-1) {
+
+ set_current_file(p_path);
+ } else {
+
+ String dir=p_path.substr(0,pos);
+ String file=p_path.substr(pos+1,p_path.length());
+ set_current_dir(dir);
+ set_current_file(file);
+ }
+}
+
+
+void FileDialog::set_mode(Mode p_mode) {
+
+ mode=p_mode;
+ switch(mode) {
+
+ case MODE_OPEN_FILE: get_ok()->set_text("Open"); set_title("Open a File"); makedir->hide(); break;
+ case MODE_OPEN_FILES: get_ok()->set_text("Open"); set_title("Open File(s)"); makedir->hide(); break;
+ case MODE_SAVE_FILE: get_ok()->set_text("Save"); set_title("Save a File"); makedir->show(); break;
+ case MODE_OPEN_DIR: get_ok()->set_text("Open"); set_title("Open a Directory"); makedir->show(); break;
+ }
+
+ if (mode==MODE_OPEN_FILES) {
+ tree->set_select_mode(Tree::SELECT_MULTI);
+ } else {
+ tree->set_select_mode(Tree::SELECT_SINGLE);
+
+ }
+}
+
+FileDialog::Mode FileDialog::get_mode() const {
+
+ return mode;
+}
+
+void FileDialog::set_access(Access p_access) {
+
+ ERR_FAIL_INDEX(p_access,3);
+ if (access==p_access)
+ return;
+ memdelete( dir_access );
+ switch(p_access) {
+ case ACCESS_FILESYSTEM: {
+
+ dir_access = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ } break;
+ case ACCESS_RESOURCES: {
+
+ dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ } break;
+ case ACCESS_USERDATA: {
+
+ dir_access = DirAccess::create(DirAccess::ACCESS_USERDATA);
+ } break;
+ }
+ access=p_access;
+ _update_drives();
+ invalidate();
+ update_filters();
+ update_dir();
+}
+
+void FileDialog::invalidate() {
+
+ if (is_visible()) {
+ update_file_list();
+ invalidated=false;
+ } else {
+ invalidated=true;
+ }
+
+}
+
+FileDialog::Access FileDialog::get_access() const{
+
+ return access;
+}
+
+void FileDialog::_make_dir_confirm() {
+
+
+ Error err = dir_access->make_dir( makedirname->get_text() );
+ if (err==OK) {
+ dir_access->change_dir(makedirname->get_text());
+ invalidate();
+ update_filters();
+ update_dir();
+ } else {
+ mkdirerr->popup_centered_minsize(Size2(250,50));
+ }
+}
+
+
+void FileDialog::_make_dir() {
+
+ makedialog->popup_centered_minsize(Size2(250,80));
+}
+
+void FileDialog::_select_drive(int p_idx) {
+
+ String d = drives->get_item_text(p_idx);
+ dir_access->change_dir(d);
+ file->set_text("");
+ invalidate();
+ update_dir();
+
+}
+
+void FileDialog::_update_drives() {
+
+
+ int dc = dir_access->get_drive_count();
+ if (dc==0 || access!=ACCESS_FILESYSTEM) {
+ drives->hide();
+ } else {
+ drives->clear();
+ drives->show();
+
+ int current=-1;
+ String abspath = dir_access->get_current_dir();
+
+ for(int i=0;i<dir_access->get_drive_count();i++) {
+ String d = dir_access->get_drive(i);
+ if (abspath.begins_with(d))
+ current=i;
+ drives->add_item(dir_access->get_drive(i));
+ }
+
+ if (current!=-1)
+ drives->select(current);
+
+ }
+}
+
+void FileDialog::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_tree_selected"),&FileDialog::_tree_selected);
+ ObjectTypeDB::bind_method(_MD("_tree_db_selected"),&FileDialog::_tree_dc_selected);
+ ObjectTypeDB::bind_method(_MD("_dir_entered"),&FileDialog::_dir_entered);
+ ObjectTypeDB::bind_method(_MD("_file_entered"),&FileDialog::_file_entered);
+ ObjectTypeDB::bind_method(_MD("_action_pressed"),&FileDialog::_action_pressed);
+ ObjectTypeDB::bind_method(_MD("_cancel_pressed"),&FileDialog::_cancel_pressed);
+ ObjectTypeDB::bind_method(_MD("_filter_selected"),&FileDialog::_filter_selected);
+ ObjectTypeDB::bind_method(_MD("_save_confirm_pressed"),&FileDialog::_save_confirm_pressed);
+
+ ObjectTypeDB::bind_method(_MD("clear_filters"),&FileDialog::clear_filters);
+ ObjectTypeDB::bind_method(_MD("add_filter","filter"),&FileDialog::add_filter);
+ ObjectTypeDB::bind_method(_MD("get_current_dir"),&FileDialog::get_current_dir);
+ ObjectTypeDB::bind_method(_MD("get_current_file"),&FileDialog::get_current_file);
+ ObjectTypeDB::bind_method(_MD("get_current_path"),&FileDialog::get_current_path);
+ ObjectTypeDB::bind_method(_MD("set_current_dir","dir"),&FileDialog::set_current_dir);
+ ObjectTypeDB::bind_method(_MD("set_current_file","file"),&FileDialog::set_current_file);
+ ObjectTypeDB::bind_method(_MD("set_current_path","path"),&FileDialog::set_current_path);
+ ObjectTypeDB::bind_method(_MD("set_mode","mode"),&FileDialog::set_mode);
+ ObjectTypeDB::bind_method(_MD("get_mode"),&FileDialog::get_mode);
+ ObjectTypeDB::bind_method(_MD("get_vbox:VBoxContainer"),&FileDialog::get_vbox);
+ ObjectTypeDB::bind_method(_MD("set_access","access"),&FileDialog::set_access);
+ ObjectTypeDB::bind_method(_MD("get_access"),&FileDialog::get_access);
+ ObjectTypeDB::bind_method(_MD("_select_drive"),&FileDialog::_select_drive);
+ ObjectTypeDB::bind_method(_MD("_make_dir"),&FileDialog::_make_dir);
+ ObjectTypeDB::bind_method(_MD("_make_dir_confirm"),&FileDialog::_make_dir_confirm);
+ ObjectTypeDB::bind_method(_MD("_update_file_list"),&FileDialog::update_file_list);
+ ObjectTypeDB::bind_method(_MD("_update_dir"),&FileDialog::update_dir);
+
+ ObjectTypeDB::bind_method(_MD("invalidate"),&FileDialog::invalidate);
+
+ ADD_SIGNAL(MethodInfo("file_selected",PropertyInfo( Variant::STRING,"path")));
+ ADD_SIGNAL(MethodInfo("files_selected",PropertyInfo( Variant::STRING_ARRAY,"paths")));
+ ADD_SIGNAL(MethodInfo("dir_selected",PropertyInfo( Variant::STRING,"dir")));
+
+ BIND_CONSTANT( MODE_OPEN_FILE );
+ BIND_CONSTANT( MODE_OPEN_FILES );
+ BIND_CONSTANT( MODE_OPEN_DIR );
+ BIND_CONSTANT( MODE_SAVE_FILE );
+
+ BIND_CONSTANT( ACCESS_RESOURCES );
+ BIND_CONSTANT( ACCESS_USERDATA );
+ BIND_CONSTANT( ACCESS_FILESYSTEM );
+
+}
+
+
+
+FileDialog::FileDialog() {
+
+ VBoxContainer *vbc = memnew( VBoxContainer );
+ add_child(vbc);
+ set_child_rect(vbc);
+
+ mode=MODE_SAVE_FILE;
+ set_title("Save a File");
+
+ dir = memnew(LineEdit);
+ HBoxContainer *pathhb = memnew( HBoxContainer );
+ pathhb->add_child(dir);
+ dir->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ drives = memnew( OptionButton );
+ pathhb->add_child(drives);
+ drives->connect("item_selected",this,"_select_drive");
+
+ makedir = memnew( Button );
+ makedir->set_text("Create Folder");
+ makedir->connect("pressed",this,"_make_dir");
+ pathhb->add_child(makedir);
+
+ vbc->add_margin_child("Path:",pathhb);
+
+ tree = memnew(Tree);
+ tree->set_hide_root(true);
+ vbc->add_margin_child("Directories & Files:",tree,true);
+
+ file = memnew(LineEdit);
+ //add_child(file);
+ vbc->add_margin_child("File:",file);
+
+
+ filter = memnew( OptionButton );
+ //add_child(filter);
+ vbc->add_margin_child("Filter:",filter);
+ filter->set_clip_text(true);//too many extensions overflow it
+
+ dir_access = DirAccess::create(DirAccess::ACCESS_RESOURCES);
+ access=ACCESS_RESOURCES;
+ _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());
+ dir->connect("text_entered", this,"_dir_entered");
+ file->connect("text_entered", this,"_file_entered");
+ filter->connect("item_selected", this,"_filter_selected");
+
+
+ confirm_save = memnew( ConfirmationDialog );
+ confirm_save->set_as_toplevel(true);
+ add_child(confirm_save);
+
+
+ confirm_save->connect("confirmed", this,"_save_confirm_pressed");
+
+ makedialog = memnew( ConfirmationDialog );
+ makedialog->set_title("Create Folder");
+ VBoxContainer *makevb= memnew( VBoxContainer );
+ makedialog->add_child(makevb);
+ makedialog->set_child_rect(makevb);
+ makedirname = memnew( LineEdit );
+ makevb->add_margin_child("Name:",makedirname);
+ add_child(makedialog);
+ makedialog->register_text_enter(makedirname);
+ makedialog->connect("confirmed",this,"_make_dir_confirm");
+ mkdirerr = memnew( AcceptDialog );
+ mkdirerr->set_text("Could not create folder.");
+ add_child(mkdirerr);
+
+
+ //update_file_list();
+ update_filters();
+ update_dir();
+
+ set_hide_on_ok(false);
+ vbox=vbc;
+
+
+ invalidated=true;
+ if (register_func)
+ register_func(this);
+
+}
+
+
+FileDialog::~FileDialog() {
+
+ if (unregister_func)
+ unregister_func(this);
+ memdelete(dir_access);
+}
+
+
+void LineEditFileChooser::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_browse"),&LineEditFileChooser::_browse);
+ ObjectTypeDB::bind_method(_MD("_chosen"),&LineEditFileChooser::_chosen);
+ ObjectTypeDB::bind_method(_MD("get_button:Button"),&LineEditFileChooser::get_button);
+ ObjectTypeDB::bind_method(_MD("get_line_edit:LineEdit"),&LineEditFileChooser::get_line_edit);
+ ObjectTypeDB::bind_method(_MD("get_file_dialog:FileDialog"),&LineEditFileChooser::get_file_dialog);
+
+}
+
+void LineEditFileChooser::_chosen(const String& p_text){
+
+ line_edit->set_text(p_text);
+ line_edit->emit_signal("text_entered",p_text);
+}
+
+void LineEditFileChooser::_browse() {
+
+ dialog->popup_centered_ratio();
+}
+
+LineEditFileChooser::LineEditFileChooser() {
+
+ line_edit = memnew( LineEdit );
+ add_child(line_edit);
+ line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ button = memnew( Button );
+ button->set_text(" .. ");
+ add_child(button);
+ button->connect("pressed",this,"_browse");
+ dialog = memnew( FileDialog);
+ add_child(dialog);
+ dialog->connect("file_selected",this,"_chosen");
+ dialog->connect("dir_selected",this,"_chosen");
+ dialog->connect("files_selected",this,"_chosen");
+
+}
diff --git a/scene/gui/file_dialog.h b/scene/gui/file_dialog.h
new file mode 100644
index 0000000000..6f274de52a
--- /dev/null
+++ b/scene/gui/file_dialog.h
@@ -0,0 +1,173 @@
+/*************************************************************************/
+/* file_dialog.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef FILE_DIALOG_H
+#define FILE_DIALOG_H
+
+#include "scene/gui/dialogs.h"
+#include "scene/gui/tree.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/option_button.h"
+#include "scene/gui/dialogs.h"
+#include "os/dir_access.h"
+#include "box_container.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class FileDialog : public ConfirmationDialog {
+
+ OBJ_TYPE( FileDialog, ConfirmationDialog );
+
+public:
+
+ enum Access {
+ ACCESS_RESOURCES,
+ ACCESS_USERDATA,
+ ACCESS_FILESYSTEM
+ };
+
+
+ enum Mode {
+ MODE_OPEN_FILE,
+ MODE_OPEN_FILES,
+ MODE_OPEN_DIR,
+ MODE_SAVE_FILE,
+ };
+
+ typedef Ref<Texture> (*GetIconFunc)(const String&);
+ typedef void (*RegisterFunc)(FileDialog*);
+
+ static GetIconFunc get_icon_func;
+ static GetIconFunc get_large_icon_func;
+ static RegisterFunc register_func;
+ static RegisterFunc unregister_func;
+
+private:
+
+ ConfirmationDialog *makedialog;
+ LineEdit *makedirname;
+
+ Button *makedir;
+ Access access;
+ //Button *action;
+ VBoxContainer *vbox;
+ Mode mode;
+ LineEdit *dir;
+ OptionButton *drives;
+ Tree *tree;
+ LineEdit *file;
+ AcceptDialog *mkdirerr;
+ OptionButton *filter;
+ DirAccess *dir_access;
+ ConfirmationDialog *confirm_save;
+
+ Vector<String> filters;
+
+ bool invalidated;
+
+ void update_dir();
+ void update_file_list();
+ void update_filters();
+
+ void _tree_selected();
+
+ void _select_drive(int p_idx);
+ void _tree_dc_selected();
+ void _dir_entered(String p_dir);
+ void _file_entered(const String& p_file);
+ void _action_pressed();
+ void _save_confirm_pressed();
+ void _cancel_pressed();
+ void _filter_selected(int);
+ void _make_dir();
+ void _make_dir_confirm();
+
+ void _update_drives();
+
+ virtual void _post_popup();
+
+protected:
+
+ void _notification(int p_what);
+ static void _bind_methods();
+ //bind helpers
+public:
+
+ void clear_filters();
+ void add_filter(const String& p_filter);
+
+ void set_enable_multiple_selection(bool p_enable);
+ Vector<String> get_selected_files() const;
+
+ String get_current_dir() const;
+ String get_current_file() const;
+ String get_current_path() const;
+ void set_current_dir(const String& p_dir);
+ void set_current_file(const String& p_file);
+ void set_current_path(const String& p_path);
+
+ void set_mode(Mode p_mode);
+ Mode get_mode() const;
+
+ VBoxContainer *get_vbox();
+ LineEdit *get_line_edit() { return file; }
+
+ void set_access(Access p_access);
+ Access get_access() const;
+
+ void invalidate();
+
+ FileDialog();
+ ~FileDialog();
+
+};
+
+class LineEditFileChooser : public HBoxContainer {
+
+ OBJ_TYPE( LineEditFileChooser, HBoxContainer );
+ Button *button;
+ LineEdit *line_edit;
+ FileDialog *dialog;
+
+ void _chosen(const String& p_text);
+ void _browse();
+protected:
+ static void _bind_methods();
+public:
+
+ Button *get_button() { return button; }
+ LineEdit *get_line_edit() { return line_edit; }
+ FileDialog *get_file_dialog() { return dialog; }
+
+ LineEditFileChooser();
+};
+
+VARIANT_ENUM_CAST( FileDialog::Mode );
+VARIANT_ENUM_CAST( FileDialog::Access );
+
+#endif
diff --git a/scene/gui/grid_container.cpp b/scene/gui/grid_container.cpp
new file mode 100644
index 0000000000..f54345cdb8
--- /dev/null
+++ b/scene/gui/grid_container.cpp
@@ -0,0 +1,230 @@
+/*************************************************************************/
+/* grid_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "grid_container.h"
+
+
+void GridContainer::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_SORT_CHILDREN: {
+
+ Map<int,int> col_minw;
+ Map<int,int> row_minh;
+ Set<int> col_expanded;
+ Set<int> row_expanded;
+
+ int sep=get_constant("separation");
+
+ int idx=0;
+ int max_row=0;
+ int max_col=0;
+
+ Size2 size = get_size();
+
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+
+ int row = idx / columns;
+ int col = idx % columns;
+
+ Size2i ms = c->get_combined_minimum_size();
+ if (col_minw.has(col))
+ col_minw[col] = MAX(col_minw[col],ms.width);
+ else
+ col_minw[col]=ms.width;
+
+ if (row_minh.has(row))
+ row_minh[row] = MAX(row_minh[row],ms.height);
+ else
+ row_minh[row]=ms.height;
+
+
+ if (c->get_h_size_flags()&SIZE_EXPAND)
+ col_expanded.insert(col);
+ if (c->get_v_size_flags()&SIZE_EXPAND)
+ row_expanded.insert(row);
+
+ max_col=MAX(col,max_col);
+ max_row=MAX(row,max_row);
+ idx++;
+ }
+
+ Size2 ms;
+ int expand_rows=0;
+ int expand_cols=0;
+
+ for (Map<int,int>::Element *E=col_minw.front();E;E=E->next()) {
+ ms.width+=E->get();
+ if (col_expanded.has(E->key()))
+ expand_cols++;
+ }
+
+ for (Map<int,int>::Element *E=row_minh.front();E;E=E->next()) {
+ ms.height+=E->get();
+ if (row_expanded.has(E->key()))
+ expand_rows++;
+ }
+
+ ms.height+=sep*max_row;
+ ms.width+=sep*max_col;
+
+ int row_expand = expand_rows?(size.y-ms.y)/expand_rows:0;
+ int col_expand = expand_cols?(size.x-ms.x)/expand_cols:0;
+
+
+ int col_ofs=0;
+ int row_ofs=0;
+ idx=0;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ int row = idx / columns;
+ int col = idx % columns;
+
+ if (col==0) {
+ col_ofs=0;
+ if (row>0 && row_minh.has(row-1))
+ row_ofs+=row_minh[row-1]+sep+(row_expanded.has(row-1)?row_expand:0);
+ }
+
+ if (c->is_visible()) {
+ Size2 s;
+ if (col_minw.has(col))
+ s.width=col_minw[col];
+ if (row_minh.has(row))
+ s.height=row_minh[col];
+
+ if (row_expanded.has(row))
+ s.height+=row_expand;
+ if (col_expanded.has(col))
+ s.width+=col_expand;
+
+ Point2 p(col_ofs,row_ofs);
+
+ fit_child_in_rect(c,Rect2(p,s));
+
+ }
+
+ if (col_minw.has(col)) {
+ col_ofs+=col_minw[col]+sep+(col_expanded.has(col)?col_expand:0);
+ }
+
+ idx++;
+ }
+
+
+
+ } break;
+ }
+
+}
+
+void GridContainer::set_columns(int p_columns) {
+
+ columns=p_columns;
+ queue_sort();
+
+}
+
+int GridContainer::get_columns() const{
+
+ return columns;
+}
+
+void GridContainer::_bind_methods(){
+
+ ObjectTypeDB::bind_method(_MD("set_columns","columns"),&GridContainer::set_columns);
+ ObjectTypeDB::bind_method(_MD("get_columns"),&GridContainer::get_columns);
+
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"columns",PROPERTY_HINT_RANGE,"1,1024,1"),_SCS("set_columns"),_SCS("get_columns"));
+}
+
+Size2 GridContainer::get_minimum_size() const {
+
+ Map<int,int> col_minw;
+ Map<int,int> row_minh;
+
+ int sep=get_constant("separation");
+
+ int idx=0;
+ int max_row=0;
+ int max_col=0;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ int row = idx / columns;
+ int col = idx % columns;
+ Size2i ms = c->get_combined_minimum_size();
+ if (col_minw.has(col))
+ col_minw[col] = MAX(col_minw[col],ms.width);
+ else
+ col_minw[col]=ms.width;
+
+ if (row_minh.has(row))
+ row_minh[row] = MAX(row_minh[row],ms.height);
+ else
+ row_minh[row]=ms.height;
+ max_col=MAX(col,max_col);
+ max_row=MAX(row,max_row);
+ idx++;
+ }
+
+ Size2 ms;
+
+ for (Map<int,int>::Element *E=col_minw.front();E;E=E->next()) {
+ ms.width+=E->get();
+ }
+
+ for (Map<int,int>::Element *E=row_minh.front();E;E=E->next()) {
+ ms.height+=E->get();
+ }
+
+ ms.height+=sep*max_row;
+ ms.width+=sep*max_col;
+
+ return ms;
+
+}
+
+
+GridContainer::GridContainer() {
+
+ columns=1;
+}
diff --git a/scene/gui/grid_container.h b/scene/gui/grid_container.h
new file mode 100644
index 0000000000..f74ad6c66a
--- /dev/null
+++ b/scene/gui/grid_container.h
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* grid_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef GRID_CONTAINER_H
+#define GRID_CONTAINER_H
+
+#include "scene/gui/container.h"
+
+class GridContainer : public Container {
+
+ OBJ_TYPE(GridContainer,Container);
+
+ int columns;
+protected:
+
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+
+ void set_columns(int p_columns);
+ int get_columns() const;
+ virtual Size2 get_minimum_size() const;
+
+ GridContainer();
+};
+
+#endif // GRID_CONTAINER_H
diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp
new file mode 100644
index 0000000000..a2f837c3c4
--- /dev/null
+++ b/scene/gui/label.cpp
@@ -0,0 +1,619 @@
+/*************************************************************************/
+/* label.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "label.h"
+#include "print_string.h"
+#include "globals.h"
+#include "translation.h"
+
+
+void Label::set_autowrap(bool p_autowrap) {
+
+ autowrap=p_autowrap;
+ word_cache_dirty=true;
+ minimum_size_changed();
+
+
+}
+bool Label::has_autowrap() const {
+
+ return autowrap;
+}
+
+
+void Label::set_uppercase(bool p_uppercase) {
+
+ uppercase=p_uppercase;
+ word_cache_dirty=true;
+ minimum_size_changed();
+}
+bool Label::is_uppercase() const {
+
+ return uppercase;
+}
+
+
+int Label::get_line_height() const {
+
+ return get_font("font")->get_height();
+
+}
+
+
+void Label::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ if (clip && !autowrap)
+ VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(),true);
+
+ if (word_cache_dirty)
+ regenerate_word_cache();
+
+
+ RID ci = get_canvas_item();
+
+ Size2 string_size;
+ Size2 size=get_size();
+
+ Ref<Font> font = get_font("font");
+ Color font_color = get_color("font_color");
+ Color font_color_shadow = get_color("font_color_shadow");
+ bool use_outlinde = get_constant("shadow_as_outline");
+ Point2 shadow_ofs(get_constant("shadow_offset_x"),get_constant("shadow_offset_y"));
+
+
+ int font_h = font->get_height();
+ int line_from=(int)get_val(); // + p_exposed.pos.y / font_h;
+ int lines_visible = size.y/font_h;
+ int line_to=(int)get_val() + lines_visible; //p_exposed.pos.y+p_exposed.size.height / font_h;
+ int space_w=font->get_char_size(' ').width;
+ int lines_total = get_max();
+ int chars_total=0;
+
+ int vbegin=0,vsep=0;
+
+ if (lines_total && lines_total < lines_visible) {
+
+
+ switch(valign) {
+
+ case VALIGN_TOP: {
+
+ //nothing
+ } break;
+ case VALIGN_CENTER: {
+
+ vbegin=(lines_visible-lines_total) * font_h / 2;
+ vsep=0;
+ } break;
+ case VALIGN_BOTTOM: {
+ vbegin=(lines_visible-lines_total) * font_h;
+ vsep=0;
+
+ } break;
+ case VALIGN_FILL: {
+ vbegin=0;
+ if (lines_total>1) {
+ vsep=(lines_visible-lines_total) * font_h / (lines_total-1);
+ } else {
+ vsep=0;
+ }
+
+ } break;
+ }
+ }
+
+
+ WordCache *wc = word_cache;
+ if (!wc)
+ return;
+
+
+ int line=0;
+ while(wc) {
+
+ /* handle lines not meant to be drawn quickly */
+ if (line>line_to)
+ break;
+ if (line<line_from) {
+
+ while (wc && wc->char_pos>=0)
+ wc=wc->next;
+ if (wc)
+ wc=wc->next;
+ line++;
+ continue;
+ }
+
+ /* handle lines normally */
+
+ if (wc->char_pos<0) {
+ //empty line
+ wc=wc->next;
+ line++;
+ continue;
+ }
+
+ WordCache *from=wc;
+ WordCache *to=wc;
+
+ int taken=0;
+ int spaces=0;
+ while(to && to->char_pos>=0) {
+
+ taken+=to->pixel_width;
+ if (to!=from) {
+ spaces++;
+ }
+ to=to->next;
+ }
+
+ bool can_fill = to && to->char_pos==WordCache::CHAR_WRAPLINE;
+
+ float x_ofs=0;
+
+ switch (align) {
+
+ case ALIGN_FILL:
+ case ALIGN_LEFT: {
+
+ x_ofs=0;
+ } break;
+ case ALIGN_CENTER: {
+
+ x_ofs=int(size.width-(taken+spaces*space_w))/2;;
+
+ } break;
+ case ALIGN_RIGHT: {
+
+
+ x_ofs=int(size.width-(taken+spaces*space_w));
+
+ } break;
+ }
+
+ int y_ofs=(line-(int)get_val())*font_h + font->get_ascent();
+ y_ofs+=vbegin + line*vsep;
+
+ while(from!=to) {
+
+ // draw a word
+ int pos = from->char_pos;
+ if (from->char_pos<0) {
+
+ ERR_PRINT("BUG");
+ return;
+ }
+ if (from!=wc) {
+ /* spacing */
+ x_ofs+=space_w;
+ if (can_fill && align==ALIGN_FILL && spaces) {
+
+ x_ofs+=int((size.width-(taken+space_w*spaces))/spaces);
+ }
+
+
+ }
+
+
+
+ if (font_color_shadow.a>0) {
+
+ int chars_total_shadow = chars_total; //save chars drawn
+ float x_ofs_shadow=x_ofs;
+ for (int i=0;i<from->word_len;i++) {
+
+ if (visible_chars < 0 || chars_total_shadow<visible_chars) {
+ CharType c = text[i+pos];
+ CharType n = text[i+pos+1];
+ if (uppercase) {
+ c=String::char_uppercase(c);
+ n=String::char_uppercase(c);
+ }
+
+ float move=font->draw_char(ci, Point2( x_ofs_shadow, y_ofs )+shadow_ofs, c, n,font_color_shadow );
+ if (use_outlinde) {
+ font->draw_char(ci, Point2( x_ofs_shadow, y_ofs )+Vector2(-shadow_ofs.x,shadow_ofs.y), c, n,font_color_shadow );
+ font->draw_char(ci, Point2( x_ofs_shadow, y_ofs )+Vector2(shadow_ofs.x,-shadow_ofs.y), c, n,font_color_shadow );
+ font->draw_char(ci, Point2( x_ofs_shadow, y_ofs )+Vector2(-shadow_ofs.x,-shadow_ofs.y), c, n,font_color_shadow );
+ }
+ x_ofs_shadow+=move;
+ chars_total_shadow++;
+ }
+ }
+
+
+ }
+ for (int i=0;i<from->word_len;i++) {
+
+ if (visible_chars < 0 || chars_total<visible_chars) {
+ CharType c = text[i+pos];
+ CharType n = text[i+pos+1];
+ if (uppercase) {
+ c=String::char_uppercase(c);
+ n=String::char_uppercase(c);
+ }
+
+ x_ofs+=font->draw_char(ci,Point2( x_ofs, y_ofs ), c, n, font_color );
+ chars_total++;
+ }
+
+ }
+ from=from->next;
+ }
+
+ wc=to?to->next:0;
+ line++;
+
+ }
+ }
+
+ if (p_what==NOTIFICATION_THEME_CHANGED) {
+
+ word_cache_dirty=true;
+ update();
+ }
+ if (p_what==NOTIFICATION_RESIZED) {
+
+ word_cache_dirty=true;
+ }
+
+}
+
+Size2 Label::get_minimum_size() const {
+
+ if (autowrap)
+ return Size2(1,1);
+ else {
+
+ // don't want to mutable everything
+ if(word_cache_dirty)
+ const_cast<Label*>(this)->regenerate_word_cache();
+
+ Size2 ms=minsize;
+ if (clip)
+ ms.width=1;
+ return ms;
+ }
+}
+
+int Label::get_longest_line_width() const {
+
+ Ref<Font> font = get_font("font");
+ int max_line_width=0;
+ int line_width=0;
+
+ for (int i=0;i<text.size()+1;i++) {
+
+ CharType current=i<text.length()?text[i]:' '; //always a space at the end, so the algo works
+ if (uppercase)
+ current=String::char_uppercase(current);
+
+ if (current<32) {
+
+ if (current=='\n') {
+
+ if (line_width>max_line_width)
+ max_line_width=line_width;
+ line_width=0;
+ }
+ } else {
+
+ int char_width=font->get_char_size(current).width;
+ line_width+=char_width;
+ }
+
+ }
+
+ if (line_width>max_line_width)
+ max_line_width=line_width;
+
+ return max_line_width;
+}
+
+int Label::get_line_count() const {
+
+ if (!is_inside_scene())
+ return 1;
+ if (word_cache_dirty)
+ const_cast<Label*>(this)->regenerate_word_cache();
+
+ return line_count;
+}
+
+void Label::regenerate_word_cache() {
+
+ while (word_cache) {
+
+ WordCache *current=word_cache;
+ word_cache=current->next;
+ memdelete( current );
+ }
+
+
+ int width=autowrap?get_size().width:get_longest_line_width();
+ Ref<Font> font = get_font("font");
+
+ int current_word_size=0;
+ int word_pos=0;
+ int line_width=0;
+ int last_width=0;
+ line_count=1;
+ total_char_cache=0;
+
+ WordCache *last=NULL;
+
+ for (int i=0;i<text.size()+1;i++) {
+
+ CharType current=i<text.length()?text[i]:' '; //always a space at the end, so the algo works
+
+ if (uppercase)
+ current=String::char_uppercase(current);
+
+ bool insert_newline=false;
+
+ if (current<33) {
+
+ if (current_word_size>0) {
+
+ WordCache *wc = memnew( WordCache );
+ if (word_cache) {
+ last->next=wc;
+ } else {
+ word_cache=wc;
+ }
+ last=wc;
+
+ wc->pixel_width=current_word_size;
+ wc->char_pos=word_pos;
+ wc->word_len=i-word_pos;
+ current_word_size=0;
+
+ }
+
+ if (current=='\n') {
+
+ insert_newline=true;
+ } else {
+ total_char_cache++;
+ }
+
+
+ } else {
+
+ if (current_word_size==0) {
+ if (line_width>0) // add a space before the new word if a word existed before
+ line_width+=font->get_char_size(' ').width;
+ word_pos=i;
+ }
+
+ int char_width=font->get_char_size(current).width;
+ current_word_size+=char_width;
+ line_width+=char_width;
+ total_char_cache++;
+
+ }
+
+ if ((autowrap && line_width>=width && last_width<width) || insert_newline) {
+
+ WordCache *wc = memnew( WordCache );
+ if (word_cache) {
+ last->next=wc;
+ } else {
+ word_cache=wc;
+ }
+ last=wc;
+
+ wc->pixel_width=0;
+ wc->char_pos=insert_newline?WordCache::CHAR_NEWLINE:WordCache::CHAR_WRAPLINE;
+
+ line_width=current_word_size;
+ line_count++;
+
+
+ }
+
+ last_width=line_width;
+
+ }
+
+
+ if (!autowrap) {
+
+ minsize.width=width;
+ minsize.height=font->get_height()*line_count;
+ set_page( line_count );
+
+ } else {
+
+ set_page( get_size().height / font->get_height() );
+ }
+
+ set_max(line_count);
+
+ word_cache_dirty=false;
+
+}
+
+
+void Label::set_align(Align p_align) {
+
+ ERR_FAIL_INDEX(p_align,4);
+ align=p_align;
+ update();
+}
+
+Label::Align Label::get_align() const{
+
+ return align;
+}
+
+void Label::set_valign(VAlign p_align) {
+
+ ERR_FAIL_INDEX(p_align,4);
+ valign=p_align;
+ update();
+}
+
+Label::VAlign Label::get_valign() const{
+
+ return valign;
+}
+
+void Label::set_text(const String& p_string) {
+
+ String str = XL_MESSAGE(p_string);
+
+ if (text==str)
+ return;
+
+ text=str;
+ word_cache_dirty=true;
+ update();
+ if (!autowrap)
+ minimum_size_changed();
+
+}
+
+void Label::set_clip_text(bool p_clip) {
+
+ if (clip==p_clip)
+ return;
+ clip=p_clip;
+ update();
+ minimum_size_changed();
+}
+
+bool Label::is_clipping_text() const {
+
+ return clip;
+}
+
+String Label::get_text() const {
+
+ return text;
+}
+void Label::set_visible_characters(int p_amount) {
+
+ visible_chars=p_amount;
+ update();
+}
+
+void Label::set_percent_visible(float p_percent) {
+
+ if (p_percent<0)
+ set_visible_characters(-1);
+ else
+ set_visible_characters(get_total_character_count()*p_percent);
+ percent_visible=p_percent;
+}
+
+float Label::get_percent_visible() const{
+
+ return percent_visible;
+}
+
+
+int Label::get_total_character_count() const {
+
+ if (word_cache_dirty)
+ const_cast<Label*>(this)->regenerate_word_cache();
+
+ return total_char_cache;
+}
+
+void Label::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_align","align"),&Label::set_align);
+ ObjectTypeDB::bind_method(_MD("get_align"),&Label::get_align);
+ ObjectTypeDB::bind_method(_MD("set_valign","valign"),&Label::set_valign);
+ ObjectTypeDB::bind_method(_MD("get_valign"),&Label::get_valign);
+ ObjectTypeDB::bind_method(_MD("set_text","text"),&Label::set_text);
+ ObjectTypeDB::bind_method(_MD("get_text"),&Label::get_text);
+ ObjectTypeDB::bind_method(_MD("set_autowrap","enable"),&Label::set_autowrap);
+ ObjectTypeDB::bind_method(_MD("has_autowrap"),&Label::has_autowrap);
+ ObjectTypeDB::bind_method(_MD("set_uppercase","enable"),&Label::set_uppercase);
+ ObjectTypeDB::bind_method(_MD("is_uppercase"),&Label::is_uppercase);
+ ObjectTypeDB::bind_method(_MD("get_line_height"),&Label::get_line_height);
+ ObjectTypeDB::bind_method(_MD("get_line_count"),&Label::get_line_count);
+ ObjectTypeDB::bind_method(_MD("get_total_character_count"),&Label::get_total_character_count);
+ ObjectTypeDB::bind_method(_MD("set_visible_characters"),&Label::set_visible_characters);
+ ObjectTypeDB::bind_method(_MD("set_percent_visible","percent_visible"),&Label::set_percent_visible);
+ ObjectTypeDB::bind_method(_MD("get_percent_visible"),&Label::get_percent_visible);
+
+ BIND_CONSTANT( ALIGN_LEFT );
+ BIND_CONSTANT( ALIGN_CENTER );
+ BIND_CONSTANT( ALIGN_RIGHT );
+ BIND_CONSTANT( ALIGN_FILL );
+
+ BIND_CONSTANT( VALIGN_TOP );
+ BIND_CONSTANT( VALIGN_CENTER );
+ BIND_CONSTANT( VALIGN_BOTTOM );
+ BIND_CONSTANT( VALIGN_FILL );
+
+ ADD_PROPERTY( PropertyInfo( Variant::STRING, "text",PROPERTY_HINT_MULTILINE_TEXT,"",PROPERTY_USAGE_DEFAULT_INTL), _SCS("set_text"),_SCS("get_text") );
+ ADD_PROPERTY( PropertyInfo( Variant::INT, "align", PROPERTY_HINT_ENUM,"Left,Center,Right,Fill" ),_SCS("set_align"),_SCS("get_align") );
+ ADD_PROPERTY( PropertyInfo( Variant::INT, "valign", PROPERTY_HINT_ENUM,"Top,Center,Bottom,Fill" ),_SCS("set_valign"),_SCS("get_valign") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "autowrap"),_SCS("set_autowrap"),_SCS("has_autowrap") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "uppercase"),_SCS("set_uppercase"),_SCS("is_uppercase") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "percent_visible"),_SCS("set_percent_visible"),_SCS("get_percent_visible") );
+
+}
+
+Label::Label(const String &p_text) {
+
+ align=ALIGN_LEFT;
+ valign=VALIGN_TOP;
+ text="";
+ word_cache=NULL;
+ word_cache_dirty=true;
+ autowrap=false;
+ line_count=0;
+ set_v_size_flags(0);
+ clip=false;
+ set_ignore_mouse(true);
+ total_char_cache=0;
+ visible_chars=-1;
+ percent_visible=-1;
+ set_text(p_text);
+ uppercase=false;
+}
+
+
+Label::~Label() {
+
+ while (word_cache) {
+
+ WordCache *current=word_cache;
+ word_cache=current->next;
+ memdelete( current );
+ }
+}
+
+
diff --git a/scene/gui/label.h b/scene/gui/label.h
new file mode 100644
index 0000000000..131ee3a944
--- /dev/null
+++ b/scene/gui/label.h
@@ -0,0 +1,136 @@
+/*************************************************************************/
+/* label.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LABEL_H
+#define LABEL_H
+
+#include "scene/gui/range.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class Label : public Range {
+
+ OBJ_TYPE( Label, Range );
+public:
+
+ enum Align {
+
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT,
+ ALIGN_FILL
+ };
+
+ enum VAlign {
+
+ VALIGN_TOP,
+ VALIGN_CENTER,
+ VALIGN_BOTTOM,
+ VALIGN_FILL
+ };
+
+private:
+ Align align;
+ VAlign valign;
+ String text;
+ bool autowrap;
+ bool clip;
+ Size2 minsize;
+ int line_count;
+ bool uppercase;
+
+ int get_longest_line_width() const;
+
+ struct WordCache {
+
+ enum {
+ CHAR_NEWLINE=-1,
+ CHAR_WRAPLINE=-2
+ };
+ int char_pos; // if -1, then newline
+ int word_len;
+ int pixel_width;
+ WordCache *next;
+ WordCache() { char_pos=0; word_len=0; pixel_width=0; next=0; }
+ };
+
+ bool word_cache_dirty;
+ void regenerate_word_cache();
+
+ float percent_visible;
+
+ WordCache *word_cache;
+ int total_char_cache;
+ int visible_chars;
+protected:
+ void _notification(int p_what);
+
+ static void _bind_methods();
+ // bind helpers
+public:
+
+ virtual Size2 get_minimum_size() const;
+
+ void set_align(Align p_align);
+ Align get_align() const;
+
+ void set_valign(VAlign p_align);
+ VAlign get_valign() const;
+
+ void set_text(const String& p_string);
+ String get_text() const;
+
+ void set_autowrap(bool p_autowrap);
+ bool has_autowrap() const;
+
+ void set_uppercase(bool p_uppercase);
+ bool is_uppercase() const;
+
+ void set_visible_characters(int p_amount);
+ int get_total_character_count() const;
+
+ void set_clip_text(bool p_clip);
+ bool is_clipping_text() const;
+
+ void set_percent_visible(float p_percent);
+ float get_percent_visible() const;
+
+
+ int get_line_height() const;
+ int get_line_count() const;
+
+ Label(const String& p_text=String());
+ ~Label();
+
+};
+
+
+VARIANT_ENUM_CAST( Label::Align );
+VARIANT_ENUM_CAST( Label::VAlign );
+
+#endif
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
new file mode 100644
index 0000000000..ad1b7fd66b
--- /dev/null
+++ b/scene/gui/line_edit.cpp
@@ -0,0 +1,821 @@
+/*************************************************************************/
+/* line_edit.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "line_edit.h"
+#include "os/keyboard.h"
+#include "os/os.h"
+#include "print_string.h"
+#include "label.h"
+
+void LineEdit::_input_event(InputEvent p_event) {
+
+
+ switch(p_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ const InputEventMouseButton &b = p_event.mouse_button;
+
+ if (b.button_index!=1)
+ break;
+
+ if (b.pressed) {
+
+ set_cursor_at_pixel_pos(b.x);
+
+ if (b.doubleclick) {
+
+ selection.enabled=true;
+ selection.begin=0;
+ selection.end=text.length();
+ selection.doubleclick=true;
+ }
+
+ selection.drag_attempt=false;
+
+ if ((cursor_pos<selection.begin) || (cursor_pos>selection.end) || !selection.enabled) {
+
+ selection_clear();
+ selection.cursor_start=cursor_pos;
+ selection.creating=true;
+ } else if (selection.enabled) {
+
+ selection.drag_attempt=true;
+ }
+
+ // if (!editable)
+ // non_editable_clicked_signal.call();
+ update();
+
+ } else {
+
+ if ( (!selection.creating) && (!selection.doubleclick)) {
+ selection_clear();
+ }
+ selection.creating=false;
+ selection.doubleclick=false;
+ }
+
+ update();
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ const InputEventMouseMotion& m=p_event.mouse_motion;
+
+ if (m.button_mask&1) {
+
+ if (selection.creating) {
+ set_cursor_at_pixel_pos(m.x);
+ selection_fill_at_cursor();
+ }
+ }
+
+ } break;
+ case InputEvent::KEY: {
+
+ const InputEventKey &k =p_event.key;
+
+ if (!k.pressed)
+ return;
+ unsigned int code = k.scancode;
+
+
+ if (k.mod.command) {
+
+ bool handled=true;
+
+ switch (code) {
+
+ case (KEY_X): { // CUT
+
+ if(k.mod.command && editable) {
+ cut_text();
+ }
+
+ } break;
+
+ case (KEY_C): { // COPY
+
+ if(k.mod.command) {
+ copy_text();
+ }
+
+ } break;
+
+ case (KEY_V): { // PASTE
+
+ if(k.mod.command && editable) {
+
+ paste_text();
+ }
+
+ } break;
+
+ case (KEY_Z): { // Simple One level undo
+
+ if( k.mod.command && editable) {
+
+ int old_cursor_pos = cursor_pos;
+ text = undo_text;
+ if(old_cursor_pos > text.length()) {
+ set_cursor_pos(text.length());
+ } else {
+ set_cursor_pos(old_cursor_pos);
+ }
+ }
+
+ emit_signal("text_changed",text);
+ _change_notify("text");
+
+ } break;
+
+ case (KEY_U): { // Delete from start to cursor
+
+ if( k.mod.command && editable) {
+
+ selection_clear();
+ undo_text = text;
+ text = text.substr(cursor_pos,text.length()-cursor_pos);
+ set_cursor_pos(0);
+ emit_signal("text_changed",text);
+ _change_notify("text");
+ }
+
+
+ } break;
+
+ case (KEY_Y): { // PASTE (Yank for unix users)
+
+ if(k.mod.command && editable) {
+
+ paste_text();
+ }
+
+ } break;
+ case (KEY_K): { // Delete from cursor_pos to end
+
+ if(k.mod.command && editable) {
+
+ selection_clear();
+ undo_text = text;
+ text = text.substr(0,cursor_pos);
+ emit_signal("text_changed",text);
+ _change_notify("text");
+ }
+
+ } break;
+ default: { handled=false;}
+ }
+
+ if (handled) {
+ accept_event();
+ return;
+ }
+ }
+
+
+ if (!k.mod.alt && !k.mod.meta && !k.mod.command) {
+
+ bool handled=true;
+ switch (code) {
+
+ case KEY_ENTER:
+ case KEY_RETURN: {
+
+ emit_signal( "text_entered",text );
+ return;
+ } break;
+
+ case KEY_BACKSPACE: {
+
+ if (editable) {
+ undo_text = text;
+ if (selection.enabled)
+ selection_delete();
+ else
+ delete_char();
+ }
+ } break;
+ case KEY_LEFT: {
+ shift_selection_check_pre(k.mod.shift);
+ set_cursor_pos(get_cursor_pos()-1);
+ shift_selection_check_post(k.mod.shift);
+
+ } break;
+ case KEY_RIGHT: {
+
+ shift_selection_check_pre(k.mod.shift);
+ set_cursor_pos(get_cursor_pos()+1);
+ shift_selection_check_post(k.mod.shift);
+ } break;
+ case KEY_DELETE: {
+
+ if (editable) {
+ undo_text = text;
+ if (selection.enabled)
+ selection_delete();
+ else if (cursor_pos<text.length()) {
+
+ set_cursor_pos(get_cursor_pos()+1);
+ delete_char();
+ }
+ }
+
+ } break;
+ case KEY_HOME: {
+
+ shift_selection_check_pre(k.mod.shift);
+ set_cursor_pos(0);
+ shift_selection_check_post(k.mod.shift);
+ } break;
+ case KEY_END: {
+
+ shift_selection_check_pre(k.mod.shift);
+ set_cursor_pos(text.length());
+ shift_selection_check_post(k.mod.shift);
+ } break;
+
+
+ default: {
+
+ if (k.unicode>=32 && k.scancode!=KEY_DELETE) {
+
+ if (editable) {
+ selection_delete();
+ CharType ucodestr[2]={k.unicode,0};
+ append_at_cursor(ucodestr);
+ emit_signal("text_changed",text);
+ _change_notify("text");
+ }
+
+ } else {
+ handled=false;
+ }
+ } break;
+ }
+
+ if (handled)
+ accept_event();
+ else
+ return;
+
+
+ selection.old_shift=k.mod.shift;
+ update();
+
+ }
+
+
+ return;
+
+ } break;
+
+ }
+}
+
+Variant LineEdit::get_drag_data(const Point2& p_point) {
+
+ if (selection.drag_attempt && selection.enabled) {
+ String t = text.substr(selection.begin, selection.end - selection.begin);
+ Label *l = memnew( Label );
+ l->set_text(t);
+ set_drag_preview(l);
+ return t;
+ }
+
+ return Variant();
+
+}
+bool LineEdit::can_drop_data(const Point2& p_point,const Variant& p_data) const{
+
+ return p_data.get_type()==Variant::STRING;
+}
+void LineEdit::drop_data(const Point2& p_point,const Variant& p_data){
+
+ if (p_data.get_type()==Variant::STRING) {
+
+ set_cursor_at_pixel_pos(p_point.x);
+ append_at_cursor(p_data);
+ }
+}
+
+
+void LineEdit::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_RESIZED: {
+
+ set_cursor_pos( get_cursor_pos() );
+
+ } break;
+ case NOTIFICATION_DRAW: {
+
+ int width,height;
+
+ Size2 size=get_size();
+ width=size.width;
+ height=size.height;
+
+ RID ci = get_canvas_item();
+
+ Ref<StyleBox> style = get_stylebox("normal");
+ if (!is_editable())
+ style=get_stylebox("read_only");
+
+ Ref<Font> font=get_font("font");
+
+ style->draw( ci, Rect2( Point2(), size ) );
+
+ if (has_focus()) {
+
+ get_stylebox("focus")->draw( ci, Rect2( Point2(), size ) );
+ }
+
+
+ int ofs=style->get_offset().x;
+ int ofs_max=width-style->get_minimum_size().width;
+ int char_ofs=window_pos;
+
+ int y_area=height-style->get_minimum_size().height;
+ int y_ofs=style->get_offset().y;
+
+ int font_ascent=font->get_ascent();
+
+ Color selection_color=get_color("selection_color");
+ Color font_color=get_color("font_color");
+ Color font_color_selected=get_color("font_color_selected");
+ Color cursor_color=get_color("cursor_color");
+
+ while(true) {
+
+ //end of string, break!
+ if (char_ofs>=text.length())
+ break;
+
+ CharType cchar=pass?'*':text[char_ofs];
+ CharType next=pass?'*':text[char_ofs+1];
+ int char_width=font->get_char_size( cchar,next ).width;
+
+ // end of widget, break!
+ if ( (ofs+char_width) > ofs_max )
+ break;
+
+
+ bool selected=selection.enabled && char_ofs>=selection.begin && char_ofs<selection.end;
+
+ if (selected)
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2( Point2( ofs , y_ofs ),Size2( char_width, y_area )),selection_color);
+
+
+ font->draw_char(ci,Point2( ofs , y_ofs+font_ascent ), cchar, next,selected?font_color_selected:font_color );
+
+ if (char_ofs==cursor_pos && has_focus())
+ VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(
+ Point2( ofs , y_ofs ), Size2( 1, y_area ) ), cursor_color );
+
+ ofs+=char_width;
+ char_ofs++;
+ }
+
+ if (char_ofs==cursor_pos && has_focus()) //may be at the end
+ VisualServer::get_singleton()->canvas_item_add_rect(ci, Rect2(
+ Point2( ofs , y_ofs ), Size2( 1, y_area ) ), cursor_color );
+
+ } break;
+ case NOTIFICATION_FOCUS_ENTER: {
+
+ if (OS::get_singleton()->has_virtual_keyboard())
+ OS::get_singleton()->show_virtual_keyboard(get_text(),get_global_rect());
+
+ } break;
+ case NOTIFICATION_FOCUS_EXIT: {
+
+ if (OS::get_singleton()->has_virtual_keyboard())
+ OS::get_singleton()->hide_virtual_keyboard();
+
+ } break;
+
+ }
+}
+
+void LineEdit::copy_text() {
+
+ if(selection.enabled) {
+
+ OS::get_singleton()->set_clipboard(text.substr(selection.begin, selection.end - selection.begin));
+ }
+}
+
+void LineEdit::cut_text() {
+
+ if(selection.enabled) {
+ undo_text = text;
+ OS::get_singleton()->set_clipboard(text.substr(selection.begin, selection.end - selection.begin));
+ selection_delete();
+ }
+}
+
+void LineEdit::paste_text() {
+
+ String paste_buffer = OS::get_singleton()->get_clipboard();
+
+ if(paste_buffer != "") {
+
+ if(selection.enabled) selection_delete();
+ append_at_cursor(paste_buffer);
+
+ emit_signal("text_changed",text);
+ _change_notify("text");
+ }
+
+
+
+}
+
+void LineEdit::shift_selection_check_pre(bool p_shift) {
+
+ if (!selection.old_shift && p_shift) {
+ selection.cursor_start=cursor_pos;
+ }
+ if (!p_shift)
+ selection_clear();
+
+}
+
+void LineEdit::shift_selection_check_post(bool p_shift) {
+
+ if (p_shift)
+ selection_fill_at_cursor();
+}
+
+void LineEdit::set_cursor_at_pixel_pos(int p_x) {
+
+ int ofs=window_pos;
+ int pixel_ofs=get_stylebox("normal")->get_offset().x;
+ Ref<Font> font=get_font("font");
+
+ while (ofs<text.length()) {
+
+ int char_w=font->get_char_size( text[ofs] ).width;
+ pixel_ofs+=char_w;
+
+ if (pixel_ofs > p_x) { //found what we look for
+
+
+ if ( (pixel_ofs-p_x) < (char_w >> 1 ) ) {
+
+ ofs+=1;
+ }
+
+ break;
+ }
+
+
+ ofs++;
+ }
+
+ set_cursor_pos( ofs );
+
+ /*
+ int new_cursor_pos=p_x;
+ int charwidth=draw_area->get_font_char_width(' ',0);
+ new_cursor_pos=( ( (new_cursor_pos-2)+ (charwidth/2) ) /charwidth );
+ if (new_cursor_pos>(int)text.length()) new_cursor_pos=text.length();
+ set_cursor_pos(window_pos+new_cursor_pos); */
+}
+
+
+void LineEdit::delete_char() {
+
+ if ((text.length()<=0) || (cursor_pos==0)) return;
+
+
+ text.erase( cursor_pos-1, 1 );
+
+ set_cursor_pos(get_cursor_pos()-1);
+
+ if (cursor_pos==window_pos) {
+
+ // set_window_pos(cursor_pos-get_window_length());
+ }
+
+ emit_signal("text_changed",text);
+ _change_notify("text");
+}
+
+void LineEdit::set_text(String p_text) {
+
+ clear_internal();
+ append_at_cursor(p_text);
+ update();
+ cursor_pos=0;
+ window_pos=0;
+}
+
+void LineEdit::clear() {
+
+ clear_internal();
+}
+
+String LineEdit::get_text() const {
+
+ return text;
+}
+
+void LineEdit::set_cursor_pos(int p_pos) {
+
+ if (p_pos>(int)text.length())
+ p_pos=text.length();
+
+ if(p_pos<0)
+ p_pos=0;
+
+
+
+ cursor_pos=p_pos;
+
+// if (cursor_pos>(window_pos+get_window_length())) {
+// set_window_pos(cursor_pos-get_window_lengt//h());
+// }
+
+ if (!is_inside_scene()) {
+
+ window_pos=cursor_pos;
+ return;
+ }
+
+ Ref<StyleBox> style = get_stylebox("normal");
+ Ref<Font> font=get_font("font");
+
+ if (cursor_pos<window_pos) {
+ /* Adjust window if cursor goes too much to the left */
+ set_window_pos(cursor_pos);
+ } else if (cursor_pos>window_pos) {
+ /* Adjust window if cursor goes too much to the right */
+ int window_width=get_size().width-style->get_minimum_size().width;
+
+ if (window_width<0)
+ return;
+ int width_to_cursor=0;
+ int wp=window_pos;
+
+ for (int i=window_pos;i<cursor_pos;i++)
+ width_to_cursor+=font->get_char_size( text[i] ).width;
+
+ while(width_to_cursor>=window_width && wp<text.length()) {
+
+ width_to_cursor-=font->get_char_size( text[ wp ] ).width;
+ wp++;
+ }
+
+ if (wp!=window_pos)
+ set_window_pos( wp );
+
+ }
+ update();
+}
+
+int LineEdit::get_cursor_pos() const {
+
+ return cursor_pos;
+}
+
+void LineEdit::set_window_pos(int p_pos) {
+
+ window_pos=p_pos;
+ if (window_pos<0) window_pos=0;
+}
+
+void LineEdit::append_at_cursor(String p_text) {
+
+
+ if ( ( max_length <= 0 ) || (text.length()+p_text.length() <= max_length)) {
+
+ undo_text = text;
+ String pre = text.substr( 0, cursor_pos );
+ String post = text.substr( cursor_pos, text.length()-cursor_pos );
+ text=pre+p_text+post;
+ set_cursor_pos(cursor_pos+p_text.length());
+
+ }
+
+}
+
+void LineEdit::clear_internal() {
+
+ cursor_pos=0;
+ window_pos=0;
+ undo_text="";
+ text="";
+ update();
+}
+
+Size2 LineEdit::get_minimum_size() const {
+
+ Ref<StyleBox> style = get_stylebox("normal");
+ Ref<Font> font=get_font("font");
+
+ Size2 min=style->get_minimum_size();
+ min.height+=font->get_height();
+ min.width+=get_constant("minimum_spaces")*font->get_char_size(' ').x;
+
+ return min;
+}
+
+/* selection */
+
+void LineEdit::selection_clear() {
+
+ selection.begin=0;
+ selection.end=0;
+ selection.cursor_start=0;
+ selection.enabled=false;
+ selection.creating=false;
+ selection.old_shift=false;
+ selection.doubleclick=false;
+ update();
+}
+
+
+void LineEdit::selection_delete() {
+
+ if (selection.enabled) {
+
+ undo_text = text;
+ text.erase(selection.begin,selection.end-selection.begin);
+
+ if (cursor_pos>=text.length()) {
+
+ cursor_pos=text.length();
+ }
+ if (window_pos>cursor_pos) {
+
+ window_pos=cursor_pos;
+ }
+
+ emit_signal("text_changed",text);
+ _change_notify("text");
+ };
+
+ selection_clear();
+}
+
+void LineEdit::set_max_length(int p_max_length) {
+
+ ERR_FAIL_COND(p_max_length<0);
+ max_length = p_max_length;
+ set_text(text);
+}
+
+int LineEdit::get_max_length() const {
+
+ return max_length;
+}
+
+void LineEdit::selection_fill_at_cursor() {
+
+ int aux;
+
+ selection.begin=cursor_pos;
+ selection.end=selection.cursor_start;
+
+ if (selection.end<selection.begin) {
+ aux=selection.end;
+ selection.end=selection.begin;
+ selection.begin=aux;
+ }
+
+ selection.enabled=(selection.begin!=selection.end);
+}
+
+void LineEdit::select_all() {
+
+ if (!text.length())
+ return;
+
+ selection.begin=0;
+ selection.end=text.length();
+ selection.enabled=true;
+ update();
+
+}
+void LineEdit::set_editable(bool p_editable) {
+
+ editable=p_editable;
+ update();
+}
+
+bool LineEdit::is_editable() const {
+
+ return editable;
+}
+
+void LineEdit::set_secret(bool p_secret) {
+
+ pass=p_secret;
+ update();
+}
+bool LineEdit::is_secret() const {
+
+ return pass;
+}
+
+void LineEdit::select(int p_from, int p_to) {
+
+ int len = text.length();
+ if (p_from<0)
+ p_from=0;
+ if (p_from>len)
+ p_from=len;
+ if (p_to<0 || p_to>len)
+ p_to=len;
+
+ if (p_from>=p_to)
+ return;
+
+ selection.enabled=true;
+ selection.begin=p_from;
+ selection.end=p_to;
+ selection.creating=false;
+ selection.old_shift=false;
+ selection.doubleclick=false;
+ update();
+}
+
+
+void LineEdit::_bind_methods() {
+
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&LineEdit::_input_event);
+ ObjectTypeDB::bind_method(_MD("clear"),&LineEdit::clear);
+ ObjectTypeDB::bind_method(_MD("select_all"),&LineEdit::select_all);
+ ObjectTypeDB::bind_method(_MD("set_text","text"),&LineEdit::set_text);
+ ObjectTypeDB::bind_method(_MD("get_text"),&LineEdit::get_text);
+ ObjectTypeDB::bind_method(_MD("set_cursor_pos","pos"),&LineEdit::set_cursor_pos);
+ ObjectTypeDB::bind_method(_MD("get_cursor_pos"),&LineEdit::get_cursor_pos);
+ ObjectTypeDB::bind_method(_MD("set_max_length","chars"),&LineEdit::set_max_length);
+ ObjectTypeDB::bind_method(_MD("get_max_length"),&LineEdit::get_max_length);
+ ObjectTypeDB::bind_method(_MD("append_at_cursor","text"),&LineEdit::append_at_cursor);
+ ObjectTypeDB::bind_method(_MD("set_editable","enabled"),&LineEdit::set_editable);
+ ObjectTypeDB::bind_method(_MD("is_editable"),&LineEdit::is_editable);
+ ObjectTypeDB::bind_method(_MD("set_secret","enabled"),&LineEdit::set_secret);
+ ObjectTypeDB::bind_method(_MD("is_secret"),&LineEdit::is_secret);
+ ObjectTypeDB::bind_method(_MD("select","from","to"),&LineEdit::is_secret,DEFVAL(0),DEFVAL(-1));
+
+ ADD_SIGNAL( MethodInfo("text_changed", PropertyInfo( Variant::STRING, "text" )) );
+ ADD_SIGNAL( MethodInfo("text_entered", PropertyInfo( Variant::STRING, "text" )) );
+
+ ADD_PROPERTY( PropertyInfo( Variant::STRING, "text" ), _SCS("set_text"),_SCS("get_text") );
+ ADD_PROPERTY( PropertyInfo( Variant::INT, "max_length" ), _SCS("set_max_length"),_SCS("get_max_length") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "editable" ), _SCS("set_editable"),_SCS("is_editable") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "secret" ), _SCS("set_secret"),_SCS("is_secret") );
+
+}
+
+LineEdit::LineEdit() {
+
+ cursor_pos=0;
+ window_pos=0;
+ max_length = 0;
+ pass=false;
+
+ selection_clear();
+ set_focus_mode( FOCUS_ALL );
+ editable=true;
+ set_default_cursor_shape(CURSOR_IBEAM);
+ set_stop_mouse(true);
+
+
+}
+
+LineEdit::~LineEdit() {
+
+
+}
+
diff --git a/scene/gui/line_edit.h b/scene/gui/line_edit.h
new file mode 100644
index 0000000000..6c18594cda
--- /dev/null
+++ b/scene/gui/line_edit.h
@@ -0,0 +1,120 @@
+/*************************************************************************/
+/* line_edit.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef LINE_EDIT_H
+#define LINE_EDIT_H
+
+#include "scene/gui/control.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class LineEdit : public Control {
+
+ OBJ_TYPE( LineEdit, Control );
+
+ bool editable;
+ bool pass;
+
+ String undo_text;
+ String text;
+
+ int cursor_pos;
+ int window_pos;
+ int max_length; // 0 for no maximum
+
+ struct Selection {
+
+ int begin;
+ int end;
+ int cursor_start;
+ bool enabled;
+ bool creating;
+ bool old_shift;
+ bool doubleclick;
+ bool drag_attempt;
+ } selection;
+
+ void shift_selection_check_pre(bool);
+ void shift_selection_check_post(bool);
+
+ void selection_clear();
+ void selection_fill_at_cursor();
+ void selection_delete();
+ void set_window_pos(int p_pos);
+
+ void set_cursor_at_pixel_pos(int p_x);
+
+ void clear_internal();
+ void changed_internal();
+
+ void copy_text();
+ void cut_text();
+ void paste_text();
+
+
+ void _input_event(InputEvent p_event);
+ void _notification(int p_what);
+
+protected:
+ static void _bind_methods();
+public:
+
+
+ virtual Variant get_drag_data(const Point2& p_point);
+ virtual bool can_drop_data(const Point2& p_point,const Variant& p_data) const;
+ virtual void drop_data(const Point2& p_point,const Variant& p_data);
+
+
+ void select_all();
+
+ void delete_char();
+ void set_text(String p_text);
+ String get_text() const;
+ void set_cursor_pos(int p_pos);
+ int get_cursor_pos() const;
+ void set_max_length(int p_max_length);
+ int get_max_length() const;
+ void append_at_cursor(String p_text);
+ void clear();
+
+
+ void set_editable(bool p_editable);
+ bool is_editable() const;
+
+ void set_secret(bool p_secret);
+ bool is_secret() const;
+
+ void select(int p_from=0, int p_to=-1);
+
+ virtual Size2 get_minimum_size() const;
+ LineEdit();
+ ~LineEdit();
+
+};
+
+#endif
diff --git a/scene/gui/margin_container.cpp b/scene/gui/margin_container.cpp
new file mode 100644
index 0000000000..54373b7592
--- /dev/null
+++ b/scene/gui/margin_container.cpp
@@ -0,0 +1,90 @@
+/*************************************************************************/
+/* margin_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "margin_container.h"
+
+
+Size2 MarginContainer::get_minimum_size() const {
+
+ int margin = get_constant("margin");
+
+ Size2 max;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (c->is_hidden())
+ continue;
+
+ Size2 s = c->get_combined_minimum_size();
+ if (s.width > max.width)
+ max.width = s.width;
+ if (s.height > max.height)
+ max.height = s.height;
+ }
+
+ max.width += margin;
+ return max;
+
+
+}
+
+void MarginContainer::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_SORT_CHILDREN) {
+
+ int margin = get_constant("margin");
+
+ Size2 s = get_size();
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ int w;
+ if (c->get_h_size_flags() & Control::SIZE_EXPAND)
+ w=s.width-margin;
+ else
+ w=c->get_combined_minimum_size().width;
+
+ fit_child_in_rect(c,Rect2(margin,0,s.width-margin,s.height));
+ }
+
+ }
+}
+
+MarginContainer::MarginContainer()
+{
+}
diff --git a/scene/gui/margin_container.h b/scene/gui/margin_container.h
new file mode 100644
index 0000000000..7f0a1f53e3
--- /dev/null
+++ b/scene/gui/margin_container.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* margin_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef MARGIN_CONTAINER_H
+#define MARGIN_CONTAINER_H
+
+#include "scene/gui/container.h"
+
+class MarginContainer : public Container {
+ OBJ_TYPE(MarginContainer,Container);
+
+protected:
+ void _notification(int p_what);
+public:
+
+ virtual Size2 get_minimum_size() const;
+
+ MarginContainer();
+};
+
+#endif // MARGIN_CONTAINER_H
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
new file mode 100644
index 0000000000..4d1609bef9
--- /dev/null
+++ b/scene/gui/menu_button.cpp
@@ -0,0 +1,153 @@
+/*************************************************************************/
+/* menu_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "menu_button.h"
+#include "os/keyboard.h"
+
+
+void MenuButton::_unhandled_key_input(InputEvent p_event) {
+
+ //check accelerators
+
+ if (p_event.type==InputEvent::KEY && p_event.key.pressed) {
+
+ if (!get_parent() || !is_visible() || is_disabled())
+ return;
+
+ uint32_t code=p_event.key.scancode;
+ if (code==0)
+ code=p_event.key.unicode;
+
+ if (p_event.key.mod.control)
+ code|=KEY_MASK_CTRL;
+ if (p_event.key.mod.alt)
+ code|=KEY_MASK_ALT;
+ if (p_event.key.mod.meta)
+ code|=KEY_MASK_META;
+ if (p_event.key.mod.shift)
+ code|=KEY_MASK_SHIFT;
+
+
+ int item = popup->find_item_by_accelerator(code);
+ if (item>=0 && ! popup->is_item_disabled(item))
+ popup->activate_item(item);
+ /*
+ for(int i=0;i<items.size();i++) {
+
+
+ if (items[i].accel==0)
+ continue;
+
+ if (items[i].accel==code) {
+
+ emit_signal("item_pressed",items[i].ID);
+ }
+ }*/
+ }
+
+}
+
+
+void MenuButton::pressed() {
+
+ emit_signal("about_to_show");
+ Size2 size=get_size();
+
+ Point2 gp = get_global_pos();
+ popup->set_global_pos( gp + Size2( 0, size.height ) );
+ popup->set_size( Size2( size.width, 0) );
+ popup->set_parent_rect( Rect2(Point2(gp-popup->get_global_pos()),get_size()));
+ popup->popup();
+ popup->call_deferred("grab_click_focus");
+
+}
+
+void MenuButton::_input_event(InputEvent p_event) {
+
+ /*if (p_event.type==InputEvent::MOUSE_BUTTON && p_event.mouse_button.button_index==BUTTON_LEFT) {
+ clicked=p_event.mouse_button.pressed;
+ }
+ if (clicked && p_event.type==InputEvent::MOUSE_MOTION && popup->is_visible()) {
+
+ Point2 gt = Point2(p_event.mouse_motion.x,p_event.mouse_motion.y);
+ gt = get_global_transform().xform(gt);
+ Point2 lt = popup->get_transform().affine_inverse().xform(gt);
+ if (popup->has_point(lt)) {
+ //print_line("HAS POINT!!!");
+ popup->call_deferred("grab_click_focus");
+ }
+
+ }*/
+
+ BaseButton::_input_event(p_event);
+}
+
+PopupMenu *MenuButton::get_popup() {
+
+ return popup;
+}
+
+Array MenuButton::_get_items() const {
+
+ return popup->get("items");
+}
+void MenuButton::_set_items(const Array& p_items) {
+
+ popup->set("items",p_items);
+}
+
+void MenuButton::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("get_popup"),&MenuButton::get_popup);
+ ObjectTypeDB::bind_method(_MD("_unhandled_key_input"),&MenuButton::_unhandled_key_input);
+ ObjectTypeDB::bind_method(_MD("_set_items"),&MenuButton::_set_items);
+ ObjectTypeDB::bind_method(_MD("_get_items"),&MenuButton::_get_items);
+
+ ADD_PROPERTY( PropertyInfo(Variant::ARRAY,"items",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR), _SCS("_set_items"),_SCS("_get_items") );
+
+ ADD_SIGNAL( MethodInfo("about_to_show") );
+}
+MenuButton::MenuButton() {
+
+
+ set_flat(true);
+ set_focus_mode(FOCUS_NONE);
+ popup = memnew( PopupMenu );
+ popup->hide();
+ add_child(popup);
+ popup->set_as_toplevel(true);
+ add_to_group("unhandled_key_input");
+ set_click_on_press(true);
+}
+
+
+MenuButton::~MenuButton() {
+
+}
+
+
diff --git a/scene/gui/menu_button.h b/scene/gui/menu_button.h
new file mode 100644
index 0000000000..52c9a9aea5
--- /dev/null
+++ b/scene/gui/menu_button.h
@@ -0,0 +1,61 @@
+/*************************************************************************/
+/* menu_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef MENU_BUTTON_H
+#define MENU_BUTTON_H
+
+#include "scene/gui/popup_menu.h"
+#include "scene/gui/button.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class MenuButton : public Button {
+
+ OBJ_TYPE( MenuButton, Button );
+
+ bool clicked;
+ PopupMenu *popup;
+ virtual void pressed();
+
+ void _unhandled_key_input(InputEvent p_event);
+ Array _get_items() const;
+ void _set_items(const Array& p_items);
+
+ void _input_event(InputEvent p_event);
+protected:
+
+
+ static void _bind_methods();
+public:
+
+ PopupMenu *get_popup();
+ MenuButton();
+ ~MenuButton();
+};
+
+#endif
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
new file mode 100644
index 0000000000..fa4a313115
--- /dev/null
+++ b/scene/gui/option_button.cpp
@@ -0,0 +1,337 @@
+/*************************************************************************/
+/* option_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "option_button.h"
+#include "print_string.h"
+
+
+Size2 OptionButton::get_minimum_size() const {
+
+
+ Size2 minsize = Button::get_minimum_size();
+
+ if (has_icon("arrow"))
+ minsize.width+=Control::get_icon("arrow")->get_width();
+
+ return minsize;
+}
+
+void OptionButton::_notification(int p_what) {
+
+
+ switch(p_what) {
+
+ case NOTIFICATION_DRAW: {
+
+ if (!has_icon("arrow"))
+ return;
+
+ RID ci = get_canvas_item();
+ Ref<Texture> arrow = Control::get_icon("arrow");
+ Ref<StyleBox> normal = get_stylebox("normal" );
+
+ Size2 size = get_size();
+
+ Point2 ofs( size.width - arrow->get_width() - get_constant("arrow_margin"), int(Math::abs((size.height-arrow->get_height())/2)));
+ arrow->draw(ci,ofs);
+
+ } break;
+ }
+}
+
+
+void OptionButton::_selected(int p_which) {
+
+ int selid = -1;
+ for (int i=0;i<popup->get_item_count();i++) {
+
+ bool is_clicked = popup->get_item_ID(i)==p_which;
+ if (is_clicked) {
+ selid=i;
+ break;
+ }
+ }
+
+ ERR_FAIL_COND(selid==-1);
+
+ _select(selid,true);
+}
+
+
+void OptionButton::pressed() {
+
+ Size2 size=get_size();
+ popup->set_global_pos( get_global_pos() + Size2( 0, size.height ) );
+ popup->set_size( Size2( size.width, 0) );
+
+ popup->popup();
+}
+
+void OptionButton::add_icon_item(const Ref<Texture>& p_icon,const String& p_label,int p_ID) {
+
+ popup->add_icon_check_item( p_icon, p_label, p_ID );
+ if (popup->get_item_count()==1)
+ select(0);
+}
+void OptionButton::add_item(const String& p_label,int p_ID) {
+
+ popup->add_check_item( p_label, p_ID );
+ if (popup->get_item_count()==1)
+ select(0);
+}
+
+void OptionButton::set_item_text(int p_idx,const String& p_text) {
+
+ popup->set_item_text(p_idx,p_text);
+
+}
+void OptionButton::set_item_icon(int p_idx,const Ref<Texture>& p_icon) {
+
+ popup->set_item_icon(p_idx,p_icon);
+
+}
+void OptionButton::set_item_ID(int p_idx,int p_ID) {
+
+ popup->set_item_ID(p_idx,p_ID);
+}
+
+void OptionButton::set_item_metadata(int p_idx,const Variant& p_metadata) {
+
+ popup->set_item_metadata(p_idx,p_metadata);
+}
+
+void OptionButton::set_item_disabled(int p_idx,bool p_disabled) {
+
+ popup->set_item_disabled(p_idx,p_disabled);
+}
+
+String OptionButton::get_item_text(int p_idx) const {
+
+ return popup->get_item_text(p_idx);
+}
+
+Ref<Texture> OptionButton::get_item_icon(int p_idx) const {
+
+ return popup->get_item_icon(p_idx);
+}
+
+int OptionButton::get_item_ID(int p_idx) const {
+
+ return popup->get_item_ID(p_idx);
+}
+Variant OptionButton::get_item_metadata(int p_idx) const {
+
+ return popup->get_item_metadata(p_idx);
+}
+
+bool OptionButton::is_item_disabled(int p_idx) const {
+
+ return popup->is_item_disabled(p_idx);
+}
+
+
+int OptionButton::get_item_count() const {
+
+ return popup->get_item_count();
+}
+
+void OptionButton::add_separator() {
+
+ popup->add_separator();
+}
+
+void OptionButton::clear() {
+
+ popup->clear();
+ set_text("");
+ current=-1;
+}
+
+void OptionButton::_select(int p_idx,bool p_emit) {
+
+ if (p_idx<0)
+ return;
+ if (p_idx==current)
+ return;
+
+ ERR_FAIL_INDEX( p_idx, popup->get_item_count() );
+
+ for (int i=0;i<popup->get_item_count();i++) {
+
+ popup->set_item_checked(i,i==p_idx);
+ }
+
+
+
+ current=p_idx;
+ set_text( popup->get_item_text( current ) );
+ set_icon( popup->get_item_icon( current ) );
+
+ if (is_inside_scene() && p_emit)
+ emit_signal("item_selected",current);
+}
+
+void OptionButton::_select_int(int p_which) {
+
+ if (p_which<0 || p_which>=popup->get_item_count())
+ return;
+ _select(p_which,false);
+
+}
+
+void OptionButton::select(int p_idx) {
+
+ _select(p_idx,false);
+}
+
+int OptionButton::get_selected() const {
+
+ return current;
+}
+
+int OptionButton::get_selected_ID() const {
+
+ int idx = get_selected();
+ if (idx<0)
+ return 0;
+ return get_item_ID(current);
+}
+Variant OptionButton::get_selected_metadata() const {
+
+ int idx = get_selected();
+ if (idx<0)
+ return Variant();
+ return get_item_metadata(current);
+
+}
+
+void OptionButton::remove_item(int p_idx) {
+
+ popup->remove_item(p_idx);
+}
+
+Array OptionButton::_get_items() const {
+
+ Array items;
+ for(int i=0;i<get_item_count();i++) {
+
+ items.push_back(get_item_text(i));
+ items.push_back(get_item_icon(i));
+ items.push_back(is_item_disabled(i));
+ items.push_back(get_item_ID(i));
+ items.push_back(get_item_metadata(i));
+ }
+
+ return items;
+
+}
+void OptionButton::_set_items(const Array& p_items){
+
+ ERR_FAIL_COND(p_items.size() % 5);
+ clear();
+
+ for(int i=0;i<p_items.size();i+=5) {
+
+ String text=p_items[i+0];
+ Ref<Texture> icon=p_items[i+1];
+ bool disabled=p_items[i+2];
+ int id=p_items[i+3];
+ Variant meta = p_items[i+4];
+
+ int idx=get_item_count();
+ add_item(text,id);
+ set_item_icon(idx,icon);
+ set_item_disabled(idx,disabled);
+ set_item_metadata(idx,meta);
+ }
+
+
+}
+
+
+void OptionButton::get_translatable_strings(List<String> *p_strings) const {
+
+ return popup->get_translatable_strings(p_strings);
+}
+
+
+void OptionButton::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_selected"),&OptionButton::_selected);
+
+ ObjectTypeDB::bind_method(_MD("add_item","label","id"),&OptionButton::add_item,DEFVAL(-1));
+ ObjectTypeDB::bind_method(_MD("add_icon_item","texture:Texture","label","id"),&OptionButton::add_icon_item);
+ ObjectTypeDB::bind_method(_MD("set_item_text","idx","text"),&OptionButton::set_item_text);
+ ObjectTypeDB::bind_method(_MD("set_item_icon","idx","texture:Texture"),&OptionButton::set_item_icon);
+ ObjectTypeDB::bind_method(_MD("set_item_disabled","idx","disabled"),&OptionButton::set_item_disabled);
+ ObjectTypeDB::bind_method(_MD("set_item_ID","idx","id"),&OptionButton::set_item_ID);
+ ObjectTypeDB::bind_method(_MD("set_item_metadata","idx","metadata"),&OptionButton::set_item_metadata);
+ ObjectTypeDB::bind_method(_MD("get_item_text","idx"),&OptionButton::get_item_text);
+ ObjectTypeDB::bind_method(_MD("get_item_icon:Texture","idx"),&OptionButton::get_item_icon);
+ ObjectTypeDB::bind_method(_MD("get_item_ID","idx"),&OptionButton::get_item_ID);
+ ObjectTypeDB::bind_method(_MD("get_item_metadata","idx"),&OptionButton::get_item_metadata);
+ ObjectTypeDB::bind_method(_MD("is_item_disabled","idx"),&OptionButton::is_item_disabled);
+ ObjectTypeDB::bind_method(_MD("get_item_count"),&OptionButton::get_item_count);
+ ObjectTypeDB::bind_method(_MD("add_separator"),&OptionButton::add_separator);
+ ObjectTypeDB::bind_method(_MD("clear"),&OptionButton::clear);
+ ObjectTypeDB::bind_method(_MD("select"),&OptionButton::select);
+ ObjectTypeDB::bind_method(_MD("get_selected"),&OptionButton::get_selected);
+ ObjectTypeDB::bind_method(_MD("get_selected_ID"),&OptionButton::get_selected_ID);
+ ObjectTypeDB::bind_method(_MD("get_selected_metadata"),&OptionButton::get_selected_metadata);
+ ObjectTypeDB::bind_method(_MD("remove_item","idx"),&OptionButton::remove_item);
+ ObjectTypeDB::bind_method(_MD("_select_int"),&OptionButton::_select_int);
+
+ ObjectTypeDB::bind_method(_MD("_set_items"),&OptionButton::_set_items);
+ ObjectTypeDB::bind_method(_MD("_get_items"),&OptionButton::_get_items);
+
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"selected"), _SCS("_select_int"),_SCS("get_selected") );
+ ADD_PROPERTY( PropertyInfo(Variant::ARRAY,"items",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR), _SCS("_set_items"),_SCS("_get_items") );
+ ADD_SIGNAL( MethodInfo("item_selected", PropertyInfo( Variant::INT,"ID") ) );
+}
+
+OptionButton::OptionButton() {
+
+
+ popup = memnew( PopupMenu );
+ popup->hide();
+ popup->set_as_toplevel(true);
+ add_child(popup);
+ popup->connect("item_pressed", this,"_selected");
+
+ current=-1;
+ set_text_align(ALIGN_LEFT);
+}
+
+
+OptionButton::~OptionButton() {
+
+
+
+}
+
+
diff --git a/scene/gui/option_button.h b/scene/gui/option_button.h
new file mode 100644
index 0000000000..295127175d
--- /dev/null
+++ b/scene/gui/option_button.h
@@ -0,0 +1,95 @@
+/*************************************************************************/
+/* option_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef OPTION_BUTTON_H
+#define OPTION_BUTTON_H
+
+#include "scene/gui/button.h"
+#include "scene/gui/popup_menu.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class OptionButton : public Button {
+
+ OBJ_TYPE( OptionButton, Button );
+
+ PopupMenu *popup;
+ int current;
+
+ void _selected(int p_which);
+ void _select(int p_which,bool p_emit=false);
+ void _select_int(int p_which);
+
+ Array _get_items() const;
+ void _set_items(const Array& p_items);
+
+ virtual void pressed();
+protected:
+
+ Size2 get_minimum_size() const;
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+
+ void add_icon_item(const Ref<Texture>& p_icon,const String& p_label,int p_ID=-1);
+ void add_item(const String& p_label,int p_ID=-1);
+
+ void set_item_text(int p_idx,const String& p_text);
+ void set_item_icon(int p_idx,const Ref<Texture>& p_icon);
+ void set_item_ID(int p_idx,int p_ID);
+ void set_item_metadata(int p_idx,const Variant& p_metadata);
+ void set_item_disabled(int p_idx,bool p_disabled);
+
+ String get_item_text(int p_idx) const;
+ Ref<Texture> get_item_icon(int p_idx) const;
+ int get_item_ID(int p_idx) const;
+ Variant get_item_metadata(int p_idx) const;
+ bool is_item_disabled(int p_idx) const;
+
+
+ int get_item_count() const;
+
+ void add_separator();
+
+ void clear();
+
+ void select(int p_idx);
+ int get_selected() const;
+ int get_selected_ID() const;
+ Variant get_selected_metadata() const;
+
+ void remove_item(int p_idx);
+
+ virtual void get_translatable_strings(List<String> *p_strings) const;
+
+ OptionButton();
+ ~OptionButton();
+
+};
+
+#endif
diff --git a/scene/gui/panel.cpp b/scene/gui/panel.cpp
new file mode 100644
index 0000000000..fd1d5d2de6
--- /dev/null
+++ b/scene/gui/panel.cpp
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* panel.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "panel.h"
+#include "print_string.h"
+
+void Panel::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+ Ref<StyleBox> style = get_stylebox("panel");
+ style->draw( ci, Rect2( Point2(), get_size() ) );
+ }
+}
+
+Panel::Panel() {
+
+ set_stop_mouse(true);
+}
+
+
+Panel::~Panel()
+{
+}
+
+
diff --git a/scene/gui/panel.h b/scene/gui/panel.h
new file mode 100644
index 0000000000..8dab05f1a6
--- /dev/null
+++ b/scene/gui/panel.h
@@ -0,0 +1,48 @@
+/*************************************************************************/
+/* panel.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef PANEL_H
+#define PANEL_H
+
+#include "scene/gui/control.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class Panel : public Control{
+
+ OBJ_TYPE(Panel,Control);
+protected:
+
+ void _notification(int p_what);
+public:
+ Panel();
+ ~Panel();
+
+};
+
+#endif
diff --git a/scene/gui/panel_container.cpp b/scene/gui/panel_container.cpp
new file mode 100644
index 0000000000..91581ecccd
--- /dev/null
+++ b/scene/gui/panel_container.cpp
@@ -0,0 +1,109 @@
+/*************************************************************************/
+/* panel_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "panel_container.h"
+
+
+Size2 PanelContainer::get_minimum_size() const {
+
+ Ref<StyleBox> style=get_stylebox("panel");
+
+
+ Size2 ms;
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ Size2 minsize = c->get_combined_minimum_size();
+ ms.width = MAX(ms.width , minsize.width);
+ ms.height = MAX(ms.height , minsize.height);
+
+
+ }
+
+ if (style.is_valid())
+ ms+=style->get_minimum_size();
+ return ms;
+
+}
+
+void PanelContainer::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+ Ref<StyleBox> style;
+
+ if (has_stylebox("panel"))
+ style=get_stylebox("panel");
+ else
+ style=get_stylebox("panel","PanelContainer");
+
+ style->draw( ci, Rect2( Point2(), get_size() ) );
+
+ }
+
+ if (p_what==NOTIFICATION_SORT_CHILDREN) {
+
+ Ref<StyleBox> style;
+
+ if (has_stylebox("panel"))
+ style=get_stylebox("panel");
+ else
+ style=get_stylebox("panel","PanelContainer");
+
+ Size2 size = get_size();
+ Point2 ofs;
+ if (style.is_valid()) {
+ size-=style->get_minimum_size();
+ ofs+=style->get_offset();
+ }
+
+
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ fit_child_in_rect(c,Rect2(ofs,size));
+
+ }
+ }
+}
+
+PanelContainer::PanelContainer()
+{
+}
diff --git a/scene/gui/panel_container.h b/scene/gui/panel_container.h
new file mode 100644
index 0000000000..ac67b52be8
--- /dev/null
+++ b/scene/gui/panel_container.h
@@ -0,0 +1,48 @@
+/*************************************************************************/
+/* panel_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef PANEL_CONTAINER_H
+#define PANEL_CONTAINER_H
+
+#include "scene/gui/container.h"
+
+class PanelContainer : public Container {
+
+ OBJ_TYPE( PanelContainer, Container );
+
+protected:
+
+ void _notification(int p_what);
+public:
+
+ virtual Size2 get_minimum_size() const;
+
+ PanelContainer();
+};
+
+#endif // PANEL_CONTAINER_H
diff --git a/scene/gui/popup.cpp b/scene/gui/popup.cpp
new file mode 100644
index 0000000000..0a5b72d2ed
--- /dev/null
+++ b/scene/gui/popup.cpp
@@ -0,0 +1,236 @@
+/*************************************************************************/
+/* popup.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "popup.h"
+#include "os/keyboard.h"
+
+
+
+void Popup::_input_event(InputEvent p_event) {
+
+
+}
+
+void Popup::_notification(int p_what) {
+
+
+}
+
+void Popup::_fix_size() {
+
+ Control *window = get_window();
+ ERR_FAIL_COND(!window);
+
+ Point2 pos = get_pos();
+ Size2 size = get_size();
+ Point2 window_size = window==this ? get_parent_area_size() :window->get_size();
+
+ if (pos.x+size.width > window_size.width)
+ pos.x=window_size.width-size.width;
+ if (pos.x<0)
+ pos.x=0;
+
+ if (pos.y+size.height > window_size.height)
+ pos.y=window_size.height-size.height;
+ if (pos.y<0)
+ pos.y=0;
+ if (pos!=get_pos())
+ set_pos(pos);
+
+}
+
+
+void Popup::popup_centered_minsize(const Size2& p_minsize) {
+
+
+ Size2 total_minsize=p_minsize;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+
+ Size2 minsize = c->get_combined_minimum_size();
+
+ for(int j=0;j<2;j++) {
+
+ Margin m_beg = Margin(0+j);
+ Margin m_end = Margin(2+j);
+
+ float margin_begin = c->get_margin(m_beg);
+ float margin_end = c->get_margin(m_end);
+ AnchorType anchor_begin = c->get_anchor(m_beg);
+ AnchorType anchor_end = c->get_anchor(m_end);
+
+ if (anchor_begin == ANCHOR_BEGIN)
+ minsize[j]+=margin_begin;
+ if (anchor_end == ANCHOR_END)
+ minsize[j]+=margin_end;
+
+ }
+
+ total_minsize.width = MAX( total_minsize.width, minsize.width );
+ total_minsize.height = MAX( total_minsize.height, minsize.height );
+ }
+
+
+ popup_centered( total_minsize );
+
+}
+
+void Popup::popup_centered(const Size2& p_size) {
+
+ Control *window = get_window();
+ ERR_FAIL_COND(!window);
+
+
+ emit_signal("about_to_show");
+ Rect2 rect;
+ rect.size = p_size==Size2()?get_size():p_size;
+ Point2 window_size = window==this ? get_parent_area_size() :window->get_size();
+ rect.pos = ((window_size-rect.size)/2.0).floor();
+ set_pos( rect.pos );
+ set_size( rect.size );
+
+ show_modal(exclusive);
+ _fix_size();
+
+ Control *focusable = find_next_valid_focus();
+ if (focusable)
+ focusable->grab_focus();
+
+ _post_popup();
+ notification(NOTIFICATION_POST_POPUP);
+}
+
+void Popup::popup_centered_ratio(float p_screen_ratio) {
+
+
+ Control *window = get_window();
+ ERR_FAIL_COND(!window);
+
+ emit_signal("about_to_show");
+
+ Rect2 rect;
+ Point2 window_size = window==this ? get_parent_area_size() :window->get_size();
+ rect.size = (window_size * p_screen_ratio).floor();
+ rect.pos = ((window_size-rect.size)/2.0).floor();
+ set_pos( rect.pos );
+ set_size( rect.size );
+
+ show_modal(exclusive);
+ _fix_size();
+
+ Control *focusable = find_next_valid_focus();
+ if (focusable)
+ focusable->grab_focus();
+
+ _post_popup();
+ notification(NOTIFICATION_POST_POPUP);
+
+}
+
+void Popup::popup() {
+
+ emit_signal("about_to_show");
+ show_modal(exclusive);
+
+
+ _fix_size();
+
+ Control *focusable = find_next_valid_focus();
+
+ if (focusable)
+ focusable->grab_focus();
+
+ _post_popup();
+ notification(NOTIFICATION_POST_POPUP);
+}
+
+void Popup::set_exclusive(bool p_exclusive) {
+
+ exclusive=p_exclusive;
+}
+
+bool Popup::is_exclusive() const {
+
+ return exclusive;
+}
+
+
+void Popup::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("popup_centered","size"),&Popup::popup_centered,DEFVAL(Size2()));
+ ObjectTypeDB::bind_method(_MD("popup_centered_ratio","ratio"),&Popup::popup_centered_ratio,DEFVAL(0.75));
+ ObjectTypeDB::bind_method(_MD("popup_centered_minsize","minsize"),&Popup::popup_centered_minsize,DEFVAL(Size2()));
+ ObjectTypeDB::bind_method(_MD("popup"),&Popup::popup);
+ ObjectTypeDB::bind_method(_MD("set_exclusive","enable"),&Popup::set_exclusive);
+ ObjectTypeDB::bind_method(_MD("is_exclusive"),&Popup::is_exclusive);
+ ADD_SIGNAL( MethodInfo("about_to_show") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "popup/exclusive"), _SCS("set_exclusive"),_SCS("is_exclusive") );
+ BIND_CONSTANT(NOTIFICATION_POST_POPUP);
+
+}
+
+Popup::Popup() {
+
+ set_as_toplevel(true);
+ exclusive=false;
+ hide();
+}
+
+
+Popup::~Popup()
+{
+
+}
+
+void PopupPanel::set_child_rect(Control *p_child) {
+ ERR_FAIL_NULL(p_child);
+
+ Ref<StyleBox> p = get_stylebox("panel");
+ p_child->set_area_as_parent_rect();
+ for(int i=0;i<4;i++) {
+ p_child->set_margin(Margin(i),p->get_margin(Margin(i)));
+ }
+}
+
+void PopupPanel::_notification(int p_what) {
+
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ get_stylebox("panel")->draw(get_canvas_item(),Rect2(Point2(),get_size()));
+ }
+}
+
+PopupPanel::PopupPanel() {
+
+
+}
diff --git a/scene/gui/popup.h b/scene/gui/popup.h
new file mode 100644
index 0000000000..3744ff283f
--- /dev/null
+++ b/scene/gui/popup.h
@@ -0,0 +1,87 @@
+/*************************************************************************/
+/* popup.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef POPUP_H
+#define POPUP_H
+
+#include "scene/gui/control.h"
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class Popup : public Control {
+
+ OBJ_TYPE( Popup, Control );
+
+ bool exclusive;
+
+protected:
+
+ virtual void _post_popup() {}
+
+ void _input_event(InputEvent p_event);
+ void _notification(int p_what);
+ void _fix_size();
+ static void _bind_methods();
+public:
+
+ enum {
+ NOTIFICATION_POST_POPUP=80
+ };
+
+ void set_exclusive(bool p_exclusive);
+ bool is_exclusive() const;
+
+ void popup_centered_ratio(float p_screen_ratio=0.75);
+ void popup_centered(const Size2& p_size=Size2());
+ void popup_centered_minsize(const Size2& p_minsize=Size2());
+ virtual void popup();
+
+
+ Popup();
+ ~Popup();
+
+};
+
+class PopupPanel : public Popup {
+
+ OBJ_TYPE(PopupPanel,Popup);
+
+
+protected:
+
+ void _notification(int p_what);
+public:
+
+ void set_child_rect(Control *p_child);
+ PopupPanel();
+
+};
+
+
+#endif
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
new file mode 100644
index 0000000000..c2dc1318c9
--- /dev/null
+++ b/scene/gui/popup_menu.cpp
@@ -0,0 +1,916 @@
+/*************************************************************************/
+/* popup_menu.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "popup_menu.h"
+#include "print_string.h"
+#include "os/keyboard.h"
+#include "translation.h"
+
+String PopupMenu::_get_accel_text(uint32_t p_accel) const {
+
+ return keycode_get_string(p_accel);
+ /*
+ String atxt;
+ if (p_accel&KEY_MASK_SHIFT)
+ atxt+="Shift+";
+ if (p_accel&KEY_MASK_ALT)
+ atxt+="Alt+";
+ if (p_accel&KEY_MASK_CTRL)
+ atxt+="Ctrl+";
+ if (p_accel&KEY_MASK_META)
+ atxt+="Meta+";
+
+ p_accel&=KEY_CODE_MASK;
+
+ atxt+=String::chr(p_accel).to_upper();
+
+ return atxt;
+*/
+}
+
+
+Size2 PopupMenu::get_minimum_size() const {
+
+ int vseparation = get_constant("vseparation");
+ int hseparation = get_constant("hseparation");
+
+ Size2 minsize = get_stylebox("panel")->get_minimum_size();
+ Ref<Font> font = get_font("font");
+
+ float max_w=0;
+ int font_h = font->get_height();
+ int check_w = get_icon("checked")->get_width();
+ int accel_max_w=0;
+
+ for (int i=0;i<items.size();i++) {
+
+ Size2 size;
+ if (!items[i].icon.is_null()) {
+
+ Size2 icon_size = items[i].icon->get_size();
+ size.height = MAX( icon_size.height, font_h );
+ size.width+=icon_size.width;
+ size.width+=hseparation;
+ } else {
+
+ size.height=font_h;
+ }
+
+ if (items[i].checkable) {
+
+ size.width+=check_w+hseparation;
+ }
+
+ size.width+=font->get_string_size(items[i].text).width;
+ if (i>0)
+ size.height+=vseparation;
+
+ if (items[i].accel) {
+
+ int accel_w = hseparation*2;
+ accel_w+=font->get_string_size(_get_accel_text(items[i].accel)).width;
+ accel_max_w = MAX( accel_w, accel_max_w );
+ }
+
+ minsize.height+=size.height;
+ max_w = MAX( max_w, size.width );
+
+ }
+
+ minsize.width+=max_w+accel_max_w;
+
+ return minsize;
+}
+
+int PopupMenu::_get_mouse_over(const Point2& p_over) const {
+
+
+ if (p_over.x<0 || p_over.x>=get_size().width)
+ return -1;
+
+ Ref<StyleBox> style = get_stylebox("panel");
+
+ Point2 ofs=style->get_offset();
+
+
+ if (ofs.y>p_over.y)
+ return -1;
+
+
+ Ref<Font> font = get_font("font");
+ int vseparation = get_constant("vseparation");
+// int hseparation = get_constant("hseparation");
+ float font_h=font->get_height();
+
+
+ for (int i=0;i<items.size();i++) {
+
+ if (i>0)
+ ofs.y+=vseparation;
+ float h;
+
+ if (!items[i].icon.is_null()) {
+
+ Size2 icon_size = items[i].icon->get_size();
+ h = MAX( icon_size.height, font_h );
+ } else {
+
+ h=font_h;
+ }
+
+ ofs.y+=h;
+ if (p_over.y < ofs.y) {
+ return i;
+ }
+ }
+
+ return -1;
+
+}
+
+
+void PopupMenu::_activate_submenu(int over) {
+
+ Node* n = get_node(items[over].submenu);
+ ERR_EXPLAIN("item subnode does not exist: "+items[over].submenu);
+ ERR_FAIL_COND(!n);
+ Popup *pm = n->cast_to<Popup>();
+ ERR_EXPLAIN("item subnode is not a Popup: "+items[over].submenu);
+ ERR_FAIL_COND(!pm);
+ if (pm->is_visible())
+ return; //already visible!
+
+
+ Point2 p = get_global_pos();
+ Rect2 pr(p,get_size());
+ Ref<StyleBox> style = get_stylebox("panel");
+ pm->set_pos(p+Point2(get_size().width,items[over]._ofs_cache-style->get_offset().y));
+ pm->popup();
+
+ PopupMenu *pum = pm->cast_to<PopupMenu>();
+ if (pum) {
+
+ pr.pos-=pum->get_global_pos();
+ pum->clear_autohide_areas();
+ pum->add_autohide_area(Rect2(pr.pos.x,pr.pos.y,pr.size.x,items[over]._ofs_cache));
+ if (over<items.size()-1) {
+ int from = items[over+1]._ofs_cache;
+ pum->add_autohide_area(Rect2(pr.pos.x,pr.pos.y+from,pr.size.x,pr.size.y-from));
+ }
+
+ }
+
+}
+
+void PopupMenu::_submenu_timeout() {
+
+ if (mouse_over==submenu_over) {
+ _activate_submenu(mouse_over);
+ submenu_over=-1;
+ }
+}
+
+
+void PopupMenu::_input_event(const InputEvent &p_event) {
+
+ switch( p_event.type) {
+
+ case InputEvent::KEY: {
+
+
+ if (!p_event.key.pressed)
+ break;
+
+ switch(p_event.key.scancode) {
+
+
+ case KEY_DOWN: {
+
+
+ for(int i=mouse_over+1;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: {
+
+
+ for(int i=mouse_over-1;i>=0;i--) {
+
+ if (i<0 || i>=items.size())
+ continue;
+
+
+ if (!items[i].separator && !items[i].disabled) {
+
+
+ mouse_over=i;
+ update();
+ break;
+ }
+ }
+ } break;
+ case KEY_RETURN:
+ case KEY_ENTER: {
+
+ if (mouse_over>=0 && mouse_over<items.size() && !items[mouse_over].separator) {
+
+
+ activate_item(mouse_over);
+
+ }
+ } break;
+ }
+
+
+
+
+
+ } break;
+
+ case InputEvent::MOUSE_BUTTON: {
+
+
+ const InputEventMouseButton &b=p_event.mouse_button;
+ if (b.pressed)
+ break;
+
+ switch(b.button_index) {
+
+
+ case BUTTON_WHEEL_DOWN: {
+
+ if (get_global_pos().y + get_size().y > get_viewport_rect().size.y) {
+
+ int vseparation = get_constant("vseparation");
+ Ref<Font> font = get_font("font");
+
+ Point2 pos = get_pos();
+ int s = (vseparation+font->get_height())*3;
+ pos.y-=s;
+ set_pos(pos);
+
+ //update hover
+ InputEvent ie;
+ ie.type=InputEvent::MOUSE_MOTION;
+ ie.mouse_motion.x=b.x;
+ ie.mouse_motion.y=b.y+s;
+ _input_event(ie);
+ }
+ } break;
+ case BUTTON_WHEEL_UP: {
+
+ if (get_global_pos().y < 0) {
+
+ int vseparation = get_constant("vseparation");
+ Ref<Font> font = get_font("font");
+
+ Point2 pos = get_pos();
+ int s = (vseparation+font->get_height())*3;
+ pos.y+=s;
+ set_pos(pos);
+
+ //update hover
+ InputEvent ie;
+ ie.type=InputEvent::MOUSE_MOTION;
+ ie.mouse_motion.x=b.x;
+ ie.mouse_motion.y=b.y-s;
+ _input_event(ie);
+
+
+ }
+ } break;
+ case BUTTON_LEFT: {
+
+ int over=_get_mouse_over(Point2(b.x,b.y));
+
+ if (over<0 || items[over].separator || items[over].disabled)
+ break; //non-activable
+
+ if (items[over].submenu!="") {
+
+ _activate_submenu(over);
+ return;
+ }
+ activate_item(over);
+
+ } break;
+ }
+
+ //update();
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+
+ const InputEventMouseMotion &m=p_event.mouse_motion;
+ for(List<Rect2>::Element *E=autohide_areas.front();E;E=E->next()) {
+
+ if (!Rect2(Point2(),get_size()).has_point(Point2(m.x,m.y)) && E->get().has_point(Point2(m.x,m.y))) {
+ call_deferred("hide");
+ return;
+ }
+ }
+
+ int over=_get_mouse_over(Point2(m.x,m.y));
+ int id = (over<0 || items[over].separator || items[over].disabled)?-1:items[over].ID;
+
+ if (id<0)
+ break;
+
+ if (items[over].submenu!="" && submenu_over!=over) {
+ submenu_over=over;
+ submenu_timer->start();
+ }
+
+ if (over!=mouse_over) {
+ mouse_over=over;
+ update();
+ }
+ } break;
+
+ }
+}
+
+
+bool PopupMenu::has_point(const Point2& p_point) const {
+
+ if (parent_rect.has_point(p_point))
+ return true;
+ for(const List<Rect2>::Element *E=autohide_areas.front();E;E=E->next()) {
+
+ if (E->get().has_point(p_point))
+ return true;
+ }
+
+ return Control::has_point(p_point);
+}
+
+void PopupMenu::_notification(int p_what) {
+
+ switch(p_what) {
+
+
+ case NOTIFICATION_DRAW: {
+
+ RID ci = get_canvas_item();
+ Size2 size=get_size();
+
+ 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");
+ Ref<Texture> submenu= get_icon("submenu");
+ Ref<StyleBox> separator = get_stylebox("separator");
+
+ style->draw( ci, Rect2( Point2(), get_size() ) );
+ Point2 ofs=style->get_offset();
+ int vseparation = get_constant("vseparation");
+ int hseparation = get_constant("hseparation");
+ Color font_color = get_color("font_color");
+ Color font_color_disabled = get_color("font_color_disabled");
+ Color font_color_accel = get_color("font_color_accel");
+ Color font_color_hover = get_color("font_color_hover");
+ float font_h=font->get_height();
+
+ for (int i=0;i<items.size();i++) {
+
+ if (i>0)
+ ofs.y+=vseparation;
+ Point2 item_ofs=ofs;
+ float h;
+ Size2 icon_size;
+
+ if (!items[i].icon.is_null()) {
+
+ icon_size = items[i].icon->get_size();
+ h = MAX( icon_size.height, font_h );
+ } else {
+
+ h=font_h;
+ }
+
+ if (i==mouse_over) {
+
+ hover->draw(ci, Rect2( ofs+Point2(-hseparation,-vseparation), Size2( get_size().width - style->get_minimum_size().width + hseparation*2, h+vseparation*2 ) ));
+ }
+
+ if (items[i].separator) {
+
+ int sep_h=separator->get_center_size().height+separator->get_minimum_size().height;
+ separator->draw(ci, Rect2( 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].icon.is_null()) {
+ items[i].icon->draw( ci, item_ofs+Point2(0,Math::floor((h-icon_size.height)/2.0)));
+ item_ofs.x+=items[i].icon->get_width();
+ item_ofs.x+=hseparation;
+ }
+
+ if (items[i].submenu!="") {
+ submenu->draw( ci, Point2(size.width - style->get_margin(MARGIN_RIGHT) - submenu->get_width(),item_ofs.y+Math::floor(h-submenu->get_height())/2));
+ }
+
+ item_ofs.y+=font->get_ascent();
+ if (!items[i].separator)
+ font->draw(ci,item_ofs+Point2(0,Math::floor((h-font_h)/2.0)),items[i].text,items[i].disabled?font_color_disabled:(i==mouse_over?font_color_hover:font_color));
+
+
+ if (items[i].accel) {
+ //accelerator
+ String text = _get_accel_text(items[i].accel);
+ item_ofs.x=size.width-style->get_margin(MARGIN_RIGHT)-font->get_string_size(text).width;
+ font->draw(ci,item_ofs+Point2(0,Math::floor((h-font_h)/2.0)),text,i==mouse_over?font_color_hover:font_color_accel);
+
+ }
+
+ items[i]._ofs_cache=ofs.y;
+
+ ofs.y+=h;
+
+ }
+
+ } break;
+ case NOTIFICATION_MOUSE_ENTER: {
+
+ grab_focus();
+ } break;
+ case NOTIFICATION_MOUSE_EXIT: {
+
+ if (mouse_over>=0) {
+ mouse_over=-1;
+ update();
+ }
+ } break;
+ }
+}
+
+
+void PopupMenu::add_icon_item(const Ref<Texture>& p_icon,const String& p_label,int p_ID,uint32_t p_accel) {
+
+ Item item;
+ item.icon=p_icon;
+ item.text=p_label;
+ item.accel=p_accel;
+ item.ID=(p_ID<0)?idcount++:p_ID;
+ items.push_back(item);
+ update();
+}
+void PopupMenu::add_item(const String& p_label,int p_ID,uint32_t p_accel) {
+
+ Item item;
+ item.text=XL_MESSAGE(p_label);
+ item.accel=p_accel;
+ item.ID=(p_ID<0)?idcount++:p_ID;
+ items.push_back(item);
+ update();
+}
+
+void PopupMenu::add_submenu_item(const String& p_label, const String& p_submenu,int p_ID){
+
+ Item item;
+ item.text=XL_MESSAGE(p_label);
+ item.ID=(p_ID<0)?idcount++:p_ID;
+ item.submenu=p_submenu;
+ items.push_back(item);
+ update();
+}
+
+void PopupMenu::add_icon_check_item(const Ref<Texture>& p_icon,const String& p_label,int p_ID,uint32_t p_accel) {
+
+ Item item;
+ item.icon=p_icon;
+ item.text=XL_MESSAGE(p_label);
+ item.accel=p_accel;
+ item.ID=(p_ID<0)?idcount++:p_ID;
+ item.checkable=true;
+ items.push_back(item);
+ update();
+}
+void PopupMenu::add_check_item(const String& p_label,int p_ID,uint32_t p_accel) {
+
+ Item item;
+ item.text=XL_MESSAGE(p_label);
+ item.accel=p_accel;
+ item.ID=(p_ID<0)?idcount++:p_ID;
+ item.checkable=true;
+ items.push_back(item);
+ update();
+}
+
+void PopupMenu::set_item_text(int p_idx,const String& p_text) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].text=XL_MESSAGE(p_text);
+
+ update();
+
+}
+void PopupMenu::set_item_icon(int p_idx,const Ref<Texture>& p_icon) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].icon=p_icon;
+
+ update();
+
+}
+void PopupMenu::set_item_checked(int p_idx,bool p_checked) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+
+ items[p_idx].checked=p_checked;
+
+ update();
+}
+void PopupMenu::set_item_ID(int p_idx,int p_ID) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].ID=p_ID;
+
+ update();
+}
+
+void PopupMenu::set_item_accelerator(int p_idx,uint32_t p_accel) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].accel=p_accel;
+
+ update();
+
+}
+
+
+void PopupMenu::set_item_metadata(int p_idx,const Variant& p_meta) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].metadata=p_meta;
+ update();
+}
+
+void PopupMenu::set_item_disabled(int p_idx,bool p_disabled) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].disabled=p_disabled;
+ update();
+}
+
+void PopupMenu::set_item_submenu(int p_idx, const String& p_submenu) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].submenu=p_submenu;
+ update();
+}
+
+String PopupMenu::get_item_text(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),"");
+ return items[p_idx].text;
+
+}
+Ref<Texture> PopupMenu::get_item_icon(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),Ref<Texture>());
+ return items[p_idx].icon;
+}
+
+
+uint32_t PopupMenu::get_item_accelerator(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),0);
+ return items[p_idx].accel;
+
+}
+
+Variant PopupMenu::get_item_metadata(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),Variant());
+ return items[p_idx].metadata;
+
+}
+
+bool PopupMenu::is_item_disabled(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),false);
+ return items[p_idx].disabled;
+}
+
+bool PopupMenu::is_item_checked(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),false);
+ return items[p_idx].checked;
+}
+
+int PopupMenu::get_item_ID(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),0);
+ return items[p_idx].ID;
+}
+
+int PopupMenu::get_item_index(int p_ID) const {
+
+ for(int i=0;i<items.size();i++) {
+
+ if (items[i].ID==p_ID)
+ return i;
+ }
+
+ return -1;
+}
+
+String PopupMenu::get_item_submenu(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),"");
+ return items[p_idx].submenu;
+}
+
+String PopupMenu::get_item_tooltip(int p_idx) const {
+
+ ERR_FAIL_INDEX_V(p_idx,items.size(),"");
+ return items[p_idx].tooltip;
+}
+
+void PopupMenu::set_item_as_separator(int p_idx, bool p_separator) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].separator=p_separator;
+ update();
+
+}
+
+bool PopupMenu::is_item_separator(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx,items.size(),false);
+ return items[p_idx].separator;
+}
+
+
+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;
+ update();
+
+}
+
+void PopupMenu::set_item_tooltip(int p_idx,const String& p_tooltip) {
+
+ ERR_FAIL_INDEX(p_idx,items.size());
+ items[p_idx].tooltip=p_tooltip;
+ update();
+}
+
+bool PopupMenu::is_item_checkable(int p_idx) const {
+ ERR_FAIL_INDEX_V(p_idx,items.size(),false);
+ return items[p_idx].checkable;
+}
+
+int PopupMenu::get_item_count() const {
+
+ return items.size();
+}
+
+int PopupMenu::find_item_by_accelerator(uint32_t p_accel) const {
+
+ int il=items.size();
+ for(int i=0;i<il;i++) {
+
+ if (items[i].accel==p_accel)
+ return i;
+ }
+ return -1;
+}
+
+void PopupMenu::activate_item(int p_item) {
+
+
+ ERR_FAIL_INDEX(p_item,items.size());
+ ERR_FAIL_COND(items[p_item].separator);
+ emit_signal("item_pressed",items[p_item].ID);
+ hide();
+
+}
+
+void PopupMenu::remove_item(int p_idx) {
+
+ items.remove(p_idx);
+ update();
+}
+
+void PopupMenu::add_separator() {
+
+ Item sep;
+ sep.separator=true;
+ sep.ID=-1;
+ items.push_back(sep);
+ update();
+}
+
+void PopupMenu::clear() {
+
+ items.clear();
+ update();
+ idcount=0;
+
+}
+
+Array PopupMenu::_get_items() const {
+
+ Array items;
+ for(int i=0;i<get_item_count();i++) {
+
+ items.push_back(get_item_text(i));
+ items.push_back(get_item_icon(i));
+ items.push_back(is_item_checkable(i));
+ items.push_back(is_item_checked(i));
+ items.push_back(is_item_disabled(i));
+
+ items.push_back(get_item_ID(i));
+ items.push_back(get_item_accelerator(i));
+ items.push_back(get_item_metadata(i));
+ items.push_back(get_item_submenu(i));
+ items.push_back(is_item_separator(i));
+ }
+
+ return items;
+
+}
+void PopupMenu::_set_items(const Array& p_items){
+
+ ERR_FAIL_COND(p_items.size() % 10);
+ clear();
+
+ for(int i=0;i<p_items.size();i+=10) {
+
+ String text=p_items[i+0];
+ Ref<Texture> icon=p_items[i+1];
+ bool checkable=p_items[i+2];
+ bool checked=p_items[i+3];
+ bool disabled=p_items[i+4];
+
+ int id=p_items[i+5];
+ int accel=p_items[i+6];
+ Variant meta=p_items[i+7];
+ String subm=p_items[i+8];
+ bool sep=p_items[i+9];
+
+ int idx=get_item_count();
+ add_item(text,id);
+ set_item_icon(idx,icon);
+ set_item_as_checkable(idx,checkable);
+ set_item_checked(idx,checked);
+ set_item_disabled(idx,disabled);
+ set_item_ID(idx,id);
+ set_item_metadata(idx,meta);
+ set_item_as_separator(idx,sep);
+ set_item_accelerator(idx,accel);
+ set_item_submenu(idx,subm);
+ }
+
+
+}
+
+
+String PopupMenu::get_tooltip(const Point2& p_pos) const {
+
+
+ int over=_get_mouse_over(p_pos);
+ if (over<0 || over>=items.size())
+ return "";
+ return items[over].tooltip;
+}
+
+
+void PopupMenu::set_parent_rect(const Rect2& p_rect) {
+
+ parent_rect=p_rect;
+}
+
+void PopupMenu::get_translatable_strings(List<String> *p_strings) const {
+
+ for(int i=0;i<items.size();i++) {
+
+ if (items[i].text!="")
+ p_strings->push_back(items[i].text);
+ }
+}
+
+void PopupMenu::add_autohide_area(const Rect2& p_area) {
+
+ autohide_areas.push_back(p_area);
+}
+
+void PopupMenu::clear_autohide_areas(){
+
+ autohide_areas.clear();
+}
+
+void PopupMenu::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&PopupMenu::_input_event);
+ ObjectTypeDB::bind_method(_MD("add_icon_item","texture","label","id","accel"),&PopupMenu::add_icon_item,DEFVAL(-1),DEFVAL(0));
+ ObjectTypeDB::bind_method(_MD("add_item","label","id","accel"),&PopupMenu::add_item,DEFVAL(-1),DEFVAL(0));
+ ObjectTypeDB::bind_method(_MD("add_icon_check_item","texture","label","id","accel"),&PopupMenu::add_icon_check_item,DEFVAL(-1),DEFVAL(0));
+ ObjectTypeDB::bind_method(_MD("add_check_item","label","id","accel"),&PopupMenu::add_check_item,DEFVAL(-1),DEFVAL(0));
+ ObjectTypeDB::bind_method(_MD("add_submenu_item","label","submenu","id"),&PopupMenu::add_check_item,DEFVAL(-1));
+ ObjectTypeDB::bind_method(_MD("set_item_text","idx","text"),&PopupMenu::set_item_text);
+ ObjectTypeDB::bind_method(_MD("set_item_icon","idx","icon"),&PopupMenu::set_item_icon);
+ ObjectTypeDB::bind_method(_MD("set_item_accelerator","idx","accel"),&PopupMenu::set_item_accelerator);
+ ObjectTypeDB::bind_method(_MD("set_item_metadata","idx","metadata"),&PopupMenu::set_item_metadata);
+ ObjectTypeDB::bind_method(_MD("set_item_checked","idx"),&PopupMenu::set_item_checked);
+ ObjectTypeDB::bind_method(_MD("set_item_disabled","idx","disabled"),&PopupMenu::set_item_disabled);
+ ObjectTypeDB::bind_method(_MD("set_item_submenu","idx","submenu"),&PopupMenu::set_item_submenu);
+ ObjectTypeDB::bind_method(_MD("set_item_as_separator","idx","enable"),&PopupMenu::set_item_as_separator);
+ ObjectTypeDB::bind_method(_MD("set_item_as_checkable","idx","enable"),&PopupMenu::set_item_as_checkable);
+ ObjectTypeDB::bind_method(_MD("set_item_ID","idx","id"),&PopupMenu::set_item_ID);
+ ObjectTypeDB::bind_method(_MD("get_item_text","idx"),&PopupMenu::get_item_text);
+ ObjectTypeDB::bind_method(_MD("get_item_icon","idx"),&PopupMenu::get_item_icon);
+ ObjectTypeDB::bind_method(_MD("get_item_metadata","idx"),&PopupMenu::get_item_metadata);
+ ObjectTypeDB::bind_method(_MD("get_item_accelerator","idx"),&PopupMenu::get_item_accelerator);
+ ObjectTypeDB::bind_method(_MD("get_item_submenu","idx"),&PopupMenu::get_item_submenu);
+ ObjectTypeDB::bind_method(_MD("is_item_separator","idx"),&PopupMenu::is_item_separator);
+ ObjectTypeDB::bind_method(_MD("is_item_checkable","idx"),&PopupMenu::is_item_checkable);
+ ObjectTypeDB::bind_method(_MD("is_item_checked","idx"),&PopupMenu::is_item_checked);
+ ObjectTypeDB::bind_method(_MD("is_item_disabled","idx"),&PopupMenu::is_item_disabled);
+ ObjectTypeDB::bind_method(_MD("get_item_ID","idx"),&PopupMenu::get_item_ID);
+ ObjectTypeDB::bind_method(_MD("get_item_index","id"),&PopupMenu::get_item_index);
+ ObjectTypeDB::bind_method(_MD("get_item_count"),&PopupMenu::get_item_count);
+ ObjectTypeDB::bind_method(_MD("add_separator"),&PopupMenu::add_separator);
+ ObjectTypeDB::bind_method(_MD("remove_item","idx"),&PopupMenu::remove_item);
+ ObjectTypeDB::bind_method(_MD("clear"),&PopupMenu::clear);
+
+ ObjectTypeDB::bind_method(_MD("_set_items"),&PopupMenu::_set_items);
+ ObjectTypeDB::bind_method(_MD("_get_items"),&PopupMenu::_get_items);
+
+ ObjectTypeDB::bind_method(_MD("_submenu_timeout"),&PopupMenu::_submenu_timeout);
+
+ ADD_PROPERTY( PropertyInfo(Variant::ARRAY,"items",PROPERTY_HINT_NONE,"",PROPERTY_USAGE_NOEDITOR), _SCS("_set_items"),_SCS("_get_items") );
+
+ ADD_SIGNAL( MethodInfo("item_pressed", PropertyInfo( Variant::INT,"ID") ) );
+
+}
+
+PopupMenu::PopupMenu() {
+
+ idcount=0;
+ mouse_over=-1;
+
+
+ set_focus_mode(FOCUS_ALL);
+ set_as_toplevel(true);
+
+ submenu_timer = memnew( Timer );
+ submenu_timer->set_wait_time(0.3);
+ submenu_timer->set_one_shot(true);
+ submenu_timer->connect("timeout",this,"_submenu_timeout");
+ add_child(submenu_timer);
+}
+
+
+PopupMenu::~PopupMenu() {
+}
+
+
diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h
new file mode 100644
index 0000000000..b150be1008
--- /dev/null
+++ b/scene/gui/popup_menu.h
@@ -0,0 +1,142 @@
+/*************************************************************************/
+/* popup_menu.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef POPUP_MENU_H
+#define POPUP_MENU_H
+
+#include "scene/gui/popup.h"
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class PopupMenu : public Popup {
+
+ OBJ_TYPE(PopupMenu, Popup );
+
+ struct Item {
+ Ref<Texture> icon;
+ String text;
+ bool checked;
+ bool checkable;
+ bool separator;
+ bool disabled;
+ int ID;
+ Variant metadata;
+ String submenu;
+ String tooltip;
+ uint32_t accel;
+ int _ofs_cache;
+
+ Item() { checked=false; checkable=false; separator=false; accel=0; disabled=false; _ofs_cache=0; }
+ };
+
+
+ Timer *submenu_timer;
+ List<Rect2> autohide_areas;
+ Vector<Item> items;
+ int idcount;
+ int mouse_over;
+ int submenu_over;
+ Rect2 parent_rect;
+ String _get_accel_text(uint32_t p_accel) const;
+ int _get_mouse_over(const Point2& p_over) const;
+ virtual Size2 get_minimum_size() const;
+ void _input_event(const InputEvent &p_event);
+ void _activate_submenu(int over);
+ void _submenu_timeout();
+
+
+ Array _get_items() const;
+ void _set_items(const Array& p_items);
+
+protected:
+
+ virtual bool has_point(const Point2& p_point) const;
+
+friend class MenuButton;
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+
+ void add_icon_item(const Ref<Texture>& p_icon,const String& p_label,int p_ID=-1,uint32_t p_accel=0);
+ 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_submenu_item(const String& p_label,const String& p_submenu, int p_ID=-1);
+
+ void set_item_text(int p_idx,const String& p_text);
+ void set_item_icon(int p_idx,const Ref<Texture>& p_icon);
+ void set_item_checked(int p_idx,bool p_checked);
+ void set_item_ID(int p_idx,int p_ID);
+ void set_item_accelerator(int p_idx,uint32_t p_accel);
+ void set_item_metadata(int p_idx,const Variant& p_meta);
+ void set_item_disabled(int p_idx,bool p_disabled);
+ 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_tooltip(int p_idx,const String& p_tooltip);
+
+ String get_item_text(int p_idx) const;
+ Ref<Texture> get_item_icon(int p_idx) const;
+ bool is_item_checked(int p_idx) const;
+ int get_item_ID(int p_idx) const;
+ int get_item_index(int p_ID) const;
+ uint32_t get_item_accelerator(int p_idx) const;
+ Variant get_item_metadata(int p_idx) const;
+ bool is_item_disabled(int p_idx) const;
+ String get_item_submenu(int p_ID) const;
+ bool is_item_separator(int p_idx) const;
+ bool is_item_checkable(int p_idx) const;
+ String get_item_tooltip(int p_idx) const;
+
+ int get_item_count() const;
+
+ int find_item_by_accelerator(uint32_t p_accel) const;
+ void activate_item(int p_item);
+
+ void remove_item(int p_idx);
+
+ void add_separator();
+
+ void clear();
+
+ void set_parent_rect(const Rect2& p_rect);
+
+ virtual String get_tooltip(const Point2& p_pos) const;
+
+ virtual void get_translatable_strings(List<String> *p_strings) const;
+
+ void add_autohide_area(const Rect2& p_area);
+ void clear_autohide_areas();
+
+ PopupMenu();
+ ~PopupMenu();
+
+};
+
+#endif
diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp
new file mode 100644
index 0000000000..09b960f345
--- /dev/null
+++ b/scene/gui/progress_bar.cpp
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* progress_bar.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "progress_bar.h"
+
+
+Size2 ProgressBar::get_minimum_size() const {
+
+ Ref<StyleBox> bg = get_stylebox("bg");
+ Ref<Font> font = get_font("font");
+
+ Size2 ms=bg->get_minimum_size()+bg->get_center_size();
+ ms.height=MAX(ms.height,bg->get_minimum_size().height+font->get_height());
+ return ms;
+}
+
+
+void ProgressBar::_notification(int p_what) {
+
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ Ref<StyleBox> bg = get_stylebox("bg");
+ Ref<StyleBox> fg = get_stylebox("fg");
+ Ref<Font> font = get_font("font");
+ Color font_color=get_color("font_color");
+ Color font_color_shadow=get_color("font_color_shadow");
+
+ draw_style_box(bg,Rect2(Point2(),get_size()));
+ float r = get_unit_value();
+ int mp = fg->get_minimum_size().width;
+ int p = r*get_size().width-mp;
+ if (p>0) {
+
+ draw_style_box(fg,Rect2(Point2(),Size2(p+fg->get_minimum_size().width,get_size().height)));
+ }
+
+ int fh=font->get_height();
+ String txt=itos(int(get_unit_value()*100))+"%";
+ font->draw_halign(get_canvas_item(),Point2(0,font->get_ascent()+(get_size().height-font->get_height())/2),HALIGN_CENTER,get_size().width,txt,font_color);
+ }
+}
+
+ProgressBar::ProgressBar() {
+
+ set_v_size_flags(0);
+}
diff --git a/scene/gui/progress_bar.h b/scene/gui/progress_bar.h
new file mode 100644
index 0000000000..fa334a2ad9
--- /dev/null
+++ b/scene/gui/progress_bar.h
@@ -0,0 +1,47 @@
+/*************************************************************************/
+/* progress_bar.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef PROGRESS_BAR_H
+#define PROGRESS_BAR_H
+
+#include "scene/gui/range.h"
+
+class ProgressBar : public Range {
+
+ OBJ_TYPE( ProgressBar, Range );
+
+protected:
+
+ void _notification(int p_what);
+public:
+
+ Size2 get_minimum_size() const;
+ ProgressBar();
+};
+
+#endif // PROGRESS_BAR_H
diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp
new file mode 100644
index 0000000000..bc0fa9675e
--- /dev/null
+++ b/scene/gui/range.cpp
@@ -0,0 +1,290 @@
+/*************************************************************************/
+/* range.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "range.h"
+
+
+
+void Range::_value_changed_notify() {
+
+ _value_changed(shared->val);
+ emit_signal("value_changed",shared->val);
+ update();
+ _change_notify("range/value");
+}
+
+void Range::Shared::emit_value_changed() {
+
+ for (Set<Range*>::Element *E=owners.front();E;E=E->next()) {
+ Range *r=E->get();
+ if (!r->is_inside_scene())
+ continue;
+ r->_value_changed_notify();
+ }
+}
+
+void Range::_changed_notify() {
+
+ emit_signal("changed",shared->val);
+ update();
+ _change_notify();
+}
+
+void Range::Shared::emit_changed() {
+
+ for (Set<Range*>::Element *E=owners.front();E;E=E->next()) {
+ Range *r=E->get();
+ if (!r->is_inside_scene())
+ continue;
+ r->_changed_notify();
+ }
+}
+
+
+void Range::set_val(double p_val) {
+
+ if(_rounded_values){
+ p_val = Math::round(p_val);
+ }
+
+ if (p_val>shared->max-shared->page)
+ p_val=shared->max-shared->page;
+
+ if (p_val<shared->min)
+ p_val=shared->min;
+
+ if (shared->val==p_val)
+ return;
+
+ shared->val=p_val;
+
+ shared->emit_value_changed();
+}
+void Range::set_min(double p_min) {
+
+ shared->min=p_min;
+ set_val(shared->val);
+
+ shared->emit_changed();
+}
+void Range::set_max(double p_max) {
+
+ shared->max=p_max;
+ set_val(shared->val);
+
+ shared->emit_changed();
+
+}
+void Range::set_step(double p_step) {
+
+ shared->step=p_step;
+ shared->emit_changed();
+
+}
+void Range::set_page(double p_page) {
+
+ shared->page=p_page;
+ set_val(shared->val);
+
+ shared->emit_changed();
+}
+
+double Range::get_val() const {
+
+ return shared->val;
+}
+double Range::get_min() const {
+
+ return shared->min;
+}
+double Range::get_max() const {
+
+ return shared->max;
+}
+double Range::get_step() const {
+
+ return shared->step;
+}
+double Range::get_page() const {
+
+ return shared->page;
+}
+
+void Range::set_unit_value(double p_value) {
+ if (shared->exp_unit_value && get_min()>0) {
+
+ double exp_min = Math::log(get_min())/Math::log(2);
+ double exp_max = Math::log(get_max())/Math::log(2);
+ double v = Math::pow(2,exp_min+(exp_max-exp_min)*p_value);
+
+ set_val( v );
+ } else {
+ set_val( (get_max() - get_min()) * p_value + get_min() );
+ }
+}
+double Range::get_unit_value() const {
+
+ if (shared->exp_unit_value && get_min()>0) {
+
+ double exp_min = Math::log(get_min())/Math::log(2);
+ double exp_max = Math::log(get_max())/Math::log(2);
+ double v = Math::log(get_val())/Math::log(2);
+
+ return (v - exp_min) / (exp_max - exp_min);
+
+ } else {
+
+ return (get_val() - get_min()) / (get_max() - get_min());
+ }
+}
+
+void Range::_share(Node *p_range) {
+
+ Range * r = p_range->cast_to<Range>();
+ ERR_FAIL_COND(!r);
+ share(r);
+}
+
+void Range::share(Range *p_range) {
+
+ ERR_FAIL_NULL(p_range);
+
+ p_range->_ref_shared(shared);
+ p_range->_changed_notify();
+ p_range->_value_changed_notify();
+}
+
+void Range::unshare() {
+
+ Shared * nshared = memnew(Shared);
+ nshared->min=shared->min;
+ nshared->max=shared->max;
+ nshared->val=shared->val;
+ nshared->step=shared->step;
+ nshared->page=shared->page;
+ _unref_shared();
+ _ref_shared(nshared);
+}
+
+void Range::_ref_shared(Shared *p_shared) {
+
+ if (shared && p_shared==shared)
+ return;
+
+ _unref_shared();
+ shared=p_shared;
+ shared->owners.insert(this);
+}
+
+
+void Range::_unref_shared() {
+
+ shared->owners.erase(this);
+ if (shared->owners.size()==0) {
+ memdelete(shared);
+ shared=NULL;
+ }
+}
+
+void Range::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("get_val"),&Range::get_val);
+ ObjectTypeDB::bind_method(_MD("get_value"),&Range::get_val);
+ ObjectTypeDB::bind_method(_MD("get_min"),&Range::get_min);
+ ObjectTypeDB::bind_method(_MD("get_max"),&Range::get_max);
+ ObjectTypeDB::bind_method(_MD("get_step"),&Range::get_step);
+ ObjectTypeDB::bind_method(_MD("get_page"),&Range::get_page);
+ ObjectTypeDB::bind_method(_MD("get_unit_value"),&Range::get_unit_value);
+ ObjectTypeDB::bind_method(_MD("get_rounded_values"),&Range::get_rounded_values);
+ ObjectTypeDB::bind_method(_MD("set_val","value"),&Range::set_val);
+ ObjectTypeDB::bind_method(_MD("set_value","value"),&Range::set_val);
+ ObjectTypeDB::bind_method(_MD("set_min","minimum"),&Range::set_min);
+ ObjectTypeDB::bind_method(_MD("set_max","maximum"),&Range::set_max);
+ ObjectTypeDB::bind_method(_MD("set_step","step"),&Range::set_step);
+ ObjectTypeDB::bind_method(_MD("set_page","pagesize"),&Range::set_page);
+ ObjectTypeDB::bind_method(_MD("set_unit_value","value"),&Range::set_unit_value);
+ ObjectTypeDB::bind_method(_MD("set_rounded_values"),&Range::set_rounded_values);
+ ObjectTypeDB::bind_method(_MD("set_exp_unit_value","enabled"),&Range::set_exp_unit_value);
+ ObjectTypeDB::bind_method(_MD("is_unit_value_exp"),&Range::is_unit_value_exp);
+
+ ObjectTypeDB::bind_method(_MD("share","with"),&Range::_share);
+ ObjectTypeDB::bind_method(_MD("unshare"),&Range::unshare);
+
+ ADD_SIGNAL( MethodInfo("value_changed", PropertyInfo(Variant::REAL,"value")));
+ ADD_SIGNAL( MethodInfo("changed"));
+
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/min" ), _SCS("set_min"), _SCS("get_min") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/max" ), _SCS("set_max"), _SCS("get_max") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/step" ), _SCS("set_step"), _SCS("get_step") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/page" ), _SCS("set_page"), _SCS("get_page") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/value" ), _SCS("set_val"), _SCS("get_val") );
+ ADD_PROPERTY( PropertyInfo( Variant::REAL, "range/exp_edit" ), _SCS("set_exp_unit_value"), _SCS("is_unit_value_exp") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "rounded_values" ), _SCS("set_rounded_values"), _SCS("get_rounded_values") );
+
+}
+
+void Range::set_rounded_values(bool p){
+ _rounded_values = p;
+}
+
+bool Range::get_rounded_values() const{
+ return _rounded_values;
+}
+
+void Range::set_exp_unit_value(bool p_enable) {
+
+ shared->exp_unit_value=p_enable;
+}
+
+bool Range::is_unit_value_exp() const {
+
+ return shared->exp_unit_value;
+}
+
+
+Range::Range()
+{
+ shared = memnew(Shared);
+ shared->min=0;
+ shared->max=100;
+ shared->val=
+ shared->step=1;
+ shared->page=0;
+ shared->owners.insert(this);
+ shared->exp_unit_value=false;
+
+ _rounded_values = false;
+}
+
+
+Range::~Range() {
+
+ _unref_shared();
+}
+
+
diff --git a/scene/gui/range.h b/scene/gui/range.h
new file mode 100644
index 0000000000..e33a71e6ab
--- /dev/null
+++ b/scene/gui/range.h
@@ -0,0 +1,96 @@
+/*************************************************************************/
+/* range.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef RANGE_H
+#define RANGE_H
+
+#include "scene/gui/control.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class Range : public Control {
+
+ OBJ_TYPE( Range, Control );
+
+
+ struct Shared {
+ double val,min,max;
+ double step,page;
+ bool exp_unit_value;
+ Set<Range*> owners;
+ void emit_value_changed();
+ void emit_changed();
+ };
+
+ Shared *shared;
+
+ void _ref_shared(Shared *p_shared);
+ void _unref_shared();
+
+ void _share(Node *p_range);
+
+ void _value_changed_notify();
+ void _changed_notify();
+
+protected:
+
+ virtual void _value_changed(double) {}
+
+ static void _bind_methods();
+
+ bool _rounded_values;
+public:
+
+ void set_val(double p_val);
+ void set_min(double p_min);
+ void set_max(double p_max);
+ void set_step(double p_step);
+ void set_page(double p_page);
+ void set_unit_value(double p_value);
+ void set_rounded_values(bool);
+
+ double get_val() const;
+ double get_min() const;
+ double get_max() const;
+ double get_step() const;
+ double get_page() const;
+ double get_unit_value() const;
+ bool get_rounded_values() const;
+
+ void set_exp_unit_value(bool p_enable);
+ bool is_unit_value_exp() const;
+
+ void share(Range *p_range);
+ void unshare();
+
+ Range();
+ ~Range();
+
+};
+
+#endif
diff --git a/scene/gui/reference_frame.cpp b/scene/gui/reference_frame.cpp
new file mode 100644
index 0000000000..679c2cc1e2
--- /dev/null
+++ b/scene/gui/reference_frame.cpp
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* reference_frame.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "reference_frame.h"
+
+void ReferenceFrame::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ if (!is_inside_scene())
+ return;
+ if (get_scene()->is_editor_hint())
+ draw_style_box(get_stylebox("border"),Rect2(Point2(),get_size())) ;
+ }
+}
+
+ReferenceFrame::ReferenceFrame()
+{
+}
diff --git a/scene/gui/reference_frame.h b/scene/gui/reference_frame.h
new file mode 100644
index 0000000000..954b17b5c8
--- /dev/null
+++ b/scene/gui/reference_frame.h
@@ -0,0 +1,45 @@
+/*************************************************************************/
+/* reference_frame.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef REFERENCE_FRAME_H
+#define REFERENCE_FRAME_H
+
+#include "scene/gui/control.h"
+
+class ReferenceFrame : public Control {
+
+ OBJ_TYPE( ReferenceFrame, Control);
+
+protected:
+
+ void _notification(int p_what);
+public:
+ ReferenceFrame();
+};
+
+#endif // REFERENCE_FRAME_H
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
new file mode 100644
index 0000000000..95c3affdf5
--- /dev/null
+++ b/scene/gui/rich_text_label.cpp
@@ -0,0 +1,1476 @@
+/*************************************************************************/
+/* rich_text_label.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "rich_text_label.h"
+#include "scene/scene_string_names.h"e"
+
+
+RichTextLabel::Item *RichTextLabel::_get_next_item(Item* p_item) {
+
+ if (p_item->subitems.size()) {
+
+ return p_item->subitems.front()->get();
+ } else if (!p_item->parent) {
+ return NULL;
+ } else if (p_item->E->next()) {
+
+ return p_item->E->next()->get();
+ } else {
+ //go up until something with a next is found
+ while (p_item->parent && !p_item->E->next()) {
+ p_item=p_item->parent;
+ }
+
+
+ if (p_item && p_item->parent)
+ return p_item->E->next()->get();
+ else
+ return NULL;
+
+ }
+
+ return NULL;
+
+}
+
+void RichTextLabel::_process_line(int &y, int p_width, int p_line, ProcessMode p_mode,const Ref<Font> &p_base_font,const Color &p_base_color,const Point2i& p_click_pos,Item **r_click_item,int *r_click_char,bool *r_outside) {
+
+ RID ci;
+ if (r_outside)
+ *r_outside=false;
+ if (p_mode==PROCESS_DRAW) {
+ ci=get_canvas_item();
+
+ if (r_click_item)
+ *r_click_item=NULL;
+
+ }
+ Line &l = lines[p_line];
+ Item *it = l.from;
+
+
+ int line_ofs=0;
+ int margin=_find_margin(it,p_base_font);
+ Align align=_find_align(it);;
+ int line=0;
+
+ if (p_mode!=PROCESS_CACHE) {
+
+ ERR_FAIL_INDEX(line,l.offset_caches.size());
+ line_ofs = l.offset_caches[line];
+ }
+
+ int wofs=margin;
+
+ if (p_mode!=PROCESS_CACHE && align!=ALIGN_FILL)
+ wofs+=line_ofs;
+
+ int begin=wofs;
+
+ Ref<Font> cfont = _find_font(it);
+ if (cfont.is_null())
+ cfont=p_base_font;
+
+ //line height should be the font height for the first time, this ensures that an empty line will never have zero height and succesive newlines are displayed
+ int line_height=cfont->get_height();
+
+ Variant meta;
+
+
+#define NEW_LINE \
+{\
+ if (p_mode!=PROCESS_CACHE) {\
+ line++;\
+ if (line < l.offset_caches.size())\
+ line_ofs=l.offset_caches[line];\
+ wofs=margin;\
+ if (align!=ALIGN_FILL)\
+ wofs+=line_ofs;\
+ } else {\
+ int used=wofs-margin;\
+ switch(align) {\
+ case ALIGN_LEFT: l.offset_caches.push_back(0); break;\
+ case ALIGN_CENTER: l.offset_caches.push_back(((p_width-margin)-used)/2); break;\
+ case ALIGN_RIGHT: l.offset_caches.push_back(((p_width-margin)-used)); break;\
+ case ALIGN_FILL: l.offset_caches.push_back(p_width-wofs); break;\
+ }\
+ l.height_caches.push_back(line_height);\
+ }\
+ y+=line_height+get_constant(SceneStringNames::get_singleton()->line_separation);\
+ line_height=0;\
+ wofs=begin;\
+ if (p_mode!=PROCESS_CACHE) {\
+ lh=line<l.height_caches.size()?l.height_caches[line]:1;\
+ }\
+ if (p_mode==PROCESS_POINTER && r_click_item && p_click_pos.y>=y && p_click_pos.y<=y+lh && p_click_pos.x<wofs) {\
+ if (r_outside) *r_outside=true;\
+ *r_click_item=it;\
+ *r_click_char=rchar;\
+ return;\
+ }\
+}
+
+
+#define ENSURE_WIDTH(m_width) \
+ if (wofs + m_width > p_width) {\
+ if (p_mode==PROCESS_POINTER && r_click_item && p_click_pos.y>=y && p_click_pos.y<=y+lh && p_click_pos.x>wofs) {\
+ if (r_outside) *r_outside=true; \
+ *r_click_item=it;\
+ *r_click_char=rchar;\
+ return;\
+ }\
+ NEW_LINE\
+ }
+
+
+#define ADVANCE(m_width) \
+{\
+ if (p_mode==PROCESS_POINTER && r_click_item && p_click_pos.y>=y && p_click_pos.y<=y+lh && p_click_pos.x>=wofs && p_click_pos.x<wofs+m_width) {\
+ if (r_outside) *r_outside=false; \
+ *r_click_item=it;\
+ *r_click_char=rchar;\
+ return;\
+ }\
+ wofs+=m_width;\
+}
+
+#define CHECK_HEIGHT( m_height ) \
+if (m_height > line_height) {\
+ line_height=m_height;\
+}
+
+ Color selection_fg;
+ Color selection_bg;
+
+ if (p_mode==PROCESS_DRAW) {
+
+
+ selection_fg = get_color("font_color_selected");
+ selection_bg = get_color("selection_color");
+ }
+ int rchar=0;
+ int lh=0;
+
+ while (it) {
+
+ switch(it->type) {
+
+ case ITEM_TEXT: {
+
+ ItemText *text = static_cast<ItemText*>(it);
+
+ Ref<Font> font=_find_font(it);
+ if (font.is_null())
+ font=p_base_font;
+
+ const CharType *c = text->text.c_str();
+ const CharType *cf=c;
+ int fh=font->get_height();
+ int ascent = font->get_ascent();
+ Color color;
+ bool underline=false;
+
+ if (p_mode==PROCESS_DRAW) {
+ color=_find_color(text,p_base_color);
+ underline=_find_underline(text);
+ if (_find_meta(text,&meta)) {
+
+ underline=true;
+ }
+
+ }
+
+ rchar=0;
+ while(*c) {
+
+ int end=0;
+ int w=0;
+
+ lh=0;
+ if (p_mode!=PROCESS_CACHE) {
+ lh=line<l.height_caches.size()?l.height_caches[line]:1;
+ }
+
+ while (c[end]!=0 && !(end && c[end-1]==' ' && c[end]!=' ')) {
+ int cw = font->get_char_size(c[end],c[end+1]).width;
+ w+=cw;
+ end++;
+ }
+
+ ENSURE_WIDTH(w);
+
+
+
+ {
+
+
+ int ofs=0;
+ for(int i=0;i<end;i++) {
+ int pofs=wofs+ofs;
+
+
+
+
+ if (p_mode==PROCESS_POINTER && r_click_char && p_click_pos.y>=y && p_click_pos.y<=y+lh) {
+ //int o = (wofs+w)-p_click_pos.x;
+
+ int cw=font->get_char_size(c[i],c[i+1]).x;
+
+ if (p_click_pos.x-cw/2>pofs) {
+
+ rchar=int((&c[i])-cf);
+ //print_line("GOT: "+itos(rchar));
+
+
+ //if (i==end-1 && p_click_pos.x+cw/2 > pofs)
+ // rchar++;
+ //int o = (wofs+w)-p_click_pos.x;
+
+ // if (o>cw/2)
+ // rchar++;
+ }
+
+
+ ofs+=cw;
+ } else if (p_mode==PROCESS_DRAW) {
+
+ bool selected=false;
+ if (selection.active) {
+
+ int cofs = (&c[i])-cf;
+ if ((text->index > selection.from->index || (text->index == selection.from->index && cofs >=selection.from_char)) && (text->index < selection.to->index || (text->index == selection.to->index && cofs <=selection.to_char))) {
+ selected=true;
+ }
+ }
+
+ int cw;
+
+ if (selected) {
+
+ cw = font->get_char_size(c[i],c[i+1]).x;
+ draw_rect(Rect2(pofs,y,cw,lh),selection_bg);
+ font->draw_char(ci,Point2(pofs,y+lh-(fh-ascent)),c[i],c[i+1],selection_fg);
+
+ } else {
+ cw=font->draw_char(ci,Point2(pofs,y+lh-(fh-ascent)),c[i],c[i+1],color);
+ }
+
+ if (underline) {
+ Color uc=color;
+ uc.a*=0.3;
+ VS::get_singleton()->canvas_item_add_line(ci,Point2(pofs,y+ascent+2),Point2(pofs+cw,y+ascent+2),uc);
+ }
+ ofs+=cw;
+ }
+
+ }
+ }
+
+
+ ADVANCE(w);
+ CHECK_HEIGHT(fh); //must be done somewhere
+ c=&c[end];
+ }
+
+
+ } break;
+ case ITEM_IMAGE: {
+
+ lh=0;
+ if (p_mode!=PROCESS_CACHE)
+ lh = line<l.height_caches.size()?l.height_caches[line]:1;
+
+ ItemImage *img = static_cast<ItemImage*>(it);
+
+ Ref<Font> font=_find_font(it);
+ if (font.is_null())
+ font=p_base_font;
+
+ if (p_mode==PROCESS_POINTER && r_click_char)
+ *r_click_char=0;
+
+ ENSURE_WIDTH( img->image->get_width() );
+
+ if (p_mode==PROCESS_DRAW) {
+ img->image->draw(ci,Point2(wofs,y+lh-font->get_descent()-img->image->get_height()));
+ }
+
+ ADVANCE( img->image->get_width() );
+ CHECK_HEIGHT( (img->image->get_height()+font->get_descent()) );
+
+ } break;
+ case ITEM_NEWLINE: {
+
+
+ lh=0;
+ if (p_mode!=PROCESS_CACHE)
+ lh = line<l.height_caches.size()?l.height_caches[line]:1;
+
+
+#if 0
+ if (p_mode==PROCESS_POINTER && r_click_item ) {
+ //previous last "wrapped" line
+ int pl = line-1;
+ if (pl<0 || lines[pl].height_caches.size()==0)
+ break;
+ int py=lines[pl].offset_caches[ lines[pl].offset_caches.size() -1 ];
+ int ph=lines[pl].height_caches[ lines[pl].height_caches.size() -1 ];
+ print_line("py: "+itos(py));
+ print_line("ph: "+itos(ph));
+
+ rchar=0;
+ if (p_click_pos.y>=py && p_click_pos.y<=py+ph) {
+ if (r_outside) *r_outside=true;
+ *r_click_item=it;
+ *r_click_char=rchar;
+ return;
+ }
+ }
+
+#endif
+ } break;
+
+ default: {}
+
+ }
+
+
+ Item *itp = it;
+
+ it = _get_next_item(it);
+
+ if (p_mode == PROCESS_POINTER && r_click_item && itp && !it && p_click_pos.y>y+lh) {
+ //at the end of all, return this
+ if (r_outside) *r_outside=true;
+ *r_click_item=itp;
+ *r_click_char=rchar;
+ return;
+ }
+
+ if (it && (p_line+1 < lines.size()) && lines[p_line+1].from==it) {
+
+ if (p_mode==PROCESS_POINTER && r_click_item && p_click_pos.y>=y && p_click_pos.y<=y+lh) {
+ //went to next line, but pointer was on the previous one
+ if (r_outside) *r_outside=true;
+ *r_click_item=itp;
+ *r_click_char=rchar;
+ return;
+ }
+
+ break;
+ }
+ }
+
+ NEW_LINE;
+
+#undef NEW_LINE
+#undef ENSURE_WIDTH
+#undef ADVANCE
+#undef CHECK_HEIGHT
+
+}
+
+void RichTextLabel::_scroll_changed(double) {
+
+ if (updating_scroll)
+ return;
+
+ if (scroll_follow && vscroll->get_val()>=(vscroll->get_max()-vscroll->get_page()))
+ scroll_following=true;
+ else
+ scroll_following=false;
+
+ update();
+
+}
+
+void RichTextLabel::_update_scroll() {
+
+ int total_height=0;
+ if (lines.size())
+ total_height=lines[lines.size()-1].height_accum_cache;
+
+ bool exceeds = total_height > get_size().height && scroll_active;
+
+
+ if (exceeds!=scroll_visible) {
+
+ if (exceeds) {
+ scroll_visible=true;
+ first_invalid_line=0;
+ scroll_w=vscroll->get_combined_minimum_size().width;
+ vscroll->show();
+ vscroll->set_anchor_and_margin( MARGIN_LEFT, ANCHOR_END,scroll_w);
+ _validate_line_caches();
+
+ } else {
+
+ scroll_visible=false;
+ vscroll->hide();
+ scroll_w=0;
+ _validate_line_caches();
+ }
+
+ }
+
+}
+
+void RichTextLabel::_notification(int p_what) {
+
+ switch (p_what) {
+
+ case NOTIFICATION_RESIZED: {
+
+ first_invalid_line=0; //invalidate ALL
+ update();
+
+ } break;
+ case NOTIFICATION_DRAW: {
+
+ _validate_line_caches();
+ _update_scroll();
+
+ RID ci=get_canvas_item();
+ Size2 size = get_size();
+
+ VisualServer::get_singleton()->canvas_item_set_clip(ci,true);
+
+ int ofs = vscroll->get_val();
+
+ //todo, change to binary search
+
+ int from_line = 0;
+ while (from_line<lines.size()) {
+
+ if (lines[from_line].height_accum_cache>=ofs)
+ break;
+ from_line++;
+ }
+
+ if (from_line>=lines.size())
+ break; //nothing to draw
+
+ int y = (lines[from_line].height_accum_cache - lines[from_line].height_cache) - ofs;
+ Ref<Font> base_font=get_font("default_font");
+ Color base_color=get_color("default_color");
+
+ while (y<size.height && from_line<lines.size()) {
+
+ _process_line(y,size.width-scroll_w,from_line,PROCESS_DRAW,base_font,base_color);
+ from_line++;
+ }
+ }
+ }
+}
+
+
+void RichTextLabel::_find_click(const Point2i& p_click,Item **r_click_item,int *r_click_char,bool *r_outside) {
+
+ if (r_click_item)
+ *r_click_item=NULL;
+
+ Size2 size = get_size();
+
+ int ofs = vscroll->get_val();
+
+ //todo, change to binary search
+ int from_line = 0;
+
+ while (from_line<lines.size()) {
+
+ if (lines[from_line].height_accum_cache>=ofs)
+ break;
+ from_line++;
+ }
+
+
+ if (from_line>=lines.size())
+ return;
+
+
+ int y = (lines[from_line].height_accum_cache - lines[from_line].height_cache) - ofs;
+ Ref<Font> base_font=get_font("default_font");
+ Color base_color=get_color("default_color");
+
+
+ while (y<size.height && from_line<lines.size()) {
+
+ _process_line(y,size.width-scroll_w,from_line,PROCESS_POINTER,base_font,base_color,p_click,r_click_item,r_click_char,r_outside);
+ if (r_click_item && *r_click_item)
+ return;
+ from_line++;
+ }
+
+
+}
+
+
+Control::CursorShape RichTextLabel::get_cursor_shape(const Point2& p_pos) const {
+
+ if (!underline_meta || selection.click)
+ return CURSOR_ARROW;
+
+ if (first_invalid_line<lines.size())
+ return CURSOR_ARROW; //invalid
+
+ int line=0;
+ Item *item=NULL;
+
+ ((RichTextLabel*)(this))->_find_click(p_pos,&item,&line);
+
+
+ if (item && ((RichTextLabel*)(this))->_find_meta(item,NULL))
+ return CURSOR_POINTING_HAND;
+
+ return CURSOR_ARROW;
+}
+
+
+void RichTextLabel::_input_event(InputEvent p_event) {
+
+ switch(p_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ if (first_invalid_line<lines.size())
+ return;
+
+ const InputEventMouseButton& b = p_event.mouse_button;
+
+ if (b.button_index==BUTTON_LEFT) {
+
+ if (true) {
+
+
+ if (b.pressed && !b.doubleclick) {
+ int line=0;
+ Item *item=NULL;
+
+ bool outside;
+ _find_click(Point2i(b.x,b.y),&item,&line,&outside);
+
+ if (item) {
+
+ Variant meta;
+ if (!outside && _find_meta(item,&meta)) {
+ //meta clicked
+
+ emit_signal("meta_clicked",meta);
+ } else if (selection.enabled) {
+
+ selection.click=item;
+ selection.click_char=line;
+
+ }
+
+ }
+
+ } else if (!b.pressed) {
+
+ selection.click=NULL;
+ }
+ }
+ }
+
+ if (b.button_index==BUTTON_WHEEL_UP) {
+
+ if (scroll_active)
+ vscroll->set_val( vscroll->get_val()-vscroll->get_page()/8 );
+ }
+ if (b.button_index==BUTTON_WHEEL_DOWN) {
+
+ if (scroll_active)
+ vscroll->set_val( vscroll->get_val()+vscroll->get_page()/8 );
+ }
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ if (first_invalid_line<lines.size())
+ return;
+
+ const InputEventMouseMotion& m = p_event.mouse_motion;
+
+ if (selection.click) {
+
+ int line=0;
+ Item *item=NULL;
+ _find_click(Point2i(m.x,m.y),&item,&line);
+ if (!item)
+ return; // do not update
+
+
+ selection.from=selection.click;
+ selection.from_char=selection.click_char;
+
+ selection.to=item;
+ selection.to_char=line;
+
+ bool swap=false;
+ if (selection.from->index > selection.to->index )
+ swap=true;
+ else if (selection.from->index == selection.to->index) {
+ if (selection.from_char > selection.to_char)
+ swap=true;
+ else if (selection.from_char == selection.to_char) {
+
+ selection.active=false;
+ return;
+ }
+ }
+
+ if (swap) {
+ SWAP( selection.from, selection.to );
+ SWAP( selection.from_char, selection.to_char );
+ }
+
+ selection.active=true;
+ update();
+
+ }
+
+ } break;
+ }
+
+}
+
+Ref<Font> RichTextLabel::_find_font(Item *p_item) {
+
+ Item *fontitem=p_item;
+
+ while(fontitem) {
+
+ if (fontitem->type==ITEM_FONT) {
+
+ ItemFont *fi = static_cast<ItemFont*>(fontitem);
+ return fi->font;
+ }
+
+ fontitem=fontitem->parent;
+ }
+
+ return Ref<Font>();
+}
+
+int RichTextLabel::_find_margin(Item *p_item,const Ref<Font>& p_base_font) {
+
+ Item *item=p_item;
+
+ int margin=0;
+
+ while(item) {
+
+ if (item->type==ITEM_INDENT) {
+
+ Ref<Font> font=_find_font(item);
+ if (font.is_null())
+ font=p_base_font;
+
+ ItemIndent *indent = static_cast<ItemIndent*>(item);
+
+ margin+=indent->level*tab_size*font->get_char_size(' ').width;
+
+ } else if (item->type==ITEM_LIST) {
+
+ Ref<Font> font=_find_font(item);
+ if (font.is_null())
+ font=p_base_font;
+
+ }
+
+ item=item->parent;
+ }
+
+ return margin;
+}
+
+
+RichTextLabel::Align RichTextLabel::_find_align(Item *p_item) {
+
+ Item *item=p_item;
+
+ while(item) {
+
+ if (item->type==ITEM_ALIGN) {
+
+ ItemAlign *align = static_cast<ItemAlign*>(item);
+ return align->align;
+
+ }
+
+ item=item->parent;
+ }
+
+ return default_align;
+}
+
+Color RichTextLabel::_find_color(Item *p_item,const Color& p_default_color) {
+
+ Item *item=p_item;
+
+ while(item) {
+
+ if (item->type==ITEM_COLOR) {
+
+ ItemColor *color = static_cast<ItemColor*>(item);
+ return color->color;
+
+ }
+
+ item=item->parent;
+ }
+
+ return p_default_color;
+}
+
+bool RichTextLabel::_find_underline(Item *p_item) {
+
+ Item *item=p_item;
+
+ while(item) {
+
+ if (item->type==ITEM_UNDERLINE) {
+
+ return true;
+
+ }
+
+ item=item->parent;
+ }
+
+ return false;
+}
+
+bool RichTextLabel::_find_meta(Item *p_item,Variant *r_meta) {
+
+ Item *item=p_item;
+
+ while(item) {
+
+ if (item->type==ITEM_META) {
+
+ ItemMeta *meta = static_cast<ItemMeta*>(item);
+ if (r_meta)
+ *r_meta=meta->meta;
+ return true;
+
+ }
+
+ item=item->parent;
+ }
+
+ return false;
+
+}
+
+void RichTextLabel::_validate_line_caches() {
+
+ if (first_invalid_line==lines.size())
+ return;
+
+ //validate invalid lines!s
+ Size2 size = get_size();
+
+ Ref<Font> base_font=get_font("default_font");
+
+ for(int i=first_invalid_line;i<lines.size();i++) {
+
+ int y=0;
+ _process_line(y,size.width-scroll_w,i,PROCESS_CACHE,base_font,Color());
+
+ lines[i].height_cache=y;
+ lines[i].height_accum_cache=y;
+
+ if (i>0)
+ lines[i].height_accum_cache+=lines[i-1].height_accum_cache;
+
+
+ }
+
+ int total_height=0;
+ if (lines.size())
+ total_height=lines[lines.size()-1].height_accum_cache;
+
+ first_invalid_line=lines.size();
+
+ updating_scroll=true;
+ vscroll->set_max(total_height);
+ vscroll->set_page(size.height);
+ if (scroll_follow && scroll_following)
+ vscroll->set_val(total_height-size.height);
+
+ updating_scroll=false;
+
+}
+
+
+void RichTextLabel::_invalidate_current_line() {
+
+ if (lines.size()-1 <= first_invalid_line) {
+
+ first_invalid_line=lines.size()-1;
+ update();
+ }
+}
+
+void RichTextLabel::add_text(const String& p_text) {
+
+ int pos=0;
+
+ while (pos<p_text.length()) {
+
+ int end=p_text.find("\n",pos);
+ String line;
+ bool eol=false;
+ if (end==-1) {
+
+ end=p_text.length();
+ } else {
+
+ eol=true;
+ }
+
+ if (pos==0 && end==p_text.length())
+ line=p_text;
+ else
+ line=p_text.substr(pos,end-pos);
+
+ if (line.length()>0) {
+
+ if (current->subitems.size() && current->subitems.back()->get()->type==ITEM_TEXT) {
+ //append text condition!
+ ItemText *ti = static_cast<ItemText*>(current->subitems.back()->get());
+ ti->text+=line;
+ _invalidate_current_line();
+
+ } else {
+ //append item condition
+ ItemText *item = memnew( ItemText );
+ item->text=line;
+ _add_item(item,false);
+
+ }
+
+
+ }
+
+ if (eol) {
+
+ ItemNewline *item = memnew( ItemNewline );
+ item->line=lines.size();
+ _add_item(item,false);
+ lines.resize(lines.size()+1);
+ lines[lines.size()-1].from=item;
+ _invalidate_current_line();
+
+ }
+
+ pos=end+1;
+ }
+}
+
+void RichTextLabel::_add_item(Item *p_item, bool p_enter) {
+
+ p_item->parent=current;
+ p_item->E=current->subitems.push_back(p_item);
+ p_item->index=current_idx++;
+
+ if (p_enter)
+ current=p_item;
+
+ if (lines[lines.size()-1].from==NULL) {
+ lines[lines.size()-1].from=p_item;
+ }
+
+ _invalidate_current_line();
+
+}
+
+void RichTextLabel::add_image(const Ref<Texture>& p_image) {
+
+ ERR_FAIL_COND(p_image.is_null());
+ ItemImage *item = memnew( ItemImage );
+
+ item->image=p_image;
+ _add_item(item,false);
+
+}
+
+void RichTextLabel::add_newline() {
+
+ ItemNewline *item = memnew( ItemNewline );
+ item->line=lines.size();
+ lines.resize(lines.size()+1);
+ _add_item(item,false);
+
+}
+
+void RichTextLabel::push_font(const Ref<Font>& p_font) {
+
+ ERR_FAIL_COND(p_font.is_null());
+ ItemFont *item = memnew( ItemFont );
+
+ item->font=p_font;
+ _add_item(item,true);
+
+}
+void RichTextLabel::push_color(const Color& p_color) {
+
+ ItemColor *item = memnew( ItemColor );
+
+ item->color=p_color;
+ _add_item(item,true);
+
+}
+void RichTextLabel::push_underline() {
+
+ ItemUnderline *item = memnew( ItemUnderline );
+
+ _add_item(item,true);
+
+}
+
+void RichTextLabel::push_align(Align p_align) {
+
+ lines.resize(lines.size()+1);
+
+ ItemAlign *item = memnew( ItemAlign );
+ item->align=p_align;
+ _add_item(item,true);
+
+ ItemNewline *itemnl = memnew( ItemNewline );
+ itemnl->line=lines.size()-1;
+ _add_item(itemnl,false);
+
+}
+
+void RichTextLabel::push_indent(int p_level) {
+
+ ERR_FAIL_COND(p_level<0);
+
+ lines.resize(lines.size()+1);
+
+ ItemIndent *item = memnew( ItemIndent );
+ item->level=p_level;
+ _add_item(item,true);
+
+ ItemNewline *itemnl = memnew( ItemNewline );
+ itemnl->line=lines.size()-1;
+ _add_item(itemnl,false);
+
+}
+
+void RichTextLabel::push_list(ListType p_list) {
+
+ ERR_FAIL_INDEX(p_list,3);
+
+ ItemList *item = memnew( ItemList );
+
+ item->list_type=p_list;
+ _add_item(item,true);
+
+}
+
+void RichTextLabel::push_meta(const Variant& p_meta) {
+
+ ItemMeta *item = memnew( ItemMeta );
+
+ item->meta=p_meta;
+ _add_item(item,true);
+
+}
+
+void RichTextLabel::pop() {
+
+ ERR_FAIL_COND(!current->parent);
+ current=current->parent;
+}
+
+void RichTextLabel::clear() {
+
+ main->_clear_children();
+ current=main;
+ lines.clear();
+ lines.resize(1);
+ first_invalid_line=0;
+ update();
+ selection.click=NULL;
+ selection.active=false;
+ current_idx=1;
+
+}
+
+void RichTextLabel::set_tab_size(int p_spaces) {
+
+ tab_size=p_spaces;
+ first_invalid_line=0;
+ update();
+}
+
+int RichTextLabel::get_tab_size() const {
+
+ return tab_size;
+}
+
+
+void RichTextLabel::set_meta_underline(bool p_underline) {
+
+ underline_meta=p_underline;
+ update();
+}
+
+bool RichTextLabel::is_meta_underlined() const {
+
+ return underline_meta;
+}
+
+void RichTextLabel::set_offset(int p_pixel) {
+
+ vscroll->set_val(p_pixel);
+}
+
+void RichTextLabel::set_scroll_active(bool p_active) {
+
+ if (scroll_active==p_active)
+ return;
+
+ scroll_active=p_active;
+ update();
+}
+
+bool RichTextLabel::is_scroll_active() const {
+
+ return scroll_active;
+}
+
+void RichTextLabel::set_scroll_follow(bool p_follow) {
+
+ scroll_follow=p_follow;
+ if (!vscroll->is_visible() || vscroll->get_val()>=(vscroll->get_max()-vscroll->get_page()))
+ scroll_following=true;
+}
+
+bool RichTextLabel::is_scroll_following() const {
+
+ return scroll_follow;
+}
+
+Error RichTextLabel::parse_bbcode(const String& p_bbcode) {
+
+
+ clear();
+ return append_bbcode(p_bbcode);
+}
+
+Error RichTextLabel::append_bbcode(const String& p_bbcode) {
+
+ int pos = 0;
+
+ List<String> tag_stack;
+ Ref<Font> base_font=get_font("default_font");
+ Color base_color=get_color("default_color");
+
+ while(pos < p_bbcode.length()) {
+
+
+ int brk_pos = p_bbcode.find("[",pos);
+
+ if (brk_pos<0)
+ brk_pos=p_bbcode.length();
+
+ if (brk_pos > pos) {
+ add_text(p_bbcode.substr(pos,brk_pos-pos));
+ }
+
+ if (brk_pos==p_bbcode.length())
+ break; //nothing else o add
+
+ int brk_end = p_bbcode.find("]",brk_pos+1);
+
+ if (brk_end==-1) {
+ //no close, add the rest
+ add_text(p_bbcode.substr(brk_pos,p_bbcode.length()-brk_pos));
+ break;
+ }
+
+
+ String tag = p_bbcode.substr(brk_pos+1,brk_end-brk_pos-1);
+
+
+ if (tag.begins_with("/")) {
+
+ bool tag_ok = tag_stack.size() && tag_stack.front()->get()==tag.substr(1,tag.length());
+
+
+
+
+
+ if (!tag_ok) {
+
+ add_text("[");
+ pos++;
+ continue;
+ }
+
+ tag_stack.pop_front();
+ pos=brk_end+1;
+ if (tag!="/img")
+ pop();
+
+ } else if (tag=="b") {
+
+ //use bold font
+ push_font(base_font);
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+ } else if (tag=="i") {
+
+ //use italics font
+ push_font(base_font);
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+ } else if (tag=="code") {
+
+ //use monospace font
+ push_font(base_font);
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+ } else if (tag=="u") {
+
+ //use underline
+ push_underline();
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+ } else if (tag=="s") {
+
+ //use strikethrough (not supported underline instead)
+ push_underline();
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+
+ } else if (tag=="url") {
+
+ //use strikethrough (not supported underline instead)
+ int end=p_bbcode.find("[",brk_end);
+ if (end==-1)
+ end=p_bbcode.length();
+ String url = p_bbcode.substr(brk_end+1,end-brk_end-1);
+ push_meta(url);
+
+ pos=brk_end+1;
+ tag_stack.push_front(tag);
+ } else if (tag.begins_with("url=")) {
+
+ String url = tag.substr(4,tag.length());
+ push_meta(url);
+ pos=brk_end+1;
+ tag_stack.push_front("url");
+ } else if (tag=="img") {
+
+ //use strikethrough (not supported underline instead)
+ int end=p_bbcode.find("[",brk_end);
+ if (end==-1)
+ end=p_bbcode.length();
+ String image = p_bbcode.substr(brk_end+1,end-brk_end-1);
+
+ Ref<Texture> texture = ResourceLoader::load(image,"Texture");
+ if (texture.is_valid())
+ add_image(texture);
+
+ pos=end;
+ tag_stack.push_front(tag);
+ } else if (tag.begins_with("color=")) {
+
+ String col = tag.substr(6,tag.length());
+ Color color;
+
+ if (col.begins_with("#"))
+ color=Color::html(col);
+ else if (col=="aqua")
+ color=Color::html("#00FFFF");
+ else if (col=="black")
+ color=Color::html("#000000");
+ else if (col=="blue")
+ color=Color::html("#0000FF");
+ else if (col=="fuchsia")
+ color=Color::html("#FF00FF");
+ else if (col=="gray" || col=="grey")
+ color=Color::html("#808080");
+ else if (col=="green")
+ color=Color::html("#008000");
+ else if (col=="lime")
+ color=Color::html("#00FF00");
+ else if (col=="maroon")
+ color=Color::html("#800000");
+ else if (col=="navy")
+ color=Color::html("#000080");
+ else if (col=="olive")
+ color=Color::html("#808000");
+ else if (col=="purple")
+ color=Color::html("#800080");
+ else if (col=="red")
+ color=Color::html("#FF0000");
+ else if (col=="silver")
+ color=Color::html("#C0C0C0");
+ else if (col=="teal")
+ color=Color::html("#008008");
+ else if (col=="white")
+ color=Color::html("#FFFFFF");
+ else if (col=="yellow")
+ color=Color::html("#FFFF00");
+ else
+ color=base_color;
+
+
+
+ push_color(color);
+ pos=brk_end+1;
+ tag_stack.push_front("color");
+
+ } else if (tag.begins_with("font=")) {
+
+ String fnt = tag.substr(5,tag.length());
+
+
+ Ref<Font> font = ResourceLoader::load(fnt,"Font");
+ if (font.is_valid())
+ push_font(font);
+ else
+ push_font(base_font);
+
+ pos=brk_end+1;
+ tag_stack.push_front("font");
+
+
+ } else {
+
+ add_text("["); //ignore
+ pos=brk_pos+1;
+
+ }
+ }
+
+ return OK;
+}
+
+
+void RichTextLabel::scroll_to_line(int p_line) {
+
+ ERR_FAIL_INDEX(p_line,lines.size());
+ _validate_line_caches();
+ vscroll->set_val(lines[p_line].height_accum_cache);
+
+
+}
+
+int RichTextLabel::get_line_count() const {
+
+ return lines.size();
+}
+
+void RichTextLabel::set_selection_enabled(bool p_enabled) {
+
+ selection.enabled=p_enabled;
+ if (!p_enabled) {
+ if (selection.active) {
+ selection.active=false;
+ update();
+ }
+ }
+
+}
+
+bool RichTextLabel::search(const String& p_string,bool p_from_selection) {
+
+ ERR_FAIL_COND_V(!selection.enabled,false);
+ Item *it=main;
+ int charidx=0;
+
+ if (p_from_selection && selection.active && selection.enabled) {
+ it=selection.to;
+ charidx=selection.to_char+1;
+ }
+
+ int line=-1;
+ while(it) {
+
+ if (it->type==ITEM_TEXT) {
+
+ ItemText *t = static_cast<ItemText*>(it);
+ int sp = t->text.find(p_string,charidx);
+ if (sp!=-1) {
+ selection.from=it;
+ selection.from_char=sp;
+ selection.to=it;
+ selection.to_char=sp+p_string.length()-1;
+ selection.active=true;
+ update();
+
+ if (line==-1) {
+
+ while(it) {
+
+ if (it->type==ITEM_NEWLINE) {
+
+ line=static_cast<ItemNewline*>(it)->line;
+ break;
+ }
+
+ it=_get_next_item(it);
+ }
+
+ if (!it)
+ line=lines.size()-1;
+ }
+
+ scroll_to_line(line-2);
+
+ return true;
+ }
+ } else if (it->type==ITEM_NEWLINE) {
+
+ line=static_cast<ItemNewline*>(it)->line;
+ }
+
+
+ it=_get_next_item(it);
+ charidx=0;
+
+ }
+
+
+
+ return false;
+
+}
+
+bool RichTextLabel::is_selection_enabled() const {
+
+ return selection.enabled;
+}
+
+
+void RichTextLabel::_bind_methods() {
+
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&RichTextLabel::_input_event);
+ ObjectTypeDB::bind_method(_MD("_scroll_changed"),&RichTextLabel::_scroll_changed);
+ ObjectTypeDB::bind_method(_MD("add_text","text"),&RichTextLabel::add_text);
+ ObjectTypeDB::bind_method(_MD("add_image","image:Texture"),&RichTextLabel::add_image);
+ ObjectTypeDB::bind_method(_MD("newline"),&RichTextLabel::add_newline);
+ ObjectTypeDB::bind_method(_MD("push_font","font"),&RichTextLabel::push_font);
+ ObjectTypeDB::bind_method(_MD("push_color","color"),&RichTextLabel::push_color);
+ ObjectTypeDB::bind_method(_MD("push_align","align"),&RichTextLabel::push_align);
+ ObjectTypeDB::bind_method(_MD("push_indent","level"),&RichTextLabel::push_indent);
+ ObjectTypeDB::bind_method(_MD("push_list","type"),&RichTextLabel::push_list);
+ ObjectTypeDB::bind_method(_MD("push_meta","data"),&RichTextLabel::push_meta);
+ ObjectTypeDB::bind_method(_MD("push_underline"),&RichTextLabel::push_underline);
+ ObjectTypeDB::bind_method(_MD("pop"),&RichTextLabel::pop);
+
+ ObjectTypeDB::bind_method(_MD("clear"),&RichTextLabel::clear);
+
+ ObjectTypeDB::bind_method(_MD("set_meta_underline","enable"),&RichTextLabel::set_meta_underline);
+ ObjectTypeDB::bind_method(_MD("is_meta_underlined"),&RichTextLabel::is_meta_underlined);
+
+ ObjectTypeDB::bind_method(_MD("set_scroll_active","active"),&RichTextLabel::set_scroll_active);
+ ObjectTypeDB::bind_method(_MD("is_scroll_active"),&RichTextLabel::is_scroll_active);
+
+ ObjectTypeDB::bind_method(_MD("set_scroll_follow","follow"),&RichTextLabel::set_scroll_follow);
+ ObjectTypeDB::bind_method(_MD("is_scroll_following"),&RichTextLabel::is_scroll_following);
+
+ ObjectTypeDB::bind_method(_MD("set_tab_size","spaces"),&RichTextLabel::set_tab_size);
+ ObjectTypeDB::bind_method(_MD("get_tab_size"),&RichTextLabel::get_tab_size);
+
+ ObjectTypeDB::bind_method(_MD("set_selection_enabled","enabled"),&RichTextLabel::set_selection_enabled);
+ ObjectTypeDB::bind_method(_MD("is_selection_enabled"),&RichTextLabel::is_selection_enabled);
+
+ ADD_SIGNAL( MethodInfo("meta_clicked",PropertyInfo(Variant::NIL,"meta")));
+
+ BIND_CONSTANT( ALIGN_LEFT );
+ BIND_CONSTANT( ALIGN_CENTER );
+ BIND_CONSTANT( ALIGN_RIGHT );
+ BIND_CONSTANT( ALIGN_FILL );
+
+ BIND_CONSTANT( LIST_NUMBERS );
+ BIND_CONSTANT( LIST_LETTERS );
+ BIND_CONSTANT( LIST_DOTS );
+
+ BIND_CONSTANT( ITEM_MAIN );
+ BIND_CONSTANT( ITEM_TEXT );
+ BIND_CONSTANT( ITEM_IMAGE );
+ BIND_CONSTANT( ITEM_NEWLINE );
+ BIND_CONSTANT( ITEM_FONT );
+ BIND_CONSTANT( ITEM_COLOR );
+ BIND_CONSTANT( ITEM_UNDERLINE );
+ BIND_CONSTANT( ITEM_ALIGN );
+ BIND_CONSTANT( ITEM_INDENT );
+ BIND_CONSTANT( ITEM_LIST );
+ BIND_CONSTANT( ITEM_META );
+
+}
+
+RichTextLabel::RichTextLabel() {
+
+
+ main = memnew( ItemMain );
+ main->index=0;
+ current=main;
+ lines.resize(1);
+ lines[0].from=main;
+ first_invalid_line=0;
+ tab_size=4;
+ default_align=ALIGN_LEFT;
+ underline_meta=true;
+
+ scroll_visible=false;
+ scroll_follow=false;
+ scroll_following=false;
+ updating_scroll=false;
+ scroll_active=true;
+ scroll_w=0;
+
+ vscroll = memnew( VScrollBar );
+ add_child(vscroll);
+ vscroll->set_step(1);
+ vscroll->set_anchor_and_margin( MARGIN_TOP, ANCHOR_BEGIN, 0);
+ vscroll->set_anchor_and_margin( MARGIN_BOTTOM, ANCHOR_END, 0);
+ vscroll->set_anchor_and_margin( MARGIN_RIGHT, ANCHOR_END, 0);
+ vscroll->connect("value_changed",this,"_scroll_changed");
+ vscroll->set_step(1);
+ vscroll->hide();
+ current_idx=1;
+
+ selection.click=NULL;
+ selection.active=false;
+ selection.enabled=false;
+
+}
+
+RichTextLabel::~RichTextLabel() {
+
+ memdelete( main );
+}
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
new file mode 100644
index 0000000000..871084a56f
--- /dev/null
+++ b/scene/gui/rich_text_label.h
@@ -0,0 +1,304 @@
+/*************************************************************************/
+/* rich_text_label.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef RICH_TEXT_LABEL_H
+#define RICH_TEXT_LABEL_H
+
+
+#include "scene/gui/scroll_bar.h"
+
+class RichTextLabel : public Control {
+
+ OBJ_TYPE( RichTextLabel, Control );
+public:
+
+ enum Align {
+
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT,
+ ALIGN_FILL
+ };
+
+ enum ListType {
+
+ LIST_NUMBERS,
+ LIST_LETTERS,
+ LIST_DOTS
+ };
+
+ enum ItemType {
+
+ ITEM_MAIN,
+ ITEM_TEXT,
+ ITEM_IMAGE,
+ ITEM_NEWLINE,
+ ITEM_FONT,
+ ITEM_COLOR,
+ ITEM_UNDERLINE,
+ ITEM_ALIGN,
+ ITEM_INDENT,
+ ITEM_LIST,
+ ITEM_META
+ };
+
+protected:
+
+ static void _bind_methods();
+private:
+
+ struct Item {
+
+ int index;
+ Item *parent;
+ ItemType type;
+ List<Item*> subitems;
+ List<Item*>::Element *E;
+
+ void _clear_children() { while (subitems.size()) { memdelete(subitems.front()->get()); subitems.pop_front(); } }
+
+ Item() { parent=NULL; E=NULL; }
+ virtual ~Item() { _clear_children(); }
+ };
+
+ struct ItemMain : public Item {
+
+ ItemMain() { type=ITEM_MAIN; }
+ };
+
+ struct ItemText : public Item {
+
+ String text;
+ ItemText() { type=ITEM_TEXT; }
+ };
+
+ struct ItemImage : public Item {
+
+ Ref<Texture> image;
+ ItemImage() { type=ITEM_IMAGE; }
+ };
+
+ struct ItemFont : public Item {
+
+ Ref<Font> font;
+ ItemFont() { type=ITEM_FONT; }
+ };
+
+ struct ItemColor : public Item {
+
+ Color color;
+ ItemColor() { type=ITEM_COLOR; }
+ };
+
+ struct ItemUnderline : public Item {
+
+ ItemUnderline() { type=ITEM_UNDERLINE; }
+ };
+
+ struct ItemMeta : public Item {
+
+ Variant meta;
+ ItemMeta() { type=ITEM_META; }
+ };
+
+ struct ItemAlign : public Item {
+
+ Align align;
+ ItemAlign() { type=ITEM_ALIGN; }
+ };
+
+ struct ItemIndent : public Item {
+
+ int level;
+ ItemIndent() { type=ITEM_INDENT; }
+ };
+
+ struct ItemList : public Item {
+
+ ListType list_type;
+ ItemList() { type=ITEM_LIST; }
+ };
+
+ struct ItemNewline : public Item {
+
+ int line;
+ ItemNewline() { type=ITEM_NEWLINE; }
+ };
+
+ ItemMain *main;
+ Item *current;
+
+ VScrollBar *vscroll;
+
+ bool scroll_visible;
+ bool scroll_follow;
+ bool scroll_following;
+ bool scroll_active;
+ int scroll_w;
+ bool updating_scroll;
+ int current_idx;
+
+ struct Line {
+
+ Item *from;
+ Vector<int> offset_caches;
+ Vector<int> height_caches;
+ int height_cache;
+ int height_accum_cache;
+
+ Line() { from=NULL; }
+ };
+
+
+
+
+ Vector<Line> lines;
+ int first_invalid_line;
+
+ int tab_size;
+ bool underline_meta;
+
+ Align default_align;
+
+ void _invalidate_current_line();
+ void _validate_line_caches();
+
+ void _add_item(Item *p_item, bool p_enter=false);
+
+
+
+
+ struct ProcessState {
+
+ int line_width;
+ };
+
+ enum ProcessMode {
+
+ PROCESS_CACHE,
+ PROCESS_DRAW,
+ PROCESS_POINTER
+ };
+
+ struct Selection {
+
+ Item *click;
+ int click_char;
+
+ Item *from;
+ int from_char;
+ Item *to;
+ int to_char;
+
+ bool active;
+ bool enabled;
+ };
+
+ Selection selection;
+
+
+
+
+
+ void _process_line(int &y, int p_width, int p_line, ProcessMode p_mode,const Ref<Font> &p_base_font,const Color &p_base_color,const Point2i& p_click_pos=Point2i(),Item **r_click_item=NULL,int *r_click_char=NULL,bool *r_outside=NULL);
+ void _find_click(const Point2i& p_click,Item **r_click_item=NULL,int *r_click_char=NULL,bool *r_outside=NULL);
+
+
+ Ref<Font> _find_font(Item *p_item);
+ int _find_margin(Item *p_item,const Ref<Font>& p_base_font);
+ Align _find_align(Item *p_item);
+ Color _find_color(Item *p_item,const Color& p_default_color);
+ bool _find_underline(Item *p_item);
+ bool _find_meta(Item *p_item,Variant *r_meta);
+
+ void _update_scroll();
+ void _scroll_changed(double);
+
+ void _input_event(InputEvent p_event);
+ Item *_get_next_item(Item* p_item);
+
+
+protected:
+ void _notification(int p_what);
+
+public:
+
+ void add_text(const String& p_text);
+ void add_image(const Ref<Texture>& p_image);
+ void add_newline();
+ void push_font(const Ref<Font>& p_font);
+ void push_color(const Color& p_color);
+ void push_underline();
+ void push_align(Align p_align);
+ void push_indent(int p_level);
+ void push_list(ListType p_list);
+ void push_meta(const Variant& p_data);
+ void pop();
+
+ void clear();
+
+ void set_offset(int p_pixel);
+
+ void set_meta_underline(bool p_underline);
+ bool is_meta_underlined() const;
+
+ void set_scroll_active(bool p_active);
+ bool is_scroll_active() const;
+
+ void set_scroll_follow(bool p_follow);
+ bool is_scroll_following() const;
+
+ void set_tab_size(int p_spaces);
+ int get_tab_size() const;
+
+
+
+ bool search(const String& p_string,bool p_from_selection=false);
+
+ void scroll_to_line(int p_line);
+ int get_line_count() const;
+
+ VScrollBar *get_v_scroll() { return vscroll; }
+
+ virtual CursorShape get_cursor_shape(const Point2& p_pos) const;
+
+ void set_selection_enabled(bool p_enabled);
+ bool is_selection_enabled() const;
+
+ Error parse_bbcode(const String& p_bbcode);
+ Error append_bbcode(const String& p_bbcode);
+
+ RichTextLabel();
+ ~RichTextLabel();
+};
+
+VARIANT_ENUM_CAST( RichTextLabel::Align );
+VARIANT_ENUM_CAST( RichTextLabel::ListType );
+VARIANT_ENUM_CAST( RichTextLabel::ItemType );
+
+#endif // RICH_TEXT_LABEL_H
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
new file mode 100644
index 0000000000..fdd30c5f60
--- /dev/null
+++ b/scene/gui/scroll_bar.cpp
@@ -0,0 +1,602 @@
+/*************************************************************************/
+/* scroll_bar.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "scroll_bar.h"
+#include "os/keyboard.h"
+#include "print_string.h"
+
+bool ScrollBar::focus_by_default=false;
+
+
+
+void ScrollBar::set_can_focus_by_default(bool p_can_focus) {
+
+ focus_by_default=p_can_focus;
+}
+
+void ScrollBar::_input_event(InputEvent p_event) {
+
+
+ switch(p_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ const InputEventMouseButton &b=p_event.mouse_button;
+ accept_event();
+
+ if (b.button_index==5 && b.pressed) {
+
+ if (orientation==VERTICAL)
+ set_val( get_val() + get_page() / 4.0 );
+ else
+ set_val( get_val() + get_page() / 4.0 );
+ accept_event();
+
+ }
+
+ if (b.button_index==4 && b.pressed) {
+
+ if (orientation==HORIZONTAL)
+ set_val( get_val() - get_page() / 4.0 );
+ else
+ set_val( get_val() - get_page() / 4.0 );
+ accept_event();
+ }
+
+ if (b.button_index!=1)
+ return;
+
+
+ if (b.pressed) {
+
+
+ double ofs = orientation==VERTICAL ? b.y : b.x ;
+ Ref<Texture> decr = get_icon("decrement");
+ Ref<Texture> incr = get_icon("increment");
+
+ double decr_size = orientation==VERTICAL ? decr->get_height() : decr->get_width();
+ double incr_size = orientation==VERTICAL ? incr->get_height() : incr->get_width();
+ double grabber_ofs = get_grabber_offset();
+ double grabber_size = get_grabber_size();
+ double total = orientation==VERTICAL ? get_size().height : get_size().width;
+
+ if (ofs < decr_size ) {
+
+ set_val( get_val() - (custom_step>=0?custom_step:get_step()) );
+ break;
+ }
+
+ if (ofs > total-incr_size ) {
+
+ set_val( get_val() + (custom_step>=0?custom_step:get_step()) );
+ break;
+ }
+
+ ofs-=decr_size;
+
+ if ( ofs < grabber_ofs ) {
+
+ set_val( get_val() - get_page() );
+ break;
+
+ }
+
+ ofs-=grabber_ofs;
+
+ if (ofs < grabber_size ) {
+
+ drag.active=true;
+ drag.pos_at_click=grabber_ofs+ofs;
+ drag.value_at_click=get_unit_value();
+ update();
+ } else {
+
+
+ set_val( get_val() + get_page() );
+ }
+
+
+ } else {
+
+ drag.active=false;
+ update();
+ }
+
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ const InputEventMouseMotion &m=p_event.mouse_motion;
+
+ accept_event();
+
+
+ if (drag.active) {
+
+ double ofs = orientation==VERTICAL ? m.y : m.x ;
+ Ref<Texture> decr = get_icon("decrement");
+
+ double decr_size = orientation==VERTICAL ? decr->get_height() : decr->get_width();
+ ofs-=decr_size;
+
+ double diff = (ofs-drag.pos_at_click) / get_area_size();
+
+ set_unit_value( drag.value_at_click + diff );
+ } else {
+
+
+ double ofs = orientation==VERTICAL ? m.y : m.x ;
+ Ref<Texture> decr = get_icon("decrement");
+ Ref<Texture> incr = get_icon("increment");
+
+ double decr_size = orientation==VERTICAL ? decr->get_height() : decr->get_width();
+ double incr_size = orientation==VERTICAL ? incr->get_height() : incr->get_width();
+ double total = orientation==VERTICAL ? get_size().height : get_size().width;
+
+ HiliteStatus new_hilite;
+
+ if (ofs < decr_size ) {
+
+ new_hilite=HILITE_DECR;
+
+ } else if (ofs > total-incr_size ) {
+
+ new_hilite=HILITE_INCR;
+
+ } else {
+
+ new_hilite=HILITE_RANGE;
+ }
+
+ if (new_hilite!=hilite) {
+
+ hilite=new_hilite;
+ update();
+
+ }
+
+ }
+ } break;
+ case InputEvent::KEY: {
+
+ const InputEventKey &k=p_event.key;
+
+ if (!k.pressed)
+ return;
+
+ switch (k.scancode) {
+
+ case KEY_LEFT: {
+
+ if (orientation!=HORIZONTAL)
+ return;
+ set_val( get_val() - (custom_step>=0?custom_step:get_step()) );
+
+ } break;
+ case KEY_RIGHT: {
+
+ if (orientation!=HORIZONTAL)
+ return;
+ set_val( get_val() + (custom_step>=0?custom_step:get_step()) );
+
+ } break;
+ case KEY_UP: {
+
+ if (orientation!=VERTICAL)
+ return;
+
+ set_val( get_val() - (custom_step>=0?custom_step:get_step()) );
+
+
+ } break;
+ case KEY_DOWN: {
+
+ if (orientation!=VERTICAL)
+ return;
+ set_val( get_val() + (custom_step>=0?custom_step:get_step()) );
+
+ } break;
+ case KEY_HOME: {
+
+ set_val( get_min() );
+
+ } break;
+ case KEY_END: {
+
+ set_val( get_max() );
+
+ } break;
+
+ } break;
+ }
+ }
+}
+
+void ScrollBar::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ RID ci = get_canvas_item();
+
+ Ref<Texture> decr = hilite==HILITE_DECR ? get_icon("decrement_hilite") : get_icon("decrement");
+ Ref<Texture> incr = hilite==HILITE_INCR ? get_icon("increment_hilite") : get_icon("increment");
+ Ref<StyleBox> bg = has_focus() ? get_stylebox("scroll_focus") : get_stylebox("scroll");
+ Ref<StyleBox> grabber = (drag.active || hilite==HILITE_RANGE) ? get_stylebox("grabber_hilite") : get_stylebox("grabber");
+
+ Point2 ofs;
+
+ VisualServer *vs = VisualServer::get_singleton();
+
+ vs->canvas_item_add_texture_rect( ci, Rect2( Point2(), decr->get_size()),decr->get_rid() );
+
+ if (orientation==HORIZONTAL)
+ ofs.x+=decr->get_width();
+ else
+ ofs.y+=decr->get_height();
+
+ Size2 area=get_size();
+
+ if (orientation==HORIZONTAL)
+ area.width-=incr->get_width()+decr->get_width();
+ else
+ area.height-=incr->get_height()+decr->get_height();
+
+ bg->draw(ci,Rect2(ofs,area));
+
+ if (orientation==HORIZONTAL)
+ ofs.width+=area.width;
+ else
+ ofs.height+=area.height;
+
+ vs->canvas_item_add_texture_rect( ci, Rect2( ofs, decr->get_size()),incr->get_rid() );
+ Rect2 grabber_rect;
+
+ if (orientation==HORIZONTAL) {
+
+ grabber_rect.size.width=get_grabber_size();
+ grabber_rect.size.height=get_size().height;
+ grabber_rect.pos.y=0;
+ grabber_rect.pos.x=get_grabber_offset()+decr->get_width()+bg->get_margin( MARGIN_LEFT );
+ } else {
+
+ grabber_rect.size.width=get_size().width;
+ grabber_rect.size.height=get_grabber_size();
+ grabber_rect.pos.y=get_grabber_offset()+decr->get_height()+bg->get_margin( MARGIN_TOP );
+ grabber_rect.pos.x=0;
+ }
+
+ grabber->draw(ci,grabber_rect);
+
+ }
+
+ if (p_what==NOTIFICATION_MOUSE_EXIT) {
+
+ hilite=HILITE_NONE;
+ update();
+ }
+}
+
+double ScrollBar::get_grabber_min_size() const {
+
+ Ref<StyleBox> grabber=get_stylebox("grabber");
+ Size2 gminsize=grabber->get_minimum_size()+grabber->get_center_size();
+ return (orientation==VERTICAL)?gminsize.height:gminsize.width;
+}
+
+double ScrollBar::get_grabber_size() const {
+
+ float range = get_max()-get_min();
+ if (range<=0)
+ return 0;
+
+ float page = (get_page()>0)? get_page() : 0;
+// if (grabber_range < get_step())
+// grabber_range=get_step();
+
+ double area_size=get_area_size();
+ double grabber_size = page / range * area_size;
+ return grabber_size+get_grabber_min_size();
+
+}
+
+double ScrollBar::get_area_size() const {
+
+ if (orientation==VERTICAL) {
+
+ double area=get_size().height;
+ area-=get_stylebox("scroll")->get_minimum_size().height;
+ area-=get_icon("increment")->get_height();
+ area-=get_icon("decrement")->get_height();
+ area-=get_grabber_min_size();
+ return area;
+
+ } else if (orientation==HORIZONTAL) {
+
+ double area=get_size().width;
+ area-=get_stylebox("scroll")->get_minimum_size().width;
+ area-=get_icon("increment")->get_width();
+ area-=get_icon("decrement")->get_width();
+ area-=get_grabber_min_size();
+ return area;
+ } else {
+
+ return 0;
+ }
+
+}
+
+double ScrollBar::get_area_offset() const {
+
+ double ofs=0;
+
+ if (orientation==VERTICAL) {
+
+ ofs+=get_stylebox("hscroll")->get_margin( MARGIN_TOP );
+ ofs+=get_icon("decrement")->get_height();
+
+ }
+
+ if (orientation==HORIZONTAL) {
+
+ ofs+=get_stylebox("hscroll")->get_margin( MARGIN_LEFT );
+ ofs+=get_icon("decrement")->get_width();
+ }
+
+ return ofs;
+}
+
+double ScrollBar::get_click_pos(const Point2& p_pos) const {
+
+
+ float pos=(orientation==VERTICAL)?p_pos.y:p_pos.x;
+ pos-=get_area_offset();
+
+ float area=get_area_size();
+ if (area==0)
+ return 0;
+ else
+ return pos/area;
+
+}
+
+double ScrollBar::get_grabber_offset() const {
+
+
+ return (get_area_size()) * get_unit_value();
+
+}
+
+
+
+Size2 ScrollBar::get_minimum_size() const {
+
+ Ref<Texture> incr = get_icon("increment");
+ Ref<Texture> decr = get_icon("decrement");
+ Ref<StyleBox> bg = get_stylebox("scroll");
+ Size2 minsize;
+
+ if (orientation==VERTICAL) {
+
+ minsize.width=MAX(incr->get_size().width,(bg->get_minimum_size()+bg->get_center_size()).width);
+ minsize.height+=incr->get_size().height;
+ minsize.height+=decr->get_size().height;
+ minsize.height+=bg->get_minimum_size().height;
+ minsize.height+=get_grabber_min_size();
+ }
+
+ if (orientation==HORIZONTAL) {
+
+ minsize.height=MAX(incr->get_size().height,(bg->get_center_size()+bg->get_minimum_size()).height);
+ minsize.width+=incr->get_size().width;
+ minsize.width+=decr->get_size().width;
+ minsize.width+=bg->get_minimum_size().width;
+ minsize.width+=get_grabber_min_size();
+ }
+
+ return minsize;
+
+}
+
+void ScrollBar::set_custom_step(float p_custom_step) {
+
+ custom_step=p_custom_step;
+}
+
+float ScrollBar::get_custom_step() const {
+
+ return custom_step;
+}
+
+
+#if 0
+
+void ScrollBar::mouse_button(const Point2& p_pos, int b.button_index,bool b.pressed,int p_modifier_mask) {
+
+ // wheel!
+
+ if (b.button_index==BUTTON_WHEEL_UP && b.pressed) {
+
+ if (orientation==VERTICAL)
+ set_val( get_val() - get_page() / 4.0 );
+ else
+ set_val( get_val() + get_page() / 4.0 );
+
+ }
+ if (b.button_index==BUTTON_WHEEL_DOWN && b.pressed) {
+
+ if (orientation==HORIZONTAL)
+ set_val( get_val() - get_page() / 4.0 );
+ else
+ set_val( get_val() + get_page() / 4.0 );
+ }
+
+ if (b.button_index!=BUTTON_LEFT)
+ return;
+
+ if (b.pressed) {
+
+ int ofs = orientation==VERTICAL ? p_pos.y : p_pos.x ;
+ int grabber_ofs = get_grabber_offset();
+ int grabber_size = get_grabber_size();
+
+ if ( ofs < grabber_ofs ) {
+
+ set_val( get_val() - get_page() );
+
+ } else if (ofs > grabber_ofs + grabber_size ) {
+
+ set_val( get_val() + get_page() );
+
+ } else {
+
+
+ drag.active=true;
+ drag.pos_at_click=get_click_pos(p_pos);
+ drag.value_at_click=get_unit_value();
+ }
+
+
+ } else {
+
+ drag.active=false;
+ }
+
+}
+void ScrollBar::mouse_motion(const Point2& p_pos, const Point2& p_rel, int b.button_index_mask) {
+
+ if (!drag.active)
+ return;
+
+ double value_ofs=drag.value_at_click+(get_click_pos(p_pos)-drag.pos_at_click);
+
+
+ value_ofs=value_ofs*( get_max() - get_min() );
+ if (value_ofs<get_min())
+ value_ofs=get_min();
+ if (value_ofs>(get_max()-get_page()))
+ value_ofs=get_max()-get_page();
+ if (get_val()==value_ofs)
+ return; //dont bother if the value is the same
+
+ set_val( value_ofs );
+
+}
+
+bool ScrollBar::key(unsigned long p_unicode, unsigned long p_scan_code,bool b.pressed,bool p_repeat,int p_modifier_mask) {
+
+ if (!b.pressed)
+ return false;
+
+ switch (p_scan_code) {
+
+ case KEY_LEFT: {
+
+ if (orientation!=HORIZONTAL)
+ return false;
+ set_val( get_val() - get_step() );
+
+ } break;
+ case KEY_RIGHT: {
+
+ if (orientation!=HORIZONTAL)
+ return false;
+ set_val( get_val() + get_step() );
+
+ } break;
+ case KEY_UP: {
+
+ if (orientation!=VERTICAL)
+ return false;
+
+ set_val( get_val() - get_step() );
+
+
+ } break;
+ case KEY_DOWN: {
+
+ if (orientation!=VERTICAL)
+ return false;
+ set_val( get_val() + get_step() );
+
+ } break;
+ case KEY_HOME: {
+
+ set_val( get_min() );
+
+ } break;
+ case KEY_END: {
+
+ set_val( get_max() );
+
+ } break;
+
+ default:
+ return false;
+
+ }
+
+ return true;
+}
+
+
+
+#endif
+
+void ScrollBar::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&ScrollBar::_input_event);
+ ObjectTypeDB::bind_method(_MD("set_custom_step","step"),&ScrollBar::set_custom_step);
+ ObjectTypeDB::bind_method(_MD("get_custom_step"),&ScrollBar::get_custom_step);
+
+ ADD_PROPERTY( PropertyInfo(Variant::REAL,"custom_step",PROPERTY_HINT_RANGE,"-1,4096"), _SCS("set_custom_step"),_SCS("get_custom_step"));
+
+}
+
+
+ScrollBar::ScrollBar(Orientation p_orientation)
+{
+
+
+ orientation=p_orientation;
+ hilite=HILITE_NONE;
+ custom_step=-1;
+
+ drag.active=false;
+
+ if (focus_by_default)
+ set_focus_mode( FOCUS_ALL );
+
+
+}
+
+
+
+ScrollBar::~ScrollBar()
+{
+}
+
+
diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h
new file mode 100644
index 0000000000..663d3ecd85
--- /dev/null
+++ b/scene/gui/scroll_bar.h
@@ -0,0 +1,109 @@
+/*************************************************************************/
+/* scroll_bar.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SCROLL_BAR_H
+#define SCROLL_BAR_H
+
+#include "scene/gui/range.h"
+
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class ScrollBar : public Range {
+
+ OBJ_TYPE( ScrollBar, Range );
+
+ enum HiliteStatus {
+ HILITE_NONE,
+ HILITE_DECR,
+ HILITE_RANGE,
+ HILITE_INCR,
+ };
+
+ static bool focus_by_default;
+
+ Orientation orientation;
+ Size2 size;
+ float custom_step;
+
+ HiliteStatus hilite;
+
+ struct Drag {
+
+ bool active;
+ float pos_at_click;
+ float value_at_click;
+ } drag;
+
+
+ double get_grabber_size() const;
+ double get_grabber_min_size() const;
+ double get_area_size() const;
+ double get_area_offset() const;
+ double get_click_pos(const Point2& p_pos) const;
+ double get_grabber_offset() const;
+
+ static void set_can_focus_by_default(bool p_can_focus);
+
+
+ void _input_event(InputEvent p_event);
+protected:
+ void _notification(int p_what);
+
+ static void _bind_methods();
+
+public:
+
+ void set_custom_step(float p_custom_step);
+ float get_custom_step() const;
+
+ virtual Size2 get_minimum_size() const;
+ ScrollBar(Orientation p_orientation=VERTICAL);
+ ~ScrollBar();
+
+};
+
+class HScrollBar : public ScrollBar {
+
+ OBJ_TYPE( HScrollBar, ScrollBar );
+public:
+
+ HScrollBar() : ScrollBar(HORIZONTAL) { set_v_size_flags(0); }
+};
+
+class VScrollBar : public ScrollBar {
+
+ OBJ_TYPE( VScrollBar, ScrollBar );
+public:
+
+ VScrollBar() : ScrollBar(VERTICAL) { set_h_size_flags(0); }
+};
+
+
+#endif
diff --git a/scene/gui/scroll_container.cpp b/scene/gui/scroll_container.cpp
new file mode 100644
index 0000000000..817833083e
--- /dev/null
+++ b/scene/gui/scroll_container.cpp
@@ -0,0 +1,427 @@
+/*************************************************************************/
+/* scroll_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "scroll_container.h"
+#include "os/os.h"
+bool ScrollContainer::clips_input() const {
+
+ return true;
+}
+
+Size2 ScrollContainer::get_minimum_size() const {
+
+ return Size2(1, 1);
+};
+
+
+void ScrollContainer::_cancel_drag() {
+ set_fixed_process(false);
+ drag_touching_deaccel=false;
+ drag_touching=false;
+ drag_speed=Vector2();
+ drag_accum=Vector2();
+ last_drag_accum=Vector2();
+ drag_from=Vector2();
+}
+
+void ScrollContainer::_input_event(const InputEvent& p_input_event) {
+
+
+ switch(p_input_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ const InputEventMouseButton &mb=p_input_event.mouse_button;
+
+ if (mb.button_index==BUTTON_WHEEL_UP && mb.pressed && v_scroll->is_visible()) {
+
+ v_scroll->set_val( v_scroll->get_val()-v_scroll->get_page()/8 );
+ }
+
+ if (mb.button_index==BUTTON_WHEEL_DOWN && mb.pressed && v_scroll->is_visible()) {
+
+ v_scroll->set_val( v_scroll->get_val()+v_scroll->get_page()/8 );
+ }
+
+ if(!OS::get_singleton()->has_touchscreen_ui_hint())
+ return;
+
+ if (mb.button_index!=BUTTON_LEFT)
+ break;
+
+ if (mb.pressed) {
+
+ if (drag_touching) {
+ set_fixed_process(false);
+ drag_touching_deaccel=false;
+ drag_touching=false;
+ drag_speed=Vector2();
+ drag_accum=Vector2();
+ last_drag_accum=Vector2();
+ drag_from=Vector2();
+ }
+
+ if (true) {
+ drag_speed=Vector2();
+ drag_accum=Vector2();
+ last_drag_accum=Vector2();
+ drag_from=Vector2(h_scroll->get_val(),v_scroll->get_val());
+ drag_touching=OS::get_singleton()->has_touchscreen_ui_hint();
+ drag_touching_deaccel=false;
+ time_since_motion=0;
+ if (drag_touching) {
+ set_fixed_process(true);
+ time_since_motion=0;
+
+ }
+ }
+
+
+ } else {
+ if (drag_touching) {
+
+ if (drag_speed==Vector2()) {
+ drag_touching_deaccel=false;
+ drag_touching=false;
+ set_fixed_process(false);
+ } else {
+
+ drag_touching_deaccel=true;
+ }
+ }
+ }
+
+
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ const InputEventMouseMotion &mm=p_input_event.mouse_motion;
+
+ if (drag_touching && ! drag_touching_deaccel) {
+
+ Vector2 motion = Vector2(mm.relative_x,mm.relative_y);
+ drag_accum-=motion;
+ Vector2 diff = drag_from+drag_accum;
+
+ if (scroll_h)
+ h_scroll->set_val(diff.x);
+ else
+ drag_accum.x=0;
+ if (scroll_v)
+ v_scroll->set_val(diff.y);
+ else
+ drag_accum.y=0;
+ time_since_motion=0;
+ }
+
+ } break;
+ }
+
+}
+
+
+void ScrollContainer::_update_scrollbar_pos() {
+
+ Size2 size = get_size();
+ Size2 hmin = h_scroll->get_combined_minimum_size();
+ Size2 vmin = v_scroll->get_combined_minimum_size();
+
+ v_scroll->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_END,vmin.width);
+ v_scroll->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0);
+ v_scroll->set_anchor_and_margin(MARGIN_TOP,ANCHOR_BEGIN,0);
+ v_scroll->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0);
+
+ h_scroll->set_anchor_and_margin(MARGIN_LEFT,ANCHOR_BEGIN,0);
+ h_scroll->set_anchor_and_margin(MARGIN_RIGHT,ANCHOR_END,0);
+ h_scroll->set_anchor_and_margin(MARGIN_TOP,ANCHOR_END,hmin.height);
+ h_scroll->set_anchor_and_margin(MARGIN_BOTTOM,ANCHOR_END,0);
+
+ h_scroll->raise();
+ v_scroll->raise();
+
+}
+
+
+void ScrollContainer::_notification(int p_what) {
+
+ if (p_what == NOTIFICATION_ENTER_SCENE || p_what == NOTIFICATION_THEME_CHANGED) {
+
+ call_deferred("_update_scrollbar_pos");
+ };
+
+
+ if (p_what==NOTIFICATION_SORT_CHILDREN) {
+
+ child_max_size = Size2(0, 0);
+ Size2 size = get_size();
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (c == h_scroll || c == v_scroll)
+ continue;
+ Size2 minsize = c->get_combined_minimum_size();
+ child_max_size.x = MAX(child_max_size.x, minsize.x);
+ child_max_size.y = MAX(child_max_size.y, minsize.y);
+
+ Rect2 r = Rect2(-scroll,minsize);
+ if (!scroll_h) {
+ r.pos.x=0;
+ r.size.width=size.width;
+ }
+ if (!scroll_v) {
+ r.pos.y=0;
+ r.size.height=size.height;
+ }
+ fit_child_in_rect(c,r);
+ }
+ update();
+ };
+
+ if (p_what == NOTIFICATION_DRAW) {
+
+ update_scrollbars();
+
+ VisualServer::get_singleton()->canvas_item_set_clip(get_canvas_item(),true);
+ }
+
+ if (p_what==NOTIFICATION_FIXED_PROCESS) {
+
+ if (drag_touching) {
+
+ if (drag_touching_deaccel) {
+
+ Vector2 pos = Vector2(h_scroll->get_val(),v_scroll->get_val());
+ pos+=drag_speed*get_fixed_process_delta_time();
+
+ bool turnoff_h=false;
+ bool turnoff_v=false;
+
+ if (pos.x<0) {
+ pos.x=0;
+ turnoff_h=true;
+ }
+ if (pos.x > (h_scroll->get_max()-h_scroll->get_page())) {
+ pos.x=h_scroll->get_max()-h_scroll->get_page();
+ turnoff_h=true;
+ }
+
+ if (pos.y<0) {
+ pos.x=0;
+ turnoff_v=true;
+ }
+ if (pos.y > (v_scroll->get_max()-v_scroll->get_page())) {
+ pos.y=v_scroll->get_max()-v_scroll->get_page();
+ turnoff_v=true;
+ }
+
+ if (scroll_h)
+ h_scroll->set_val(pos.x);
+ if (scroll_v)
+ v_scroll->set_val(pos.y);
+
+ float sgn_x = drag_speed.x<0? -1 : 1;
+ float val_x = Math::abs(drag_speed.x);
+ val_x-=1000*get_fixed_process_delta_time();
+
+ if (val_x<0) {
+ turnoff_h=true;
+ }
+
+
+ float sgn_y = drag_speed.y<0? -1 : 1;
+ float val_y = Math::abs(drag_speed.y);
+ val_y-=1000*get_fixed_process_delta_time();
+
+ if (val_y<0) {
+ turnoff_v=true;
+ }
+
+
+ drag_speed=Vector2(sgn_x*val_x,sgn_y*val_y);
+
+ if (turnoff_h && turnoff_v) {
+ set_fixed_process(false);
+ drag_touching=false;
+ drag_touching_deaccel=false;
+ }
+
+
+ } else {
+
+
+ if (time_since_motion==0 || time_since_motion>0.1) {
+
+ Vector2 diff = drag_accum - last_drag_accum;
+ last_drag_accum=drag_accum;
+ drag_speed=diff/get_fixed_process_delta_time();
+ }
+
+ time_since_motion+=get_fixed_process_delta_time();
+ }
+ }
+ }
+
+};
+
+void ScrollContainer::update_scrollbars() {
+
+ Size2 size = get_size();
+
+ Size2 hmin = h_scroll->get_combined_minimum_size();
+ Size2 vmin = v_scroll->get_combined_minimum_size();
+
+ Size2 min = child_max_size;
+
+ if (!scroll_v || min.height <= size.height - hmin.height) {
+
+ v_scroll->hide();
+ scroll.y=0;
+ } else {
+
+ v_scroll->show();
+ scroll.y=v_scroll->get_val();
+
+ }
+
+ v_scroll->set_max(min.height);
+ v_scroll->set_page(size.height - hmin.height);
+
+
+ if (!scroll_h || min.width <= size.width - vmin.width) {
+
+ h_scroll->hide();
+ scroll.x=0;
+ } else {
+
+ h_scroll->show();
+ h_scroll->set_max(min.width);
+ h_scroll->set_page(size.width - vmin.width);
+ scroll.x=h_scroll->get_val();
+ }
+}
+
+void ScrollContainer::_scroll_moved(float) {
+
+ scroll.x=h_scroll->get_val();
+ scroll.y=v_scroll->get_val();
+ queue_sort();
+
+ update();
+};
+
+
+void ScrollContainer::set_enable_h_scroll(bool p_enable) {
+
+ scroll_h=p_enable;
+ queue_sort();
+}
+
+bool ScrollContainer::is_h_scroll_enabled() const{
+
+ return scroll_h;
+}
+
+void ScrollContainer::set_enable_v_scroll(bool p_enable) {
+
+ scroll_v=p_enable;
+ queue_sort();
+}
+
+bool ScrollContainer::is_v_scroll_enabled() const{
+
+ return scroll_v;
+}
+
+int ScrollContainer::get_v_scroll() const {
+
+
+ return v_scroll->get_val();
+}
+void ScrollContainer::set_v_scroll(int p_pos) {
+
+ v_scroll->set_val(p_pos);
+ _cancel_drag();
+}
+
+int ScrollContainer::get_h_scroll() const {
+
+ return h_scroll->get_val();
+}
+void ScrollContainer::set_h_scroll(int p_pos) {
+
+ h_scroll->set_val(p_pos);
+ _cancel_drag();
+
+}
+
+
+void ScrollContainer::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_scroll_moved"),&ScrollContainer::_scroll_moved);
+ ObjectTypeDB::bind_method(_MD("_input_event"),&ScrollContainer::_input_event);
+ ObjectTypeDB::bind_method(_MD("set_enable_h_scroll","enable"),&ScrollContainer::set_enable_h_scroll);
+ ObjectTypeDB::bind_method(_MD("is_h_scroll_enabled"),&ScrollContainer::is_h_scroll_enabled);
+ ObjectTypeDB::bind_method(_MD("set_enable_v_scroll","enable"),&ScrollContainer::set_enable_v_scroll);
+ ObjectTypeDB::bind_method(_MD("is_v_scroll_enabled"),&ScrollContainer::is_v_scroll_enabled);
+ ObjectTypeDB::bind_method(_MD("_update_scrollbar_pos"),&ScrollContainer::_update_scrollbar_pos);
+ ObjectTypeDB::bind_method(_MD("set_h_scroll","val"),&ScrollContainer::set_h_scroll);
+ ObjectTypeDB::bind_method(_MD("get_h_scroll"),&ScrollContainer::get_h_scroll);
+ ObjectTypeDB::bind_method(_MD("set_v_scroll","val"),&ScrollContainer::set_v_scroll);
+ ObjectTypeDB::bind_method(_MD("get_v_scroll"),&ScrollContainer::get_v_scroll);
+
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "scroll/horizontal"), _SCS("set_enable_h_scroll"),_SCS("is_h_scroll_enabled"));
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "scroll/vertical"), _SCS("set_enable_v_scroll"),_SCS("is_v_scroll_enabled"));
+
+};
+
+ScrollContainer::ScrollContainer() {
+
+ h_scroll = memnew(HScrollBar);
+ h_scroll->set_name("_h_scroll");
+ add_child(h_scroll);
+
+ v_scroll = memnew(VScrollBar);
+ v_scroll->set_name("_v_scroll");
+ add_child(v_scroll);
+
+ h_scroll->connect("value_changed", this,"_scroll_moved");
+ v_scroll->connect("value_changed", this,"_scroll_moved");
+
+ drag_speed=Vector2();
+ drag_touching=false;
+ drag_touching_deaccel=false;
+ scroll_h=true;
+ scroll_v=true;
+
+
+};
+
diff --git a/scene/gui/scroll_container.h b/scene/gui/scroll_container.h
new file mode 100644
index 0000000000..c8b03b3671
--- /dev/null
+++ b/scene/gui/scroll_container.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* scroll_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SCROLL_CONTAINER_H
+#define SCROLL_CONTAINER_H
+
+#include "container.h"
+
+#include "scroll_bar.h"
+
+class ScrollContainer : public Container {
+
+ OBJ_TYPE(ScrollContainer, Container);
+
+ HScrollBar* h_scroll;
+ VScrollBar* v_scroll;
+
+ Size2 child_max_size;
+ Size2 scroll;
+
+ void update_scrollbars();
+
+ Vector2 drag_speed;
+ Vector2 drag_accum;
+ Vector2 drag_from;
+ Vector2 last_drag_accum;
+ float last_drag_time;
+ float time_since_motion;
+ bool drag_touching;
+ bool drag_touching_deaccel;
+ bool click_handled;
+
+ bool scroll_h;
+ bool scroll_v;
+
+ void _cancel_drag();
+
+protected:
+ Size2 get_minimum_size() const;
+
+
+ void _input_event(const InputEvent& p_input_event);
+ void _notification(int p_what);
+
+ void _scroll_moved(float);
+ static void _bind_methods();
+
+ void _update_scrollbar_pos();
+public:
+
+ int get_v_scroll() const;
+ void set_v_scroll(int p_pos);
+
+ int get_h_scroll() const;
+ void set_h_scroll(int p_pos);
+
+ void set_enable_h_scroll(bool p_enable);
+ bool is_h_scroll_enabled() const;
+
+ void set_enable_v_scroll(bool p_enable);
+ bool is_v_scroll_enabled() const;
+
+ virtual bool clips_input() const;
+ ScrollContainer();
+};
+
+#endif
diff --git a/scene/gui/separator.cpp b/scene/gui/separator.cpp
new file mode 100644
index 0000000000..aac9ac0d03
--- /dev/null
+++ b/scene/gui/separator.cpp
@@ -0,0 +1,81 @@
+/*************************************************************************/
+/* separator.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "separator.h"
+
+
+Size2 Separator::get_minimum_size() const {
+
+ Size2 ms(3,3);
+ ms[orientation]=get_constant("separation");
+ return ms;
+
+
+}
+
+void Separator::_notification(int p_what) {
+
+
+ switch(p_what) {
+
+ case NOTIFICATION_DRAW: {
+
+ Size2i size = get_size();
+ Ref<StyleBox> style = get_stylebox("separator");
+ Size2i ssize=style->get_minimum_size()+style->get_center_size();
+
+ if (orientation==VERTICAL) {
+
+ style->draw(get_canvas_item(),Rect2( (size.x-ssize.x)/2,0,ssize.x,size.y ));
+ } else {
+
+ style->draw(get_canvas_item(),Rect2( 0,(size.y-ssize.y)/2,size.x,ssize.y ));
+ }
+
+ } break;
+ }
+}
+
+Separator::Separator()
+{
+}
+
+
+Separator::~Separator()
+{
+}
+
+HSeparator::HSeparator() {
+
+ orientation=HORIZONTAL;
+}
+
+VSeparator::VSeparator() {
+
+ orientation=VERTICAL;
+}
diff --git a/scene/gui/separator.h b/scene/gui/separator.h
new file mode 100644
index 0000000000..177e69cffe
--- /dev/null
+++ b/scene/gui/separator.h
@@ -0,0 +1,75 @@
+/*************************************************************************/
+/* separator.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SEPARATOR_H
+#define SEPARATOR_H
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+#include "scene/gui/control.h"
+class Separator : public Control {
+
+ OBJ_TYPE( Separator, Control );
+
+
+protected:
+
+ Orientation orientation;
+ void _notification(int p_what);
+public:
+
+ virtual Size2 get_minimum_size() const;;
+
+ Separator();
+ ~Separator();
+
+};
+
+class VSeparator : public Separator {
+
+ OBJ_TYPE( VSeparator, Separator );
+
+public:
+
+ VSeparator();
+
+};
+
+class HSeparator : public Separator {
+
+ OBJ_TYPE( HSeparator, Separator );
+
+public:
+
+ HSeparator();
+
+};
+
+#endif
diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp
new file mode 100644
index 0000000000..68525cf464
--- /dev/null
+++ b/scene/gui/slider.cpp
@@ -0,0 +1,247 @@
+/*************************************************************************/
+/* slider.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "slider.h"
+#include "os/keyboard.h"
+
+
+Size2 Slider::get_minimum_size() const {
+
+ Ref<StyleBox> style = get_stylebox("slider");
+ Size2i ms = style->get_minimum_size()+style->get_center_size();
+ return ms;
+}
+
+void Slider::_input_event(InputEvent p_event) {
+
+
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON) {
+
+ InputEventMouseButton &mb = p_event.mouse_button;
+ if (mb.button_index==BUTTON_LEFT) {
+
+ if (mb.pressed) {
+ grab.pos=orientation==VERTICAL?mb.y:mb.x;
+ double max = orientation==VERTICAL ? get_size().height : get_size().width ;
+ set_val( ( ( (double)grab.pos / max) * ( get_max() - get_min() ) ) + get_min() );
+ grab.active=true;
+ grab.uvalue=get_unit_value();
+ } else {
+ grab.active=false;
+ }
+ } else if (mb.pressed && mb.button_index==BUTTON_WHEEL_UP) {
+
+ set_val( get_val() + get_step());
+ } else if (mb.pressed && mb.button_index==BUTTON_WHEEL_DOWN) {
+ set_val( get_val() - get_step());
+ }
+
+ } else if (p_event.type==InputEvent::MOUSE_MOTION) {
+
+ if (grab.active) {
+
+ Size2i size = get_size();
+ Ref<Texture> grabber = get_icon("grabber");
+ float motion = (orientation==VERTICAL?p_event.mouse_motion.y:p_event.mouse_motion.x) - grab.pos;
+ if (orientation==VERTICAL)
+ motion=-motion;
+ float areasize = orientation==VERTICAL?size.height - grabber->get_size().height:size.width - grabber->get_size().width;
+ if (areasize<=0)
+ return;
+ float umotion = motion / float(areasize);
+ set_unit_value( grab.uvalue + umotion );
+ }
+ } else {
+
+ if (p_event.is_action("ui_left") && p_event.is_pressed()) {
+
+ if (orientation!=HORIZONTAL)
+ return;
+ set_val( get_val() - (custom_step>=0?custom_step:get_step()) );
+ accept_event();
+ } else if (p_event.is_action("ui_right") && p_event.is_pressed()) {
+
+ if (orientation!=HORIZONTAL)
+ return;
+ set_val( get_val() + (custom_step>=0?custom_step:get_step()) );
+ accept_event();
+ } else if (p_event.is_action("ui_up") && p_event.is_pressed()) {
+
+ if (orientation!=VERTICAL)
+ return;
+
+ set_val( get_val() + (custom_step>=0?custom_step:get_step()) );
+ accept_event();
+ } else if (p_event.is_action("ui_down") && p_event.is_pressed()) {
+
+ if (orientation!=VERTICAL)
+ return;
+ set_val( get_val() - (custom_step>=0?custom_step:get_step()) );
+ accept_event();
+
+ } else if (p_event.type==InputEvent::KEY) {
+
+ const InputEventKey &k=p_event.key;
+
+ if (!k.pressed)
+ return;
+
+ switch (k.scancode) {
+
+ case KEY_HOME: {
+
+ set_val( get_min() );
+ accept_event();
+ } break;
+ case KEY_END: {
+
+ set_val( get_max() );
+ accept_event();
+
+ } break;
+
+ } ;
+ }
+ }
+
+}
+
+void Slider::_notification(int p_what) {
+
+
+ switch(p_what) {
+
+ case NOTIFICATION_MOUSE_ENTER: {
+
+ mouse_inside=true;
+ update();
+ } break;
+ case NOTIFICATION_MOUSE_EXIT: {
+
+ mouse_inside=false;
+ update();
+ } break;
+ case NOTIFICATION_DRAW: {
+ RID ci = get_canvas_item();
+ Size2i size = get_size();
+ Ref<StyleBox> style = get_stylebox("slider");
+ Ref<StyleBox> focus = get_stylebox("focus");
+ Ref<Texture> grabber = get_icon(mouse_inside||has_focus()?"grabber_hilite":"grabber");
+ Ref<Texture> tick = get_icon("tick");
+
+ if (orientation==VERTICAL) {
+
+ style->draw(ci,Rect2i(Point2i(),Size2i(style->get_minimum_size().width+style->get_center_size().width,size.height)));
+ //if (mouse_inside||has_focus())
+ // focus->draw(ci,Rect2i(Point2i(),Size2i(style->get_minimum_size().width+style->get_center_size().width,size.height)));
+ float areasize = size.height - grabber->get_size().height;
+ if (ticks>1) {
+ int tickarea = size.height - tick->get_height();
+ for(int i=0;i<ticks;i++) {
+ if( ! ticks_on_borders && (i == 0 || i + 1 == ticks) ) continue;
+ int ofs = i*tickarea/(ticks-1);
+ tick->draw(ci,Point2(0,ofs));
+ }
+
+ }
+ grabber->draw(ci,Point2i(size.width/2-grabber->get_size().width/2,size.height - get_unit_value()*areasize - grabber->get_size().height));
+ } else {
+ style->draw(ci,Rect2i(Point2i(),Size2i(size.width,style->get_minimum_size().height+style->get_center_size().height)));
+ //if (mouse_inside||has_focus())
+ // focus->draw(ci,Rect2i(Point2i(),Size2i(size.width,style->get_minimum_size().height+style->get_center_size().height)));
+
+ float areasize = size.width - grabber->get_size().width;
+ if (ticks>1) {
+ int tickarea = size.width - tick->get_width();
+ for(int i=0;i<ticks;i++) {
+ if( (! ticks_on_borders) && ( (i == 0) || ((i + 1) == ticks)) ) continue;
+ int ofs = i*tickarea/(ticks-1);
+ tick->draw(ci,Point2(ofs,0));
+ }
+
+ }
+ grabber->draw(ci,Point2i(get_unit_value()*areasize,size.height/2-grabber->get_size().height/2));
+ }
+
+ } break;
+ }
+}
+
+void Slider::set_custom_step(float p_custom_step) {
+
+ custom_step=p_custom_step;
+}
+
+float Slider::get_custom_step() const {
+
+ return custom_step;
+}
+
+void Slider::set_ticks(int p_count) {
+
+ ticks=p_count;
+ update();
+}
+
+int Slider::get_ticks() const {
+
+ return ticks;
+}
+
+bool Slider::get_ticks_on_borders() const{
+ return ticks_on_borders;
+}
+
+void Slider::set_ticks_on_borders(bool _tob){
+ ticks_on_borders = _tob;
+ update();
+}
+
+void Slider::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&Slider::_input_event);
+ ObjectTypeDB::bind_method(_MD("set_ticks","count"),&Slider::set_ticks);
+ ObjectTypeDB::bind_method(_MD("get_ticks"),&Slider::get_ticks);
+
+ ObjectTypeDB::bind_method(_MD("get_ticks_on_borders"),&Slider::get_ticks_on_borders);
+ ObjectTypeDB::bind_method(_MD("set_ticks_on_borders","ticks_on_border"),&Slider::set_ticks_on_borders);
+
+ ADD_PROPERTY( PropertyInfo( Variant::INT, "tick_count", PROPERTY_HINT_RANGE,"0,4096,1"), _SCS("set_ticks"), _SCS("get_ticks") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "ticks_on_borders" ), _SCS("set_ticks_on_borders"), _SCS("get_ticks_on_borders") );
+
+}
+
+Slider::Slider(Orientation p_orientation) {
+ orientation=p_orientation;
+ mouse_inside=false;
+ grab.active=false;
+ ticks=0;
+ custom_step=-1;
+ set_focus_mode(FOCUS_ALL);
+}
diff --git a/scene/gui/slider.h b/scene/gui/slider.h
new file mode 100644
index 0000000000..d9cb7e754b
--- /dev/null
+++ b/scene/gui/slider.h
@@ -0,0 +1,91 @@
+/*************************************************************************/
+/* slider.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SLIDER_H
+#define SLIDER_H
+
+#include "scene/gui/range.h"
+
+class Slider : public Range {
+
+ OBJ_TYPE( Slider, Range );
+
+ struct Grab {
+ int pos;
+ float uvalue;
+ bool active;
+ } grab;
+
+ int ticks;
+ bool mouse_inside;
+ Orientation orientation;
+ float custom_step;
+
+
+protected:
+
+ void _input_event(InputEvent p_event);
+ void _notification(int p_what);
+ static void _bind_methods();
+ bool ticks_on_borders;
+
+public:
+
+ virtual Size2 get_minimum_size() const;
+
+ void set_custom_step(float p_custom_step);
+ float get_custom_step() const;
+
+ void set_ticks(int p_count);
+ int get_ticks() const;
+
+ void set_ticks_on_borders(bool);
+ bool get_ticks_on_borders() const;
+
+ Slider(Orientation p_orientation=VERTICAL);
+};
+
+
+
+class HSlider : public Slider {
+
+ OBJ_TYPE( HSlider, Slider );
+public:
+
+ HSlider() : Slider(HORIZONTAL) { set_v_size_flags(0);}
+};
+
+class VSlider : public Slider {
+
+ OBJ_TYPE( VSlider, Slider );
+public:
+
+ VSlider() : Slider(VERTICAL) { set_h_size_flags(0);}
+};
+
+#endif // SLIDER_H
diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp
new file mode 100644
index 0000000000..59f4386996
--- /dev/null
+++ b/scene/gui/spin_box.cpp
@@ -0,0 +1,199 @@
+/*************************************************************************/
+/* spin_box.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "spin_box.h"
+
+
+Size2 SpinBox::get_minimum_size() const {
+
+ Size2 ms = line_edit->get_combined_minimum_size();
+ ms.width+=last_w;
+ return ms;
+}
+
+
+void SpinBox::_value_changed(double) {
+
+ String value = String::num(get_val(),Math::decimals(get_step()));
+ if (prefix!="")
+ value=prefix+" "+value;
+ if (suffix!="")
+ value+=" "+suffix;
+ line_edit->set_text(value);
+}
+
+void SpinBox::_text_entered(const String& p_string) {
+
+ //if (!p_string.is_numeric())
+ // return;
+ set_val( p_string.to_double() );
+ _value_changed(0);
+}
+
+
+LineEdit *SpinBox::get_line_edit() {
+
+ return line_edit;
+}
+
+
+void SpinBox::_input_event(const InputEvent& p_event) {
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON && p_event.mouse_button.pressed) {
+ const InputEventMouseButton &mb=p_event.mouse_button;
+
+ if (mb.doubleclick)
+ return; //ignore doubleclick
+
+ bool up = mb.y < (get_size().height/2);
+
+ switch(mb.button_index) {
+
+ case BUTTON_LEFT: {
+
+ set_val( get_val() + (up?get_step():-get_step()));
+
+ } break;
+ case BUTTON_RIGHT: {
+
+ set_val( (up?get_max():get_min()) );
+
+ } break;
+ case BUTTON_WHEEL_UP: {
+
+ set_val( get_val() + get_step() );
+ } break;
+ case BUTTON_WHEEL_DOWN: {
+
+ set_val( get_val() - get_step() );
+ } break;
+ }
+ }
+}
+
+
+void SpinBox::_line_edit_focus_exit() {
+
+ _text_entered(line_edit->get_text());
+}
+
+void SpinBox::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ Ref<Texture> updown = get_icon("updown");
+
+ int w = updown->get_width();
+ if (w!=last_w) {
+ line_edit->set_margin(MARGIN_RIGHT,w);
+ last_w=w;
+ }
+
+ RID ci = get_canvas_item();
+ Size2i size = get_size();
+
+ updown->draw(ci,Point2i(size.width-updown->get_width(),(size.height-updown->get_height())/2));
+ } else if (p_what==NOTIFICATION_FOCUS_EXIT) {
+
+
+ //_value_changed(0);
+ } else if (p_what==NOTIFICATION_ENTER_SCENE) {
+
+ _value_changed(0);
+ }
+
+}
+
+
+void SpinBox::set_suffix(const String& p_suffix) {
+
+ suffix=p_suffix;
+ _value_changed(0);
+
+}
+
+String SpinBox::get_suffix() const{
+
+ return suffix;
+}
+
+
+void SpinBox::set_prefix(const String& p_prefix) {
+
+ prefix=p_prefix;
+ _value_changed(0);
+
+}
+
+String SpinBox::get_prefix() const{
+
+ return prefix;
+}
+
+void SpinBox::set_editable(bool p_editable) {
+ line_edit->set_editable(p_editable);
+}
+
+bool SpinBox::is_editable() const {
+
+ return line_edit->is_editable();
+}
+
+void SpinBox::_bind_methods() {
+
+ //ObjectTypeDB::bind_method(_MD("_value_changed"),&SpinBox::_value_changed);
+ ObjectTypeDB::bind_method(_MD("_input_event"),&SpinBox::_input_event);
+ ObjectTypeDB::bind_method(_MD("_text_entered"),&SpinBox::_text_entered);
+ ObjectTypeDB::bind_method(_MD("set_suffix","suffix"),&SpinBox::set_suffix);
+ ObjectTypeDB::bind_method(_MD("get_suffix"),&SpinBox::get_suffix);
+ ObjectTypeDB::bind_method(_MD("set_prefix","prefix"),&SpinBox::set_prefix);
+ ObjectTypeDB::bind_method(_MD("get_prefix"),&SpinBox::get_prefix);
+ ObjectTypeDB::bind_method(_MD("set_editable","editable"),&SpinBox::set_editable);
+ ObjectTypeDB::bind_method(_MD("is_editable"),&SpinBox::is_editable);
+ ObjectTypeDB::bind_method(_MD("_line_edit_focus_exit"),&SpinBox::_line_edit_focus_exit);
+ ObjectTypeDB::bind_method(_MD("get_line_edit"),&SpinBox::get_line_edit);
+
+
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL,"editable"),_SCS("set_editable"),_SCS("is_editable"));
+ ADD_PROPERTY(PropertyInfo(Variant::STRING,"prefix"),_SCS("set_prefix"),_SCS("get_prefix"));
+ ADD_PROPERTY(PropertyInfo(Variant::STRING,"suffix"),_SCS("set_suffix"),_SCS("get_suffix"));
+
+
+}
+
+SpinBox::SpinBox() {
+
+ last_w = 0;
+ line_edit = memnew( LineEdit );
+ add_child(line_edit);
+
+ line_edit->set_area_as_parent_rect();
+ //connect("value_changed",this,"_value_changed");
+ line_edit->connect("text_entered",this,"_text_entered",Vector<Variant>(),CONNECT_DEFERRED);
+ line_edit->connect("focus_exit",this,"_line_edit_focus_exit",Vector<Variant>(),CONNECT_DEFERRED);
+}
diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h
new file mode 100644
index 0000000000..70121c9088
--- /dev/null
+++ b/scene/gui/spin_box.h
@@ -0,0 +1,75 @@
+/*************************************************************************/
+/* spin_box.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SPIN_BOX_H
+#define SPIN_BOX_H
+
+#include "scene/gui/line_edit.h"
+#include "scene/gui/range.h"
+
+class SpinBox : public Range {
+
+ OBJ_TYPE( SpinBox, Range );
+
+ LineEdit *line_edit;
+ int last_w;
+
+ void _text_entered(const String& p_string);
+ virtual void _value_changed(double);
+ String prefix;
+ String suffix;
+
+ void _line_edit_focus_exit();
+
+protected:
+
+ void _input_event(const InputEvent& p_event);
+
+
+ void _notification(int p_what);
+
+ static void _bind_methods();
+public:
+
+ LineEdit *get_line_edit();
+
+ virtual Size2 get_minimum_size() const;
+
+ void set_editable(bool p_editable);
+ bool is_editable() const;
+
+ void set_suffix(const String& p_suffix);
+ String get_suffix() const;
+
+ void set_prefix(const String& p_prefix);
+ String get_prefix() const;
+
+ SpinBox();
+};
+
+#endif // SPIN_BOX_H
diff --git a/scene/gui/split_container.cpp b/scene/gui/split_container.cpp
new file mode 100644
index 0000000000..49a7957d7c
--- /dev/null
+++ b/scene/gui/split_container.cpp
@@ -0,0 +1,453 @@
+/*************************************************************************/
+/* split_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "split_container.h"
+
+#include "margin_container.h"
+#include "label.h"
+
+
+
+struct _MinSizeCache {
+
+ int min_size;
+ bool will_stretch;
+ int final_size;
+};
+
+
+Control *SplitContainer::_getch(int p_idx) const {
+
+ int idx=0;
+
+ for(int i=0;i<get_child_count();i++) {
+ Control *c=get_child(i)->cast_to<Control>();
+ if (!c || !c->is_visible())
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ if (idx==p_idx)
+ return c;
+
+ idx++;
+ }
+
+ return NULL;
+
+}
+
+
+void SplitContainer::_resort() {
+
+ /** First pass, determine minimum size AND amount of stretchable elements */
+
+ int axis = vertical?1:0;
+
+ bool has_first=_getch(0);
+ bool has_second=_getch(1);
+
+ if (!has_first && !has_second) {
+ return;
+ } else if (! (has_first && has_second)) {
+ if (has_first)
+ fit_child_in_rect(_getch(0),Rect2(Point2(),get_size()));
+ else
+ fit_child_in_rect(_getch(1),Rect2(Point2(),get_size()));
+
+ return;
+ }
+
+
+
+ Control *first=_getch(0);
+ Control *second=_getch(1);
+
+
+ bool ratiomode=false;
+ bool expand_first_mode=false;
+
+
+
+
+ if (vertical) {
+
+ ratiomode=first->get_v_size_flags()&SIZE_EXPAND && second->get_v_size_flags()&SIZE_EXPAND;
+ expand_first_mode=first->get_v_size_flags()&SIZE_EXPAND && !(second->get_v_size_flags()&SIZE_EXPAND);
+ } else {
+
+ ratiomode=first->get_h_size_flags()&SIZE_EXPAND && second->get_h_size_flags()&SIZE_EXPAND;
+ expand_first_mode=first->get_h_size_flags()&SIZE_EXPAND && !(second->get_h_size_flags()&SIZE_EXPAND);
+ }
+
+
+ int sep=get_constant("separation");
+ Ref<Texture> g = get_icon("grabber");
+
+ if (collapsed || !dragger_visible) {
+ sep=0;
+ } else {
+ sep=MAX(sep,vertical?g->get_height():g->get_width());
+ }
+
+ int total = vertical?get_size().height:get_size().width;
+
+ total-=sep;
+
+ int minimum=0;
+
+ Size2 ms_first = first->get_combined_minimum_size();
+ Size2 ms_second = second->get_combined_minimum_size();
+
+ if (vertical) {
+
+ minimum=ms_first.height+ms_second.height;
+ } else {
+
+ minimum=ms_first.width+ms_second.width;
+ }
+
+ int available=total-minimum;
+ if (available<0)
+ available=0;
+
+
+ middle_sep=0;
+
+ if (collapsed) {
+
+
+ if (ratiomode) {
+
+ middle_sep=ms_first[axis]+available/2;
+
+
+ } else if (expand_first_mode) {
+
+ middle_sep=get_size()[axis]-ms_second[axis]-sep;
+
+ } else {
+
+ middle_sep=ms_first[axis];
+ }
+
+
+ } else if (ratiomode) {
+
+ if (expand_ofs<-(available/2))
+ expand_ofs=-(available/2);
+ else if (expand_ofs>(available/2))
+ expand_ofs=(available/2);
+
+ middle_sep=ms_first[axis]+available/2+expand_ofs;
+
+
+ } else if (expand_first_mode) {
+
+ if (expand_ofs>0)
+ expand_ofs=0;
+
+ if (expand_ofs < -available)
+ expand_ofs=-available;
+
+ middle_sep=get_size()[axis]-ms_second[axis]-sep+expand_ofs;
+
+ } else {
+
+ if (expand_ofs<0)
+ expand_ofs=0;
+
+ if (expand_ofs > available)
+ expand_ofs=available;
+
+ middle_sep=ms_first[axis]+expand_ofs;
+
+ }
+
+
+
+ if (vertical) {
+
+ fit_child_in_rect(first,Rect2(Point2(0,0),Size2(get_size().width,middle_sep)));
+ int sofs=middle_sep+sep;
+ fit_child_in_rect(second,Rect2(Point2(0,sofs),Size2(get_size().width,get_size().height-sofs)));
+
+ } else {
+
+
+ fit_child_in_rect(first,Rect2(Point2(0,0),Size2(middle_sep,get_size().height)));
+ int sofs=middle_sep+sep;
+ fit_child_in_rect(second,Rect2(Point2(sofs,0),Size2(get_size().width-sofs,get_size().height)));
+ }
+
+ update();
+ _change_notify("split/offset");
+
+}
+
+
+
+
+Size2 SplitContainer::get_minimum_size() const {
+
+
+ /* Calculate MINIMUM SIZE */
+
+ Size2i minimum;
+ int sep=get_constant("separation");
+ Ref<Texture> g = get_icon("grabber");
+ sep=dragger_visible?MAX(sep,vertical?g->get_height():g->get_width()):0;
+
+ for(int i=0;i<2;i++) {
+
+ if (!_getch(i))
+ break;
+
+ if (i==1) {
+
+ if (vertical)
+ minimum.height+=sep;
+ else
+ minimum.width+=sep;
+ }
+
+ Size2 ms = _getch(i)->get_combined_minimum_size();
+
+ if (vertical) {
+
+ minimum.height+=ms.height;
+ minimum.width=MAX(minimum.width,ms.width);
+ } else {
+
+ minimum.width+=ms.width;
+ minimum.height=MAX(minimum.height,ms.height);
+ }
+
+ }
+
+ return minimum;
+
+
+}
+
+void SplitContainer::_notification(int p_what) {
+
+ switch(p_what) {
+
+ case NOTIFICATION_SORT_CHILDREN: {
+
+ _resort();
+ } break;
+ case NOTIFICATION_MOUSE_ENTER: {
+ mouse_inside=true;
+ update();
+ } break;
+ case NOTIFICATION_MOUSE_EXIT: {
+ mouse_inside=false;
+ update();
+ } break;
+ case NOTIFICATION_DRAW: {
+
+ if (!_getch(0) || !_getch(1))
+ return;
+
+ if (collapsed || (!mouse_inside && get_constant("autohide")))
+ return;
+ int sep=dragger_visible?get_constant("separation"):0;
+ Ref<Texture> tex = get_icon("grabber");
+ Size2 size=get_size();
+ if (vertical) {
+
+ //draw_style_box( get_stylebox("bg"), Rect2(0,middle_sep,get_size().width,sep));
+ if (dragger_visible)
+ draw_texture(tex,Point2i((size.x-tex->get_width())/2,middle_sep+(sep-tex->get_height())/2));
+
+ } else {
+
+ //draw_style_box( get_stylebox("bg"), Rect2(middle_sep,0,sep,get_size().height));
+ if (dragger_visible)
+ draw_texture(tex,Point2i(middle_sep+(sep-tex->get_width())/2,(size.y-tex->get_height())/2));
+
+ }
+
+ } break;
+ }
+}
+
+void SplitContainer::_input_event(const InputEvent& p_event) {
+
+ if (collapsed || !_getch(0) || !_getch(1) || !dragger_visible)
+ return;
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON) {
+
+ const InputEventMouseButton &mb=p_event.mouse_button;
+
+ if (mb.button_index==BUTTON_LEFT) {
+
+ if (mb.pressed) {
+ int sep=get_constant("separation");
+
+ if (vertical) {
+
+
+ if (mb.y > middle_sep && mb.y < middle_sep+sep) {
+ dragging=true;
+ drag_from=mb.y;
+ drag_ofs=expand_ofs;
+ }
+ } else {
+
+ if (mb.x > middle_sep && mb.x < middle_sep+sep) {
+ dragging=true;
+ drag_from=mb.x;
+ drag_ofs=expand_ofs;
+ }
+ }
+ } else {
+
+ dragging=false;
+ }
+
+ }
+ }
+
+ if (p_event.type==InputEvent::MOUSE_MOTION) {
+
+ const InputEventMouseMotion &mm=p_event.mouse_motion;
+
+ if (dragging) {
+
+ expand_ofs=drag_ofs+((vertical?mm.y:mm.x)-drag_from);
+ queue_sort();
+ }
+ }
+
+}
+
+Control::CursorShape SplitContainer::get_cursor_shape(const Point2& p_pos) {
+
+ if (collapsed)
+ return Control::get_cursor_shape(p_pos);
+
+ if (dragging)
+ return (vertical?CURSOR_VSIZE:CURSOR_HSIZE);
+
+ int sep=get_constant("separation");
+
+ if (vertical) {
+
+
+ if (p_pos.y > middle_sep && p_pos.y < middle_sep+sep) {
+ return CURSOR_VSIZE;
+ }
+ } else {
+
+ if (p_pos.x > middle_sep && p_pos.x < middle_sep+sep) {
+ return CURSOR_HSIZE;
+ }
+ }
+
+ return Control::get_cursor_shape(p_pos);
+
+}
+
+void SplitContainer::set_split_offset(int p_offset) {
+
+ if (expand_ofs==p_offset)
+ return;
+ expand_ofs=p_offset;
+ queue_sort();
+}
+
+int SplitContainer::get_split_offset() const {
+
+ return expand_ofs;
+}
+
+void SplitContainer::set_collapsed(bool p_collapsed) {
+
+ if (collapsed==p_collapsed)
+ return;
+ collapsed=p_collapsed;
+ queue_sort();
+
+}
+
+void SplitContainer::set_dragger_visible(bool p_true) {
+
+ dragger_visible=p_true;
+ queue_sort();
+ update();
+}
+
+bool SplitContainer::is_dragger_visible() const{
+
+
+ return dragger_visible;
+}
+
+bool SplitContainer::is_collapsed() const {
+
+
+ return collapsed;
+}
+
+
+void SplitContainer::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&SplitContainer::_input_event);
+ ObjectTypeDB::bind_method(_MD("set_split_offset","offset"),&SplitContainer::set_split_offset);
+ ObjectTypeDB::bind_method(_MD("get_split_offset"),&SplitContainer::get_split_offset);
+
+ ObjectTypeDB::bind_method(_MD("set_collapsed","collapsed"),&SplitContainer::set_collapsed);
+ ObjectTypeDB::bind_method(_MD("is_collapsed"),&SplitContainer::is_collapsed);
+
+ ObjectTypeDB::bind_method(_MD("set_dragger_visible","visible"),&SplitContainer::set_dragger_visible);
+ ObjectTypeDB::bind_method(_MD("is_dragger_visible"),&SplitContainer::is_dragger_visible);
+
+
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"split/offset"),_SCS("set_split_offset"),_SCS("get_split_offset"));
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"split/collapsed"),_SCS("set_collapsed"),_SCS("is_collapsed"));
+ ADD_PROPERTY( PropertyInfo(Variant::INT,"split/dragger_visible"),_SCS("set_dragger_visible"),_SCS("is_dragger_visible"));
+
+}
+
+SplitContainer::SplitContainer(bool p_vertical) {
+
+
+ mouse_inside=false;
+ expand_ofs=0;
+ middle_sep=0;
+ vertical=p_vertical;
+ dragging=false;
+ collapsed=false;
+ dragger_visible=true;
+}
+
+
diff --git a/scene/gui/split_container.h b/scene/gui/split_container.h
new file mode 100644
index 0000000000..4065b7818c
--- /dev/null
+++ b/scene/gui/split_container.h
@@ -0,0 +1,99 @@
+/*************************************************************************/
+/* split_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef SPLIT_CONTAINER_H
+#define SPLIT_CONTAINER_H
+
+#include "scene/gui/container.h"
+
+
+class SplitContainer : public Container {
+
+ OBJ_TYPE(SplitContainer,Container);
+
+ bool vertical;
+ int expand_ofs;
+ int middle_sep;
+ bool dragging;
+ int drag_from;
+ int drag_ofs;
+ bool collapsed;
+ bool dragger_visible;
+ bool mouse_inside;
+
+
+ Control *_getch(int p_idx) const;
+
+ void _resort();
+protected:
+
+
+ void _input_event(const InputEvent& p_event);
+ void _notification(int p_what);
+ static void _bind_methods();
+public:
+
+
+
+ void set_split_offset(int p_offset);
+ int get_split_offset() const;
+
+ void set_collapsed(bool p_collapsed);
+ bool is_collapsed() const;
+
+ void set_dragger_visible(bool p_true);
+ bool is_dragger_visible() const;
+
+ virtual CursorShape get_cursor_shape(const Point2& p_pos=Point2i());
+
+ virtual Size2 get_minimum_size() const;
+
+ SplitContainer(bool p_vertical=false);
+};
+
+
+class HSplitContainer : public SplitContainer {
+
+ OBJ_TYPE(HSplitContainer,SplitContainer);
+
+public:
+
+ HSplitContainer() : SplitContainer(false) {set_default_cursor_shape(CURSOR_HSPLIT);}
+};
+
+
+class VSplitContainer : public SplitContainer {
+
+ OBJ_TYPE(VSplitContainer,SplitContainer);
+
+public:
+
+ VSplitContainer() : SplitContainer(true) {set_default_cursor_shape(CURSOR_VSPLIT);}
+};
+
+#endif // SPLIT_CONTAINER_H
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
new file mode 100644
index 0000000000..3c95b102d7
--- /dev/null
+++ b/scene/gui/tab_container.cpp
@@ -0,0 +1,640 @@
+/*************************************************************************/
+/* tab_container.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "tab_container.h"
+
+#include "message_queue.h"
+
+
+
+int TabContainer::_get_top_margin() const {
+
+ Ref<StyleBox> tab_bg = get_stylebox("tab_bg");
+ Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
+ Ref<Font> font = get_font("font");
+
+ int h = MAX( tab_bg->get_minimum_size().height,tab_fg->get_minimum_size().height);
+
+// h+=MIN( get_constant("label_valign_fg"), get_constant("label_valign_bg") );
+
+ int ch = font->get_height();;
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (!c->has_meta("_tab_icon"))
+ continue;
+
+ Ref<Texture> tex = c->get_meta("_tab_icon");
+ if (!tex.is_valid())
+ continue;
+ ch = MAX( ch, tex->get_size().height );
+ }
+
+ h+=ch;
+
+ return h;
+
+}
+
+
+
+void TabContainer::_input_event(const InputEvent& p_event) {
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON &&
+ p_event.mouse_button.pressed &&
+ p_event.mouse_button.button_index==BUTTON_LEFT) {
+
+ // clicks
+ Point2 pos( p_event.mouse_button.x, p_event.mouse_button.y );
+
+ int top_margin = _get_top_margin();
+ if (pos.y>top_margin)
+ return; // no click (too far down)
+
+ if (pos.x<tabs_ofs_cache)
+ return; // no click (too far left)
+
+ Ref<StyleBox> tab_bg = get_stylebox("tab_bg");
+ Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
+ Ref<Font> font = get_font("font");
+ Ref<Texture> incr = get_icon("increment");
+ Ref<Texture> decr = get_icon("decrement");
+
+ pos.x-=tabs_ofs_cache;
+
+ int idx=0;
+ int found=-1;
+ bool rightroom=false;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ if (idx<tab_display_ofs) {
+ idx++;
+ continue;
+ }
+
+ if (idx>last_tab_cache) {
+ rightroom=true;
+ break;
+ }
+
+ String s = c->has_meta("_tab_title")?String(XL_MESSAGE(String(c->get_meta("_tab_title")))):String(c->get_name());
+ int tab_width=font->get_string_size(s).width;
+
+ if (c->has_meta("_tab_icon")) {
+ Ref<Texture> icon = c->get_meta("_tab_icon");
+ if (icon.is_valid()) {
+ tab_width+=icon->get_width();
+ if (s!="")
+ tab_width+=get_constant("hseparation");
+
+ }
+ }
+
+ if (idx==current) {
+
+ tab_width+=tab_fg->get_minimum_size().width;
+ } else {
+ tab_width+=tab_bg->get_minimum_size().width;
+ }
+
+ if (pos.x < tab_width) {
+
+ found=idx;
+ break;
+ }
+
+ pos.x-=tab_width;
+ idx++;
+ }
+
+ if (buttons_visible_cache) {
+
+ if (p_event.mouse_button.x>get_size().width-incr->get_width()) {
+ if (rightroom) {
+ tab_display_ofs+=1;
+ update();
+ }
+ } else if (p_event.mouse_button.x>get_size().width-incr->get_width()-decr->get_width()) {
+
+ if (tab_display_ofs>0) {
+ tab_display_ofs-=1;
+ update();
+ }
+
+ }
+ }
+
+
+ if (found!=-1) {
+
+ set_current_tab(found);
+ }
+ }
+
+}
+
+void TabContainer::_notification(int p_what) {
+
+
+ switch(p_what) {
+
+
+ case NOTIFICATION_DRAW: {
+
+ RID ci = get_canvas_item();
+ Ref<StyleBox> panel = get_stylebox("panel");
+ Size2 size = get_size();
+
+ if (!tabs_visible) {
+
+ panel->draw(ci, Rect2( 0, 0, size.width, size.height));
+ return;
+ }
+
+
+
+ Ref<StyleBox> tab_bg = get_stylebox("tab_bg");
+ Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
+ Ref<Texture> incr = get_icon("increment");
+ Ref<Texture> incr_hl = get_icon("increment_hilite");
+ Ref<Texture> decr = get_icon("decrement");
+ Ref<Texture> decr_hl = get_icon("decrement_hilite");
+ Ref<Font> font = get_font("font");
+ Color color_fg = get_color("font_color_fg");
+ Color color_bg = get_color("font_color_bg");
+
+ int side_margin = get_constant("side_margin");
+ int top_margin = _get_top_margin();
+
+ int label_valign_fg = get_constant("label_valign_fg");
+ int label_valign_bg = get_constant("label_valign_bg");
+
+
+ Size2 top_size = Size2( size.width, top_margin );
+
+
+ int w=0;
+ int idx=0;
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+
+ String s = c->has_meta("_tab_title")?String(XL_MESSAGE(String(c->get_meta("_tab_title")))):String(c->get_name());
+ w+=font->get_string_size(s).width;
+ if (c->has_meta("_tab_icon")) {
+ Ref<Texture> icon = c->get_meta("_tab_icon");
+ if (icon.is_valid()) {
+ w+=icon->get_width();
+ if (s!="")
+ w+=get_constant("hseparation");
+
+ }
+ }
+
+ if (idx==current) {
+
+ w+=tab_fg->get_minimum_size().width;
+ } else {
+ w+=tab_bg->get_minimum_size().width;
+ }
+
+ idx++;
+ }
+
+
+ int ofs;
+ int limit=get_size().width;
+
+
+ if (w<=get_size().width) {
+ switch(align) {
+
+ case ALIGN_LEFT: ofs = side_margin; break;
+ case ALIGN_CENTER: ofs = (int(top_size.width) - w)/2; break;
+ case ALIGN_RIGHT: ofs = int(top_size.width) - w - side_margin; break;
+ };
+
+ tab_display_ofs=0;
+ buttons_visible_cache=false;
+ } else {
+
+ ofs=0;
+ limit-=incr->get_width()+decr->get_width();
+ buttons_visible_cache=true;
+ }
+
+
+ tabs_ofs_cache=ofs;
+ last_tab_cache=-1;
+ idx=0;
+ bool notdone=false;
+
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ if (idx<tab_display_ofs) {
+ idx++;
+ continue;
+ }
+
+ String s = c->has_meta("_tab_title")?String(c->get_meta("_tab_title")):String(c->get_name());
+ int w=font->get_string_size(s).width;
+ Ref<Texture> icon;
+ if (c->has_meta("_tab_icon")) {
+ icon = c->get_meta("_tab_icon");
+ if (icon.is_valid()) {
+
+ w+=icon->get_width();
+ if (s!="")
+ w+=get_constant("hseparation");
+
+ }
+ }
+
+
+ Ref<StyleBox> sb;
+ int va;
+ Color col;
+
+ if (idx==current) {
+
+ sb=tab_fg;
+ va=label_valign_fg;
+ col=color_fg;
+ } else {
+ sb=tab_bg;
+ va=label_valign_bg;
+ col=color_bg;
+ }
+
+
+ Size2i sb_ms = sb->get_minimum_size();
+ Rect2 sb_rect = Rect2( ofs, 0, w+sb_ms.width, top_margin);
+
+ if (sb_ms.width+w+ofs > limit) {
+ notdone=true;
+ break;
+ }
+ sb->draw(ci, sb_rect );
+
+ Point2i lpos = sb_rect.pos;
+ lpos.x+=sb->get_margin(MARGIN_LEFT);
+ if (icon.is_valid()) {
+
+ icon->draw(ci, Point2i( lpos.x, sb->get_margin(MARGIN_TOP)+((sb_rect.size.y-sb_ms.y)-icon->get_height())/2 ) );
+ if (s!="")
+ lpos.x+=icon->get_width()+get_constant("hseparation");
+
+ }
+
+ font->draw(ci, Point2i( lpos.x, sb->get_margin(MARGIN_TOP)+((sb_rect.size.y-sb_ms.y)-font->get_height())/2+font->get_ascent() ), s, col );
+ ofs+=sb_ms.x+w;
+ last_tab_cache=idx;
+
+ /*
+ int sb_mw = sb->get_minimum_size().width;
+ int font_ofs = sb_mw / 2;
+
+ Rect2i rect =Rect2( ofs, 0, w+sb_mw, top_margin);
+
+ rect.size
+ sb->draw(ci,rect);
+ rect.y+=va;
+ rect.height+=va;
+ int font_y = (rect.height - font->get_height())/2;
+
+ font->draw(ci, Point2( ofs+font_ofs, va+font_y ), s, col );
+
+*/
+ idx++;
+ }
+
+
+ if (buttons_visible_cache) {
+
+ int vofs = (top_margin-incr->get_height())/2;
+ decr->draw(ci,Point2(limit,vofs),Color(1,1,1,tab_display_ofs==0?0.5:1.0));
+ incr->draw(ci,Point2(limit+incr->get_width(),vofs),Color(1,1,1,notdone?1.0:0.5));
+ }
+
+ panel->draw(ci, Rect2( 0, top_size.height, size.width, size.height-top_size.height));
+
+ } break;
+ }
+}
+
+void TabContainer::_child_renamed_callback() {
+
+ update();
+}
+
+void TabContainer::add_child_notify(Node *p_child) {
+
+
+ Control *c = p_child->cast_to<Control>();
+ if (!c)
+ return;
+ if (c->is_set_as_toplevel())
+ return;
+
+ bool first=false;
+
+ if (get_tab_count()!=1)
+ c->hide();
+ else {
+ c->show();
+ //call_deferred("set_current_tab",0);
+ first=true;
+ current=0;
+ }
+ c->set_area_as_parent_rect();
+ if (tabs_visible)
+ c->set_margin(MARGIN_TOP,_get_top_margin());
+ Ref<StyleBox> sb = get_stylebox("panel");
+ for(int i=0;i<4;i++)
+ c->set_margin(Margin(i),c->get_margin(Margin(i))+sb->get_margin(Margin(i)));
+
+
+ update();
+ p_child->connect("renamed", this,"_child_renamed_callback");
+ if(first)
+ emit_signal("tab_changed",current);
+}
+
+int TabContainer::get_tab_count() const {
+
+ int count=0;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ count++;
+ }
+
+ return count;
+}
+
+
+void TabContainer::set_current_tab(int p_current) {
+
+ ERR_FAIL_INDEX( p_current, get_tab_count() );
+
+ //printf("DEBUG %p: set_current_tab to %i\n", this, p_current);
+ current=p_current;
+
+ int idx=0;
+
+ Ref<StyleBox> sb=get_stylebox("panel");
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (idx==current) {
+ c->show();
+ c->set_area_as_parent_rect();
+ if (tabs_visible)
+ c->set_margin(MARGIN_TOP,_get_top_margin());
+ for(int i=0;i<4;i++)
+ c->set_margin(Margin(i),c->get_margin(Margin(i))+sb->get_margin(Margin(i)));
+
+
+ } else
+ c->hide();
+ idx++;
+ }
+
+ _change_notify("current_tab");
+ emit_signal("tab_changed",current);
+ update();
+}
+
+int TabContainer::get_current_tab() const {
+
+ return current;
+}
+
+
+void TabContainer::remove_child_notify(Node *p_child) {
+
+ int tc = get_tab_count();
+// bool set_curent=false;
+ if (current==tc-1) {
+ current--;
+ if (current<0)
+ current=0;
+ else {
+ call_deferred("set_current_tab",current);
+ }
+ }
+
+ p_child->disconnect("renamed", this,"_child_renamed_callback");
+
+ update();
+}
+
+void TabContainer::set_tab_align(TabAlign p_align) {
+
+ ERR_FAIL_INDEX(p_align,3);
+ align=p_align;
+ update();
+
+ _change_notify("tab_align");
+}
+TabContainer::TabAlign TabContainer::get_tab_align() const {
+
+ return align;
+}
+
+void TabContainer::set_tabs_visible(bool p_visibe) {
+
+ if (p_visibe==tabs_visible)
+ return;
+
+ tabs_visible=p_visibe;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (p_visibe)
+ c->set_margin(MARGIN_TOP,_get_top_margin());
+ else
+ c->set_margin(MARGIN_TOP,0);
+
+ }
+ update();
+}
+
+bool TabContainer::are_tabs_visible() const {
+
+ return tabs_visible;
+
+}
+
+
+Control *TabContainer::_get_tab(int p_idx) const {
+
+ int idx=0;
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+ if (idx==p_idx)
+ return c;
+ idx++;
+
+ }
+ return NULL;
+
+}
+
+void TabContainer::set_tab_title(int p_tab,const String& p_title) {
+
+ Control *child = _get_tab(p_tab);
+ ERR_FAIL_COND(!child);
+ child->set_meta("_tab_name",p_title);
+
+}
+
+String TabContainer::get_tab_title(int p_tab) const{
+
+ Control *child = _get_tab(p_tab);
+ ERR_FAIL_COND_V(!child,"");
+ if (child->has_meta("_tab_name"))
+ return child->get_meta("_tab_name");
+ else
+ return child->get_name();
+
+}
+
+void TabContainer::set_tab_icon(int p_tab,const Ref<Texture>& p_icon){
+
+ Control *child = _get_tab(p_tab);
+ ERR_FAIL_COND(!child);
+ child->set_meta("_tab_icon",p_icon);
+
+}
+Ref<Texture> TabContainer::get_tab_icon(int p_tab) const{
+
+ Control *child = _get_tab(p_tab);
+ ERR_FAIL_COND_V(!child,Ref<Texture>());
+ if (child->has_meta("_tab_icon"))
+ return child->get_meta("_tab_icon");
+ else
+ return Ref<Texture>();
+}
+
+void TabContainer::get_translatable_strings(List<String> *p_strings) const {
+
+ for(int i=0;i<get_child_count();i++) {
+
+ Control *c = get_child(i)->cast_to<Control>();
+ if (!c)
+ continue;
+ if (c->is_set_as_toplevel())
+ continue;
+
+ if (!c->has_meta("_tab_name"))
+ continue;
+
+ String name = c->get_meta("_tab_name");
+
+ if (name!="")
+ p_strings->push_back(name);
+ }
+}
+
+
+void TabContainer::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&TabContainer::_input_event);
+ ObjectTypeDB::bind_method(_MD("get_tab_count"),&TabContainer::get_tab_count);
+ ObjectTypeDB::bind_method(_MD("set_current_tab","tab_idx"),&TabContainer::set_current_tab);
+ ObjectTypeDB::bind_method(_MD("get_current_tab"),&TabContainer::get_current_tab);
+ ObjectTypeDB::bind_method(_MD("set_tab_align","align"),&TabContainer::set_tab_align);
+ ObjectTypeDB::bind_method(_MD("get_tab_align"),&TabContainer::get_tab_align);
+ ObjectTypeDB::bind_method(_MD("set_tabs_visible","visible"),&TabContainer::set_tabs_visible);
+ ObjectTypeDB::bind_method(_MD("are_tabs_visible"),&TabContainer::are_tabs_visible);
+ ObjectTypeDB::bind_method(_MD("set_tab_title","tab_idx","title"),&TabContainer::set_tab_title);
+ ObjectTypeDB::bind_method(_MD("get_tab_title","tab_idx"),&TabContainer::get_tab_title);
+ ObjectTypeDB::bind_method(_MD("set_tab_icon","tab_idx","icon:Texture"),&TabContainer::set_tab_icon);
+ ObjectTypeDB::bind_method(_MD("get_tab_icon:Texture","tab_idx"),&TabContainer::get_tab_icon);
+
+ ObjectTypeDB::bind_method(_MD("_child_renamed_callback"),&TabContainer::_child_renamed_callback);
+
+ ADD_SIGNAL(MethodInfo("tab_changed",PropertyInfo(Variant::INT,"tab")));
+
+ ADD_PROPERTY( PropertyInfo(Variant::INT, "tab_align", PROPERTY_HINT_ENUM,"Left,Center,Right"), _SCS("set_tab_align"), _SCS("get_tab_align") );
+ ADD_PROPERTY( PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE,"-1,4096,1",PROPERTY_USAGE_EDITOR), _SCS("set_current_tab"), _SCS("get_current_tab") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL, "tabs_visible"), _SCS("set_tabs_visible"), _SCS("are_tabs_visible") );
+
+}
+
+TabContainer::TabContainer() {
+
+ tab_display_ofs=0;
+ buttons_visible_cache=false;
+ tabs_ofs_cache=0;
+ current=0;
+ mouse_x_cache=0;
+ align=ALIGN_CENTER;
+ tabs_visible=true;
+
+}
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
new file mode 100644
index 0000000000..d5b6a2b503
--- /dev/null
+++ b/scene/gui/tab_container.h
@@ -0,0 +1,96 @@
+/*************************************************************************/
+/* tab_container.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TAB_CONTAINER_H
+#define TAB_CONTAINER_H
+
+
+#include "scene/gui/control.h"
+
+class TabContainer : public Control {
+
+ OBJ_TYPE( TabContainer, Control );
+public:
+
+ enum TabAlign {
+
+ ALIGN_LEFT,
+ ALIGN_CENTER,
+ ALIGN_RIGHT
+ };
+private:
+
+ int mouse_x_cache;
+ int tab_display_ofs;
+ int tabs_ofs_cache;
+ int last_tab_cache;
+ int current;
+ bool tabs_visible;
+ bool buttons_visible_cache;
+ TabAlign align;
+ Control *_get_tab(int idx) const;
+ int _get_top_margin() const;
+
+protected:
+
+ void _child_renamed_callback();
+ void _input_event(const InputEvent& p_event);
+ void _notification(int p_what);
+ virtual void add_child_notify(Node *p_child);
+ virtual void remove_child_notify(Node *p_child);
+
+ static void _bind_methods();
+
+public:
+
+
+ void set_tab_align(TabAlign p_align);
+ TabAlign get_tab_align() const;
+
+ void set_tabs_visible(bool p_visibe);
+ bool are_tabs_visible() const;
+
+ void set_tab_title(int p_tab,const String& p_title);
+ String get_tab_title(int p_tab) const;
+
+ void set_tab_icon(int p_tab,const Ref<Texture>& p_icon);
+ Ref<Texture> get_tab_icon(int p_tab) const;
+
+ int get_tab_count() const;
+ void set_current_tab(int p_current);
+ int get_current_tab() const;
+
+ virtual void get_translatable_strings(List<String> *p_strings) const;
+
+ TabContainer();
+};
+
+
+VARIANT_ENUM_CAST( TabContainer::TabAlign );
+
+#endif // TAB_CONTAINER_H
diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp
new file mode 100644
index 0000000000..b7c857b9c7
--- /dev/null
+++ b/scene/gui/tabs.cpp
@@ -0,0 +1,293 @@
+/*************************************************************************/
+/* tabs.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "tabs.h"
+
+#include "message_queue.h"
+
+Size2 Tabs::get_minimum_size() const {
+
+
+ Ref<StyleBox> tab_bg = get_stylebox("tab_bg");
+ Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
+ Ref<Font> font = get_font("font");
+
+ Size2 ms(0, MAX( tab_bg->get_minimum_size().height,tab_fg->get_minimum_size().height)+font->get_height() );
+
+// h+=MIN( get_constant("label_valign_fg"), get_constant("label_valign_bg") );
+
+ for(int i=0;i<tabs.size();i++) {
+
+ Ref<Texture> tex = tabs[i].icon;
+ if (tex.is_valid()) {
+ ms.height = MAX( ms.height, tex->get_size().height );
+ if (tabs[i].text!="")
+ ms.width+=get_constant("hseparation");
+
+ }
+ ms.width+=font->get_string_size(tabs[i].text).width;
+ if (current==i)
+ ms.width+=tab_fg->get_minimum_size().width;
+ else
+ ms.width+=tab_bg->get_minimum_size().width;
+
+
+ }
+
+ return ms;
+}
+
+
+
+void Tabs::_input_event(const InputEvent& p_event) {
+
+ if (p_event.type==InputEvent::MOUSE_BUTTON &&
+ p_event.mouse_button.pressed &&
+ p_event.mouse_button.button_index==BUTTON_LEFT) {
+
+ // clicks
+ Point2 pos( p_event.mouse_button.x, p_event.mouse_button.y );
+
+ int found=-1;
+ for(int i=0;i<tabs.size();i++) {
+
+ int ofs=tabs[i].ofs_cache;
+
+ if (pos.x < ofs) {
+
+ found=i;
+ break;
+ }
+ }
+
+
+ if (found!=-1) {
+
+ set_current_tab(found);
+ }
+ }
+
+}
+
+void Tabs::_notification(int p_what) {
+
+
+ switch(p_what) {
+
+
+ case NOTIFICATION_DRAW: {
+
+ RID ci = get_canvas_item();
+
+ Ref<StyleBox> tab_bg = get_stylebox("tab_bg");
+ Ref<StyleBox> tab_fg = get_stylebox("tab_fg");
+ Ref<Font> font = get_font("font");
+ Color color_fg = get_color("font_color_fg");
+ Color color_bg = get_color("font_color_bg");
+
+ int h = get_size().height;
+
+ int label_valign_fg = get_constant("label_valign_fg");
+ int label_valign_bg = get_constant("label_valign_bg");
+
+ int w=0;
+
+ for(int i=0;i<tabs.size();i++) {
+
+
+ String s = tabs[i].text;
+ int lsize=0;
+ int slen=font->get_string_size(s).width;;
+ lsize+=slen;
+
+ Ref<Texture> icon;
+ if (tabs[i].icon.is_valid()) {
+ Ref<Texture> icon = tabs[i].icon;
+ if (icon.is_valid()) {
+ lsize+=icon->get_width();
+ if (s!="")
+ lsize+=get_constant("hseparation");
+
+ }
+ }
+
+
+ Ref<StyleBox> sb;
+ int va;
+ Color col;
+
+ if (i==current) {
+
+ sb=tab_fg;
+ va=label_valign_fg;
+ col=color_fg;
+ } else {
+ sb=tab_bg;
+ va=label_valign_bg;
+ col=color_bg;
+ }
+
+
+ Size2i sb_ms = sb->get_minimum_size();
+ Rect2 sb_rect = Rect2( w, 0, lsize+sb_ms.width, h);
+ sb->draw(ci, sb_rect );
+
+ w+=sb->get_margin(MARGIN_LEFT);
+
+ if (icon.is_valid()) {
+
+ icon->draw(ci, Point2i( w, sb->get_margin(MARGIN_TOP)+((sb_rect.size.y-sb_ms.y)-icon->get_height())/2 ) );
+ if (s!="")
+ w+=icon->get_width()+get_constant("hseparation");
+
+ }
+
+ font->draw(ci, Point2i( w, sb->get_margin(MARGIN_TOP)+((sb_rect.size.y-sb_ms.y)-font->get_height())/2+font->get_ascent() ), s, col );
+
+ w+=slen+sb->get_margin(MARGIN_RIGHT);
+
+ tabs[i].ofs_cache=w;
+
+ }
+
+
+ } break;
+ }
+}
+
+int Tabs::get_tab_count() const {
+
+
+ return tabs.size();
+}
+
+
+void Tabs::set_current_tab(int p_current) {
+
+ ERR_FAIL_INDEX( p_current, get_tab_count() );
+
+ //printf("DEBUG %p: set_current_tab to %i\n", this, p_current);
+ current=p_current;
+
+ _change_notify("current_tab");
+ emit_signal("tab_changed",current);
+ update();
+}
+
+int Tabs::get_current_tab() const {
+
+ return current;
+}
+
+
+void Tabs::set_tab_title(int p_tab,const String& p_title) {
+
+ ERR_FAIL_INDEX(p_tab,tabs.size());
+ tabs[p_tab].text=p_title;
+ update();
+ minimum_size_changed();
+
+}
+
+String Tabs::get_tab_title(int p_tab) const{
+
+ ERR_FAIL_INDEX_V(p_tab,tabs.size(),"");
+ return tabs[p_tab].text;
+
+
+}
+
+void Tabs::set_tab_icon(int p_tab,const Ref<Texture>& p_icon){
+
+ ERR_FAIL_INDEX(p_tab,tabs.size());
+ tabs[p_tab].icon=p_icon;
+ update();
+ minimum_size_changed();
+
+}
+Ref<Texture> Tabs::get_tab_icon(int p_tab) const{
+
+ ERR_FAIL_INDEX_V(p_tab,tabs.size(),Ref<Texture>());
+ return tabs[p_tab].icon;
+
+}
+
+void Tabs::add_tab(const String& p_str,const Ref<Texture>& p_icon) {
+
+ Tab t;
+ t.text=p_str;
+ t.icon=p_icon;
+ tabs.push_back(t);
+
+ update();
+ minimum_size_changed();
+
+}
+
+void Tabs::remove_tab(int p_idx) {
+
+ ERR_FAIL_INDEX(p_idx,tabs.size());
+ tabs.remove(p_idx);
+ if (current>=p_idx)
+ current--;
+ update();
+ minimum_size_changed();
+
+ if (current<0)
+ current=0;
+ if (current>=tabs.size())
+ current=tabs.size()-1;
+
+ emit_signal("tab_changed",current);
+
+}
+
+
+void Tabs::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&Tabs::_input_event);
+ ObjectTypeDB::bind_method(_MD("get_tab_count"),&Tabs::get_tab_count);
+ ObjectTypeDB::bind_method(_MD("set_current_tab","tab_idx"),&Tabs::set_current_tab);
+ ObjectTypeDB::bind_method(_MD("get_current_tab"),&Tabs::get_current_tab);
+ ObjectTypeDB::bind_method(_MD("set_tab_title","tab_idx","title"),&Tabs::set_tab_title);
+ ObjectTypeDB::bind_method(_MD("get_tab_title","tab_idx"),&Tabs::get_tab_title);
+ ObjectTypeDB::bind_method(_MD("set_tab_icon","tab_idx","icon:Texture"),&Tabs::set_tab_icon);
+ ObjectTypeDB::bind_method(_MD("get_tab_icon:Texture","tab_idx"),&Tabs::get_tab_icon);
+ ObjectTypeDB::bind_method(_MD("remove_tab","tab_idx","icon:Texture"),&Tabs::remove_tab);
+
+ ADD_SIGNAL(MethodInfo("tab_changed",PropertyInfo(Variant::INT,"tab")));
+
+ ADD_PROPERTY( PropertyInfo(Variant::INT, "current_tab", PROPERTY_HINT_RANGE,"-1,4096,1",PROPERTY_USAGE_EDITOR), _SCS("set_current_tab"), _SCS("get_current_tab") );
+
+}
+
+Tabs::Tabs() {
+
+ current=0;
+
+}
diff --git a/scene/gui/tabs.h b/scene/gui/tabs.h
new file mode 100644
index 0000000000..72c077a8b0
--- /dev/null
+++ b/scene/gui/tabs.h
@@ -0,0 +1,80 @@
+/*************************************************************************/
+/* tabs.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TABS_H
+#define TABS_H
+
+#include "scene/gui/control.h"
+
+class Tabs : public Control {
+
+ OBJ_TYPE( Tabs, Control );
+private:
+
+
+ struct Tab {
+
+ String text;
+ Ref<Texture> icon;
+ int ofs_cache;
+ };
+
+ Vector<Tab> tabs;
+ int current;
+ Control *_get_tab(int idx) const;
+ int _get_top_margin() const;
+
+protected:
+
+ void _input_event(const InputEvent& p_event);
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+
+ void add_tab(const String& p_str="",const Ref<Texture>& p_icon=Ref<Texture>());
+
+ void set_tab_title(int p_tab,const String& p_title);
+ String get_tab_title(int p_tab) const;
+
+ void set_tab_icon(int p_tab,const Ref<Texture>& p_icon);
+ Ref<Texture> get_tab_icon(int p_tab) const;
+
+ int get_tab_count() const;
+ void set_current_tab(int p_current);
+ int get_current_tab() const;
+
+ void remove_tab(int p_idx);
+
+ Size2 get_minimum_size() const;
+
+ Tabs();
+};
+
+
+#endif // TABS_H
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
new file mode 100644
index 0000000000..18c8c0e9f7
--- /dev/null
+++ b/scene/gui/text_edit.cpp
@@ -0,0 +1,2943 @@
+/*************************************************************************/
+/* text_edit.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+ /*****f********************************************/
+/* text_edit.cpp */
+/*************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/*************************************************/
+/* Source code within this file is: */
+/* (c) 2007-2010 Juan Linietsky, Ariel Manzur */
+/* All Rights Reserved. */
+/*************************************************/
+
+#include "text_edit.h"
+#include "os/keyboard.h"
+#include "os/os.h"
+
+#include "globals.h"
+#include "message_queue.h"
+
+#define TAB_PIXELS
+
+
+
+static bool _is_text_char(CharType c) {
+
+ return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9') || c=='_';
+}
+
+static bool _is_symbol(CharType c) {
+
+ return c!='_' && ((c>='!' && c<='/') || (c>=':' && c<='@') || (c>='[' && c<='`') || (c>='{' && c<='~') || c=='\t');
+}
+
+void TextEdit::Text::set_font(const Ref<Font>& p_font) {
+
+ font=p_font;
+}
+
+void TextEdit::Text::set_tab_size(int p_tab_size) {
+
+ tab_size=p_tab_size;
+}
+
+void TextEdit::Text::_update_line_cache(int p_line) const {
+
+ int w =0;
+ int tab_w=font->get_char_size(' ').width;
+
+ int len = text[p_line].data.length();
+ const CharType *str = text[p_line].data.c_str();
+
+ //update width
+
+ for(int i=0;i<len;i++) {
+ if (str[i]=='\t') {
+
+ int left = w%tab_w;
+ if (left==0)
+ w+=tab_w;
+ else
+ w+=tab_w-w%tab_w; // is right...
+
+ } else {
+
+ w+=font->get_char_size(str[i],str[i+1]).width;
+ }
+ }
+
+
+ text[p_line].width_cache=w;
+
+ //update regions
+
+ text[p_line].region_info.clear();
+
+ for(int i=0;i<len;i++) {
+
+ if (!_is_symbol(str[i]))
+ continue;
+ if (str[i]=='\\') {
+ i++; //skip quoted anything
+ continue;
+ }
+
+ int left=len-i;
+
+ for(int j=0;j<color_regions->size();j++) {
+
+ const ColorRegion& cr=color_regions->operator [](j);
+
+ /* BEGIN */
+
+ int lr=cr.begin_key.length();
+ if (lr==0 || lr>left)
+ continue;
+
+ const CharType* kc = cr.begin_key.c_str();
+
+ bool match=true;
+
+ for(int k=0;k<lr;k++) {
+ if (kc[k]!=str[i+k]) {
+ match=false;
+ break;
+ }
+ }
+
+ if (match) {
+
+ ColorRegionInfo cri;
+ cri.end=false;
+ cri.region=j;
+ text[p_line].region_info[i]=cri;
+ i+=lr-1;
+ break;
+ }
+
+ /* END */
+
+ lr=cr.end_key.length();
+ if (lr==0 || lr>left)
+ continue;
+
+ kc = cr.end_key.c_str();
+
+ match=true;
+
+ for(int k=0;k<lr;k++) {
+ if (kc[k]!=str[i+k]) {
+ match=false;
+ break;
+ }
+ }
+
+ if (match) {
+
+ ColorRegionInfo cri;
+ cri.end=true;
+ cri.region=j;
+ text[p_line].region_info[i]=cri;
+ i+=lr-1;
+ break;
+ }
+
+ }
+ }
+
+
+}
+
+const Map<int,TextEdit::Text::ColorRegionInfo>& TextEdit::Text::get_color_region_info(int p_line) {
+
+ Map<int,ColorRegionInfo> *cri=NULL;
+ ERR_FAIL_INDEX_V(p_line,text.size(),*cri); //enjoy your crash
+
+ if (text[p_line].width_cache==-1) {
+ _update_line_cache(p_line);
+ }
+
+ return text[p_line].region_info;
+}
+
+int TextEdit::Text::get_line_width(int p_line) const {
+
+ ERR_FAIL_INDEX_V(p_line,text.size(),-1);
+
+ if (text[p_line].width_cache==-1) {
+ _update_line_cache(p_line);
+ }
+
+ return text[p_line].width_cache;
+}
+
+void TextEdit::Text::clear_caches() {
+
+ for(int i=0;i<text.size();i++)
+ text[i].width_cache=-1;
+
+}
+
+void TextEdit::Text::clear() {
+
+
+ text.clear();;
+ insert(0,"");
+}
+
+int TextEdit::Text::get_max_width() const {
+ //quite some work.. but should be fast enough.
+
+ int max = 0;
+
+ for(int i=0;i<text.size();i++)
+ max=MAX(max,get_line_width(i));
+ return max;
+
+}
+
+void TextEdit::Text::set(int p_line,const String& p_text) {
+
+ ERR_FAIL_INDEX(p_line,text.size());
+
+ text[p_line].width_cache=-1;
+ text[p_line].data=p_text;
+}
+
+
+void TextEdit::Text::insert(int p_at,const String& p_text) {
+
+ Line line;
+ line.marked=false;
+ line.breakpoint=false;
+ line.width_cache=-1;
+ line.data=p_text;
+ text.insert(p_at,line);
+}
+void TextEdit::Text::remove(int p_at) {
+
+ text.remove(p_at);
+}
+
+void TextEdit::_update_scrollbars() {
+
+
+ Size2 size = get_size();
+ Size2 hmin = h_scroll->get_combined_minimum_size();
+ Size2 vmin = v_scroll->get_combined_minimum_size();
+
+
+
+ v_scroll->set_begin( Point2(size.width - vmin.width, cache.style_normal->get_margin(MARGIN_TOP)) );
+ v_scroll->set_end( Point2(size.width, size.height - cache.style_normal->get_margin(MARGIN_TOP) - cache.style_normal->get_margin(MARGIN_BOTTOM)) );
+
+ h_scroll->set_begin( Point2( 0, size.height - hmin.height) );
+ h_scroll->set_end( Point2(size.width-vmin.width, size.height) );
+
+
+ int hscroll_rows = ((hmin.height-1)/get_row_height())+1;
+ int visible_rows = get_visible_rows();
+ int total_rows = text.size() * cache.line_spacing;
+
+ int vscroll_pixels = v_scroll->get_combined_minimum_size().width;
+ int visible_width = size.width - cache.style_normal->get_minimum_size().width;
+ int total_width = text.get_max_width();
+
+ bool use_hscroll=true;
+ bool use_vscroll=true;
+
+ if (total_rows <= visible_rows && total_width <= visible_width) {
+ //thanks yessopie for this clever bit of logic
+ use_hscroll=false;
+ use_vscroll=false;
+
+ } else {
+
+ if (total_rows > visible_rows && total_width <= visible_width - vscroll_pixels) {
+ //thanks yessopie for this clever bit of logic
+ use_hscroll=false;
+ }
+
+ if (total_rows <= visible_rows - hscroll_rows && total_width > visible_width) {
+ //thanks yessopie for this clever bit of logic
+ use_vscroll=false;
+ }
+ }
+
+ updating_scrolls=true;
+
+ if (use_vscroll) {
+
+ v_scroll->show();
+ v_scroll->set_max(total_rows);
+ v_scroll->set_page(visible_rows);
+
+ v_scroll->set_val(cursor.line_ofs);
+
+ } else {
+
+ v_scroll->hide();
+ }
+
+ if (use_hscroll) {
+
+ h_scroll->show();
+ h_scroll->set_max(total_width);
+ h_scroll->set_page(visible_width);
+ h_scroll->set_val(cursor.x_ofs);
+ } else {
+
+ h_scroll->hide();
+ }
+
+
+
+ updating_scrolls=false;
+}
+
+
+void TextEdit::_notification(int p_what) {
+
+ switch(p_what) {
+ case NOTIFICATION_ENTER_SCENE: {
+
+ _update_caches();
+ if (cursor_changed_dirty)
+ MessageQueue::get_singleton()->push_call(this,"_cursor_changed_emit");
+ if (text_changed_dirty)
+ MessageQueue::get_singleton()->push_call(this,"_text_changed_emit");
+
+ } break;
+ case NOTIFICATION_RESIZED: {
+
+ cache.size=get_size();
+ adjust_viewport_to_cursor();
+
+
+ } break;
+ case NOTIFICATION_THEME_CHANGED: {
+
+ _update_caches();
+ };
+ case NOTIFICATION_DRAW: {
+
+ int line_number_char_count=0;
+
+ {
+ int lc=text.size()+1;
+ cache.line_number_w=0;
+ while(lc) {
+ cache.line_number_w+=1;
+ lc/=10;
+ };
+
+ if (line_numbers) {
+
+ line_number_char_count=cache.line_number_w;
+ cache.line_number_w=(cache.line_number_w+1)*cache.font->get_char_size('0').width;
+ } else {
+ cache.line_number_w=0;
+ }
+
+
+ }
+ _update_scrollbars();
+
+
+ RID ci = get_canvas_item();
+ int xmargin_beg=cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w;
+ int xmargin_end=cache.size.width-cache.style_normal->get_margin(MARGIN_RIGHT);
+ //let's do it easy for now:
+ cache.style_normal->draw(ci,Rect2(Point2(),cache.size));
+ if (has_focus())
+ cache.style_focus->draw(ci,Rect2(Point2(),cache.size));
+
+
+ int ascent=cache.font->get_ascent();
+
+ int visible_rows = get_visible_rows();
+
+ int tab_w = cache.font->get_char_size(' ').width*tab_size;
+
+ Color color = cache.font_color;
+ int in_region=-1;
+
+ if (syntax_coloring) {
+
+ if (custom_bg_color.a>0.01) {
+
+ Point2i ofs = Point2i(cache.style_normal->get_offset())/2.0;
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(ofs, get_size()-cache.style_normal->get_minimum_size()+ofs),custom_bg_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 deregion=0; //force it to clear inrgion
+ Point2 cursor_pos;
+
+ for (int i=0;i<visible_rows;i++) {
+
+ int line=i+cursor.line_ofs;
+
+ if (line<0 || line>=(int)text.size())
+ continue;
+
+
+
+ const String &str=text[line];
+
+ int char_margin=xmargin_beg-cursor.x_ofs;
+ int char_ofs=0;
+ int ofs_y=i*get_row_height()+cache.line_spacing/2;
+ bool prev_is_char=false;
+ bool in_keyword=false;
+ Color keyword_color;
+
+ if (cache.line_number_w) {
+ Color fcol = cache.font_color;
+ fcol.a*=0.4;
+ String fc = String::num(line+1);
+ while (fc.length() < line_number_char_count) {
+ fc="0"+fc;
+ }
+
+ cache.font->draw(ci,Point2(cache.style_normal->get_margin(MARGIN_LEFT),ofs_y+cache.font->get_ascent()),fc,fcol);
+ }
+
+ const Map<int,Text::ColorRegionInfo>& cri_map=text.get_color_region_info(line);
+
+
+ if (text.is_marked(line)) {
+
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(xmargin_beg, ofs_y,xmargin_end-xmargin_beg,get_row_height()),cache.mark_color);
+ }
+
+ if (text.is_breakpoint(line)) {
+
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(xmargin_beg, ofs_y,xmargin_end-xmargin_beg,get_row_height()),cache.breakpoint_color);
+ }
+
+
+ if (line==cursor.line) {
+
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(xmargin_beg, ofs_y,xmargin_end-xmargin_beg,get_row_height()),cache.current_line_color);
+
+ }
+ 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
+ //find keyword
+ bool is_char = _is_text_char(str[j]);
+ bool is_symbol=_is_symbol(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
+ }
+
+ 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;
+
+ if (in_region==-1 && !in_keyword && is_char && !prev_is_char) {
+
+ int to=j;
+ while(_is_text_char(str[to]) && to<str.length())
+ 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) {
+
+ in_keyword=true;
+ keyword_color=*col;
+ }
+ }
+
+
+ if (in_region>=0)
+ color=color_regions[in_region].color;
+ else if (in_keyword)
+ color=keyword_color;
+ else if (is_symbol)
+ color=symbol_color;
+
+ prev_is_char=is_char;
+
+ }
+ int char_w;
+
+ //handle tabulator
+
+
+ if (str[j]=='\t') {
+ int left = char_ofs%tab_w;
+ if (left==0)
+ char_w=tab_w;
+ else
+ char_w=tab_w-char_ofs%tab_w; // is right...
+
+ } else {
+ char_w=cache.font->get_char_size(str[j],str[j+1]).width;
+ }
+
+ if ( (char_ofs+char_margin)<xmargin_beg) {
+ char_ofs+=char_w;
+ continue;
+ }
+
+ if ( (char_ofs+char_margin+char_w)>=xmargin_end) {
+ if (syntax_coloring)
+ continue;
+ else
+ break;
+ }
+
+ bool in_selection = (selection.active && line>=selection.from_line && line<=selection.to_line && (line>selection.from_line || j>=selection.from_column) && (line<selection.to_line || j<selection.to_column));
+
+
+ if (in_selection) {
+ //inside selection!
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(Point2i( char_ofs+char_margin, ofs_y ), Size2i(char_w,get_row_height())),cache.selection_color);
+ }
+
+
+
+ if (str[j]>=32)
+ cache.font->draw_char(ci,Point2i( char_ofs+char_margin, ofs_y+ascent),str[j],str[j+1],in_selection?cache.font_selected_color:color);
+ else if (draw_tabs && str[j]=='\t') {
+ int yofs= (get_row_height() - cache.tab_icon->get_height())/2;
+ cache.tab_icon->draw(ci, Point2(char_ofs+char_margin,ofs_y+yofs),in_selection?cache.font_selected_color:color);
+ }
+
+
+ if (cursor.column==j && cursor.line==line) {
+
+ cursor_pos = Point2i( char_ofs+char_margin, ofs_y );
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(cursor_pos, Size2i(1,get_row_height())),cache.font_color);
+
+
+ }
+ char_ofs+=char_w;
+
+ }
+
+ if (cursor.column==str.length() && cursor.line==line) {
+
+ cursor_pos=Point2i( char_ofs+char_margin, ofs_y );
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,Rect2(cursor_pos, Size2i(1,get_row_height())),cache.font_color);
+
+ }
+ }
+
+ if (completion_active) {
+ // code completion box
+ Ref<StyleBox> csb = get_stylebox("completion");
+ Ref<StyleBox> csel = get_stylebox("completion_selected");
+ int maxlines = get_constant("completion_lines");
+ int cmax_width = get_constant("completion_max_width")*cache.font->get_char_size('x').x;
+ Color existing = get_color("completion_existing");
+ int scrollw = get_constant("completion_scroll_width");
+ Color scrollc = get_color("completion_scroll_color");
+
+
+
+ int lines = MIN(completion_options.size(),maxlines);
+ int w=0;
+ int h=lines*get_row_height();
+ int nofs = cache.font->get_string_size(completion_base).width;
+
+
+ if (completion_options.size() < 50) {
+ for(int i=0;i<completion_options.size();i++) {
+ int w2=MIN(cache.font->get_string_size(completion_options[i]).x,cmax_width);
+ if (w2>w)
+ w=w2;
+ }
+ } else {
+ w=cmax_width;
+ }
+
+ int th = h + csb->get_minimum_size().y;
+ if (cursor_pos.y+get_row_height()+th > get_size().height) {
+ completion_rect.pos.y=cursor_pos.y-th;
+ } else {
+ completion_rect.pos.y=cursor_pos.y+get_row_height()+csb->get_offset().y;
+ }
+
+ if (cursor_pos.x-nofs+w+scrollw > get_size().width) {
+ completion_rect.pos.x=get_size().width-w-scrollw;
+ } else {
+ completion_rect.pos.x=cursor_pos.x-nofs;
+ }
+
+ completion_rect.size.width=w;
+ completion_rect.size.height=h;
+ if (completion_options.size()<=maxlines)
+ scrollw=0;
+
+ draw_style_box(csb,Rect2(completion_rect.pos-csb->get_offset(),completion_rect.size+csb->get_minimum_size()+Size2(scrollw,0)));
+
+
+ int line_from = CLAMP(completion_index - lines/2, 0, completion_options.size() - lines);
+ draw_style_box(csel,Rect2(Point2(completion_rect.pos.x,completion_rect.pos.y+(completion_index-line_from)*get_row_height()),Size2(completion_rect.size.width,get_row_height())));
+
+ draw_rect(Rect2(completion_rect.pos,Size2(nofs,completion_rect.size.height)),existing);
+
+ for(int i=0;i<lines;i++) {
+
+ int l = line_from + i;
+ ERR_CONTINUE( l < 0 || l>= completion_options.size());
+ draw_string(cache.font,Point2(completion_rect.pos.x,completion_rect.pos.y+i*get_row_height()+cache.font->get_ascent()),completion_options[l],cache.font_color,completion_rect.size.width);
+ }
+
+ if (scrollw) {
+ //draw a small scroll rectangle to show a position in the options
+ float r = maxlines / (float)completion_options.size();
+ float o = line_from / (float)completion_options.size();
+ draw_rect(Rect2(completion_rect.pos.x+completion_rect.size.width,completion_rect.pos.y+o*completion_rect.size.y,scrollw,completion_rect.size.y*r),scrollc);
+ }
+
+ completion_line_ofs=line_from;
+
+ }
+
+
+
+ } break;
+ case NOTIFICATION_FOCUS_ENTER: {
+
+ if (OS::get_singleton()->has_virtual_keyboard())
+ OS::get_singleton()->show_virtual_keyboard(get_text(),get_global_rect());
+
+ } break;
+ case NOTIFICATION_FOCUS_EXIT: {
+
+ if (OS::get_singleton()->has_virtual_keyboard())
+ OS::get_singleton()->hide_virtual_keyboard();
+
+ } break;
+
+ }
+}
+
+void TextEdit::backspace_at_cursor() {
+
+ if (cursor.column==0 && cursor.line==0)
+ return;
+
+ int prev_line = cursor.column?cursor.line:cursor.line-1;
+ int prev_column = cursor.column?(cursor.column-1):(text[cursor.line-1].length());
+ _remove_text(prev_line,prev_column,cursor.line,cursor.column);
+ cursor_set_line(prev_line);
+ cursor_set_column(prev_column);
+
+}
+
+
+bool TextEdit::_get_mouse_pos(const Point2i& p_mouse, int &r_row, int &r_col) const {
+
+ int row=p_mouse.y;
+ row-=cache.style_normal->get_margin(MARGIN_TOP);
+ row/=get_row_height();
+
+ if (row<0 || row>=get_visible_rows())
+ return false;
+
+ row+=cursor.line_ofs;
+ int col=0;
+
+ if (row>=text.size()) {
+
+ row=text.size()-1;
+ col=text[row].size();
+ } else {
+
+ col=p_mouse.x-(cache.style_normal->get_margin(MARGIN_LEFT)+cache.line_number_w);
+ col+=cursor.x_ofs;
+ col=get_char_pos_for( col, get_line(row) );
+ }
+
+ r_row=row;
+ r_col=col;
+ return true;
+}
+
+void TextEdit::_input_event(const InputEvent& p_input_event) {
+
+ switch(p_input_event.type) {
+
+ case InputEvent::MOUSE_BUTTON: {
+
+ const InputEventMouseButton &mb=p_input_event.mouse_button;
+
+ if (completion_active && completion_rect.has_point(Point2(mb.x,mb.y))) {
+
+ if (!mb.pressed)
+ return;
+
+ if (mb.button_index==BUTTON_WHEEL_UP) {
+ if (completion_index>0) {
+ completion_index--;
+ completion_current=completion_options[completion_index];
+ update();
+ }
+
+ }
+ if (mb.button_index==BUTTON_WHEEL_DOWN) {
+
+ if (completion_index<completion_options.size()-1) {
+ completion_index++;
+ completion_current=completion_options[completion_index];
+ update();
+ }
+ }
+
+ if (mb.button_index==BUTTON_LEFT) {
+
+ completion_index=CLAMP(completion_line_ofs+(mb.y-completion_rect.pos.y)/get_row_height(),0,completion_options.size()-1);
+
+ completion_current=completion_options[completion_index];
+ update();
+ if (mb.doubleclick)
+ _confirm_completion();
+ }
+ return;
+ } else {
+ _cancel_completion();
+ }
+
+ if (mb.pressed) {
+ if (mb.button_index==BUTTON_WHEEL_UP) {
+ v_scroll->set_val( v_scroll->get_val() -3 );
+ }
+ if (mb.button_index==BUTTON_WHEEL_DOWN) {
+ v_scroll->set_val( v_scroll->get_val() +3 );
+ }
+ if (mb.button_index==BUTTON_LEFT) {
+
+ int row,col;
+ if (!_get_mouse_pos(Point2i(mb.x,mb.y), row,col))
+ return;
+
+ cursor_set_line( row );
+ cursor_set_column( col );
+
+ //if sel active and dblick last time < something
+
+ //else
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_POINTER;
+ selection.selecting_line=row;
+ selection.selecting_column=col;
+
+
+ if (!mb.doubleclick && (OS::get_singleton()->get_ticks_msec()-last_dblclk)<600) {
+ //tripleclick select line
+ select(cursor.line,0,cursor.line,text[cursor.line].length());
+ last_dblclk=0;
+
+ } else if (mb.doubleclick && text[cursor.line].length()) {
+
+ //doubleclick select world
+ String s = text[cursor.line];
+ int beg=CLAMP(cursor.column,0,s.length());
+ int end=beg;
+
+ if (s[beg]>32 || beg==s.length()) {
+
+ bool symbol = beg < s.length() && _is_symbol(s[beg]); //not sure if right but most editors behave like this
+
+ while(beg>0 && s[beg-1]>32 && (symbol==_is_symbol(s[beg-1]))) {
+ beg--;
+ }
+ while(end<s.length() && s[end+1]>32 && (symbol==_is_symbol(s[end+1]))) {
+ end++;
+ }
+
+ if (end<s.length())
+ end+=1;
+
+ select(cursor.line,beg,cursor.line,end);
+ }
+
+ last_dblclk = OS::get_singleton()->get_ticks_msec();
+
+ }
+
+ update();
+ }
+ } else {
+
+ selection.selecting_mode=Selection::MODE_NONE;
+ }
+
+ } break;
+ case InputEvent::MOUSE_MOTION: {
+
+ const InputEventMouseMotion &mm=p_input_event.mouse_motion;
+
+ if (mm.button_mask&BUTTON_MASK_LEFT) {
+
+ int row,col;
+ if (!_get_mouse_pos(Point2i(mm.x,mm.y), row,col))
+ return;
+
+ if (selection.selecting_mode==Selection::MODE_POINTER) {
+
+ select(selection.selecting_line,selection.selecting_column,row,col);
+
+ cursor_set_line( row );
+ cursor_set_column( col );
+ update();
+
+ }
+
+ }
+
+ } break;
+
+ case InputEvent::KEY: {
+
+ InputEventKey k=p_input_event.key;
+
+ if (!k.pressed)
+ return;
+
+ if (completion_active) {
+
+ bool valid=true;
+ if (k.mod.command || k.mod.alt || k.mod.meta)
+ valid=false;
+
+ if (valid) {
+
+ if (k.scancode==KEY_UP) {
+
+ if (completion_index>0) {
+ completion_index--;
+ completion_current=completion_options[completion_index];
+ update();
+ }
+ accept_event();
+ return;
+ }
+
+
+ if (k.scancode==KEY_DOWN) {
+
+ if (completion_index<completion_options.size()-1) {
+ completion_index++;
+ completion_current=completion_options[completion_index];
+ update();
+ }
+ accept_event();
+ return;
+ }
+
+ if (k.scancode==KEY_PAGEUP) {
+
+ completion_index-=get_constant("completion_lines");
+ if (completion_index<0)
+ completion_index=0;
+ completion_current=completion_options[completion_index];
+ update();
+ accept_event();
+ return;
+ }
+
+
+ if (k.scancode==KEY_PAGEDOWN) {
+
+ completion_index+=get_constant("completion_lines");
+ if (completion_index>=completion_options.size())
+ completion_index=completion_options.size()-1;
+ completion_current=completion_options[completion_index];
+ update();
+ accept_event();
+ return;
+ }
+
+ if (k.scancode==KEY_HOME) {
+
+ completion_index=0;
+ completion_current=completion_options[completion_index];
+ update();
+ accept_event();
+ return;
+ }
+
+ if (k.scancode==KEY_END) {
+
+ completion_index=completion_options.size()-1;
+ completion_current=completion_options[completion_index];
+ update();
+ accept_event();
+ return;
+ }
+
+
+ if (k.scancode==KEY_DOWN) {
+
+ if (completion_index<completion_options.size()-1) {
+ completion_index++;
+ completion_current=completion_options[completion_index];
+ update();
+ }
+ accept_event();
+ return;
+ }
+
+ if (k.scancode==KEY_RETURN) {
+
+ _confirm_completion();
+ accept_event();
+ return;
+ }
+
+ if (k.scancode==KEY_BACKSPACE) {
+
+ backspace_at_cursor();
+ _update_completion_candidates();
+ accept_event();
+ return;
+ }
+
+
+ if (k.scancode==KEY_SHIFT) {
+ accept_event();
+ return;
+ }
+
+ if (k.unicode>32) {
+
+ if (cursor.column<text[cursor.line].length() && text[cursor.line][cursor.column]==k.unicode) {
+ //same char, move ahead
+ cursor_set_column(cursor.column+1);
+ } else {
+ //different char, go back
+ const CharType chr[2] = {k.unicode, 0};
+ _insert_text_at_cursor(chr);
+ }
+ _update_completion_candidates();
+ accept_event();
+
+ return;
+ }
+ }
+
+ _cancel_completion();
+ }
+
+ /* TEST CONTROL FIRST!! */
+
+ // some remaps for duplicate functions..
+ if (k.mod.command && !k.mod.shift && !k.mod.alt && !k.mod.meta && k.scancode==KEY_INSERT) {
+
+ k.scancode=KEY_C;
+ }
+ if (!k.mod.command && k.mod.shift && !k.mod.alt && !k.mod.meta && k.scancode==KEY_INSERT) {
+
+ k.scancode=KEY_V;
+ k.mod.command=true;
+ k.mod.shift=false;
+ }
+
+ // stuff to do when selection is active..
+
+ if (selection.active) {
+
+ bool clear=false;
+ bool unselect=false;
+ bool dobreak=false;
+
+ switch(k.scancode) {
+
+ case KEY_TAB: {
+
+ String txt = _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ String prev_txt=txt;
+
+ if (k.mod.shift) {
+
+ for(int i=0;i<txt.length();i++) {
+ if (((i>0 && txt[i-1]=='\n') || (i==0 && selection.from_column==0)) && (txt[i]=='\t' || txt[i]==' ')) {
+ txt.remove(i);
+ //i--;
+ }
+ }
+ } else {
+
+ for(int i=0;i<txt.length();i++) {
+
+ if (((i>0 && txt[i-1]=='\n') || (i==0 && selection.from_column==0))) {
+ txt=txt.insert(i,"\t");
+ //i--;
+ }
+ }
+ }
+
+ if (txt!=prev_txt) {
+
+ int sel_line=selection.from_line;
+ int sel_column=selection.from_column;
+
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ _insert_text_at_cursor(txt);
+ selection.active=true;
+ selection.from_column=sel_column;
+ selection.from_line=sel_line;
+ selection.to_column=cursor.column;
+ selection.to_line=cursor.line;
+ update();
+ }
+
+ dobreak=true;
+ accept_event();
+
+ } break;
+ case KEY_X:
+ case KEY_C:
+ //special keys often used with control, wait...
+ clear=(!k.mod.command || k.mod.shift || k.mod.alt );
+ break;
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ accept_event();
+ clear=true; dobreak=true;
+ break;
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case KEY_HOME:
+ case KEY_END:
+ if (k.mod.shift) // if selecting ignore
+ break;
+ unselect=true;
+ break;
+ default:
+ if (k.unicode>=32 && !k.mod.command && !k.mod.alt && !k.mod.meta)
+ clear=true;
+
+ }
+
+ if (unselect) {
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_NONE;
+ update();
+ }
+ if (clear) {
+
+ selection.active=false;
+ update();
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+ update();
+ }
+ if (dobreak)
+ break;
+ }
+
+ selection.selecting_test=false;
+
+ bool scancode_handled=true;
+
+ // special scancode test...
+
+ switch (k.scancode) {
+
+ case KEY_ENTER:
+ case KEY_RETURN: {
+
+ String ins="\n";
+
+ //keep indentation
+ for(int i=0;i<text[cursor.line].length();i++) {
+ if (text[cursor.line][i]=='\t')
+ ins+="\t";
+ else
+ break;
+ }
+
+ _insert_text_at_cursor(ins);
+ _push_current_op();
+
+ } break;
+ case KEY_TAB: {
+
+ if (readonly)
+ break;
+
+ if (selection.active) {
+
+
+ } else {
+ if (k.mod.shift) {
+
+ int cc = cursor.column;
+ if (cc>0 && cc<=text[cursor.line].length() && text[cursor.line][cursor.column-1]=='\t') {
+ //simple unindent
+
+ backspace_at_cursor();
+ }
+ } else {
+ //simple indent
+ _insert_text_at_cursor("\t");
+ }
+ }
+
+ } break;
+ case KEY_BACKSPACE: {
+ if (readonly)
+ break;
+ backspace_at_cursor();
+
+ } break;
+ case KEY_LEFT: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+#ifdef APPLE_STYLE_KEYS
+ if (k.mod.command) {
+ cursor_set_column(0);
+ } else if (k.mod.alt) {
+
+#else
+ if (k.mod.command) {
+#endif
+ bool prev_char=false;
+ int cc=cursor.column;
+ while (cc>0) {
+
+ bool ischar=_is_text_char(text[cursor.line][cc-1]);
+
+ if (prev_char && !ischar)
+ break;
+
+ prev_char=ischar;
+ cc--;
+
+ }
+
+ cursor_set_column(cc);
+
+ } else if (cursor.column==0) {
+
+ if (cursor.line>0) {
+ cursor_set_line(cursor.line-1);
+ cursor_set_column(text[cursor.line].length());
+ }
+ } else {
+ cursor_set_column(cursor_get_column()-1);
+ }
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_RIGHT: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+#ifdef APPLE_STYLE_KEYS
+ if (k.mod.command) {
+ cursor_set_column(text[cursor.line].length());
+ } else if (k.mod.alt) {
+#else
+ if (k.mod.command) {
+#endif
+ bool prev_char=false;
+ int cc=cursor.column;
+ while (cc<text[cursor.line].length()) {
+
+ bool ischar=_is_text_char(text[cursor.line][cc]);
+
+ if (prev_char && !ischar)
+ break;
+ prev_char=ischar;
+ cc++;
+ }
+
+ cursor_set_column(cc);
+
+ } else if (cursor.column==text[cursor.line].length()) {
+
+ if (cursor.line<text.size()-1) {
+ cursor_set_line(cursor.line+1);
+ cursor_set_column(0);
+ }
+ } else {
+ cursor_set_column(cursor_get_column()+1);
+ }
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_UP: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+#ifdef APPLE_STYLE_KEYS
+ if (k.mod.command)
+ cursor_set_line(0);
+ else
+#endif
+ cursor_set_line(cursor_get_line()-1);
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_DOWN: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+#ifdef APPLE_STYLE_KEYS
+ if (k.mod.command)
+ cursor_set_line(text.size()-1);
+ else
+#endif
+ cursor_set_line(cursor_get_line()+1);
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+
+ case KEY_DELETE: {
+
+ if (readonly)
+ break;
+ int curline_len = text[cursor.line].length();
+
+ if (cursor.line==text.size()-1 && cursor.column==curline_len)
+ break; //nothing to do
+
+ int next_line = cursor.column<curline_len?cursor.line:cursor.line+1;
+ int next_column = cursor.column<curline_len?(cursor.column+1):0;
+ _remove_text(cursor.line,cursor.column,next_line,next_column);
+ update();
+ } break;
+#ifdef APPLE_STYLE_KEYS
+ case KEY_HOME: {
+
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ cursor_set_line(0);
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_END: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ cursor_set_line(text.size()-1);
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+
+#else
+ case KEY_HOME: {
+
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ cursor_set_column(0);
+ if (k.mod.command)
+ cursor_set_line(0);
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_END: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ if (k.mod.command)
+ cursor_set_line(text.size()-1);
+ cursor_set_column(text[cursor.line].length());
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+#endif
+ case KEY_PAGEUP: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ cursor_set_line(cursor_get_line()-get_visible_rows());
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_PAGEDOWN: {
+
+ if (k.mod.shift)
+ _pre_shift_selection();
+
+ cursor_set_line(cursor_get_line()+get_visible_rows());
+
+ if (k.mod.shift)
+ _post_shift_selection();
+
+ } break;
+ case KEY_A: {
+
+ if (!k.mod.command || k.mod.shift || k.mod.alt) {
+ scancode_handled=false;
+ break;
+ }
+
+ if (text.size()==1 && text[0].length()==0)
+ break;
+ selection.active=true;
+ selection.from_line=0;
+ selection.from_column=0;
+ selection.to_line=text.size()-1;
+ selection.to_column=text[selection.to_line].size();
+ selection.selecting_mode=Selection::MODE_NONE;
+ update();
+
+ } break;
+ case KEY_X: {
+
+ if (!k.mod.command || k.mod.shift || k.mod.alt) {
+ scancode_handled=false;
+ break;
+ }
+
+ if (!selection.active)
+ break;
+
+ String clipboard = _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ OS::get_singleton()->set_clipboard(clipboard);
+
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_NONE;
+ update();
+
+ } break;
+ case KEY_C: {
+
+ if (!k.mod.command || k.mod.shift || k.mod.alt) {
+ scancode_handled=false;
+ break;
+ }
+
+ if (!selection.active)
+ break;
+
+ String clipboard = _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ OS::get_singleton()->set_clipboard(clipboard);
+ } break;
+ case KEY_Z: {
+
+ if (!k.mod.command) {
+ scancode_handled=false;
+ break;
+ }
+
+ if (k.mod.shift)
+ redo();
+ else
+ undo();
+ } break;
+ case KEY_V: {
+
+ if (!k.mod.command || k.mod.shift || k.mod.alt) {
+ scancode_handled=false;
+ break;
+ }
+
+ String clipboard = OS::get_singleton()->get_clipboard();
+
+ if (selection.active) {
+ selection.active=false;
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+
+ }
+
+ _insert_text_at_cursor(clipboard);
+
+ update();
+ } break;
+ case KEY_SPACE: {
+ if (completion_enabled && k.mod.command) {
+
+ query_code_comple();
+ scancode_handled=true;
+ } else {
+ scancode_handled=false;
+ }
+
+ } break;
+
+ default: {
+
+ scancode_handled=false;
+ } break;
+
+ }
+
+ if (scancode_handled)
+ accept_event();
+
+ if (!scancode_handled && !k.mod.command && !k.mod.alt) {
+
+ if (k.unicode>=32) {
+
+ if (readonly)
+ break;
+
+ const CharType chr[2] = {k.unicode, 0};
+ _insert_text_at_cursor(chr);
+ accept_event();
+ } else {
+
+ break;
+ }
+ }
+
+ if (!selection.selecting_test) {
+
+ selection.selecting_mode=Selection::MODE_NONE;
+ }
+
+ return;
+ } break;
+
+ }
+
+}
+
+
+void TextEdit::_pre_shift_selection() {
+
+
+ if (!selection.active || selection.selecting_mode!=Selection::MODE_SHIFT) {
+
+ selection.selecting_line=cursor.line;
+ selection.selecting_column=cursor.column;
+ selection.active=true;
+ selection.selecting_mode=Selection::MODE_SHIFT;
+ }
+}
+
+void TextEdit::_post_shift_selection() {
+
+
+ if (selection.active && selection.selecting_mode==Selection::MODE_SHIFT) {
+
+ select(selection.selecting_line,selection.selecting_column,cursor.line,cursor.column);
+ update();
+ }
+
+
+ selection.selecting_test=true;
+}
+
+/**** TEXT EDIT CORE API ****/
+
+
+
+void TextEdit::_base_insert_text(int p_line, int p_char,const String& p_text,int &r_end_line,int &r_end_column) {
+
+ //save for undo...
+ ERR_FAIL_INDEX(p_line,text.size());
+ ERR_FAIL_COND(p_char<0);
+
+ /* STEP 1 add spaces if the char is greater than the end of the line */
+ while(p_char>text[p_line].length()) {
+
+ text.set(p_line,text[p_line]+String::chr(' '));
+ }
+
+ /* STEP 2 separate dest string in pre and post text */
+
+ String preinsert_text = text[p_line].substr(0,p_char);
+ String postinsert_text = text[p_line].substr(p_char,text[p_line].size());
+
+ /* STEP 3 remove \r from source text and separate in substrings */
+
+ //buh bye \r and split
+ Vector<String> substrings = p_text.replace("\r","").split("\n");
+
+
+ for(int i=0;i<substrings.size();i++) {
+ //insert the substrings
+
+ if (i==0) {
+
+ text.set(p_line,preinsert_text+substrings[i]);
+ } else {
+
+ text.insert(p_line+i,substrings[i]);
+ }
+
+ if (i==substrings.size()-1){
+
+ text.set(p_line+i,text[p_line+i]+postinsert_text);
+ }
+ }
+
+ r_end_line=p_line+substrings.size()-1;
+ r_end_column=text[r_end_line].length()-postinsert_text.length();
+
+ if (!text_changed_dirty && !setting_text) {
+
+ if (is_inside_scene())
+ MessageQueue::get_singleton()->push_call(this,"_text_changed_emit");
+ text_changed_dirty=true;
+ }
+
+}
+
+String TextEdit::_base_get_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) const {
+
+ ERR_FAIL_INDEX_V(p_from_line,text.size(),String());
+ ERR_FAIL_INDEX_V(p_from_column,text[p_from_line].length()+1,String());
+ ERR_FAIL_INDEX_V(p_to_line,text.size(),String());
+ ERR_FAIL_INDEX_V(p_to_column,text[p_to_line].length()+1,String());
+ ERR_FAIL_COND_V(p_to_line < p_from_line ,String()); // from > to
+ ERR_FAIL_COND_V(p_to_line == p_from_line && p_to_column<p_from_column,String()); // from > to
+
+ String ret;
+
+ for(int i=p_from_line;i<=p_to_line;i++) {
+
+ int begin = (i==p_from_line)?p_from_column:0;
+ int end = (i==p_to_line)?p_to_column:text[i].length();
+
+ if (i>p_from_line)
+ ret+="\n";
+ ret+=text[i].substr(begin,end-begin);
+ }
+
+ return ret;
+}
+
+void TextEdit::_base_remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) {
+
+ ERR_FAIL_INDEX(p_from_line,text.size());
+ ERR_FAIL_INDEX(p_from_column,text[p_from_line].length()+1);
+ ERR_FAIL_INDEX(p_to_line,text.size());
+ ERR_FAIL_INDEX(p_to_column,text[p_to_line].length()+1);
+ ERR_FAIL_COND(p_to_line < p_from_line ); // from > to
+ ERR_FAIL_COND(p_to_line == p_from_line && p_to_column<p_from_column); // from > to
+
+
+ String pre_text = text[p_from_line].substr(0,p_from_column);
+ String post_text = text[p_to_line].substr(p_to_column,text[p_to_line].length());
+
+ for(int i=p_from_line;i<p_to_line;i++) {
+
+ text.remove(p_from_line+1);
+ }
+
+ text.set(p_from_line,pre_text+post_text);
+
+ if (!text_changed_dirty && !setting_text) {
+
+ if (is_inside_scene())
+ MessageQueue::get_singleton()->push_call(this,"_text_changed_emit");
+ text_changed_dirty=true;
+ }
+}
+
+
+
+
+void TextEdit::_insert_text(int p_line, int p_char,const String& p_text,int *r_end_line,int *r_end_column) {
+
+ if (!setting_text)
+ idle_detect->start();
+
+ if (undo_enabled) {
+
+ _clear_redo();
+ }
+
+ int retline,retchar;
+ _base_insert_text(p_line,p_char,p_text,retline,retchar);
+ if (r_end_line)
+ *r_end_line=retline;
+ if (r_end_column)
+ *r_end_column=retchar;
+
+ if (!undo_enabled)
+ return;
+
+ /* UNDO!! */
+ TextOperation op;
+ op.type=TextOperation::TYPE_INSERT;
+ op.from_line=p_line;
+ op.from_column=p_char;
+ op.to_line=retline;
+ op.to_column=retchar;
+ op.text=p_text;
+ op.version=++version;
+ op.chain_forward=false;
+ op.chain_backward=false;
+
+ //see if it shold just be set as current op
+ if (current_op.type!=op.type) {
+ _push_current_op();
+ current_op=op;
+
+ return; //set as current op, return
+ }
+ //see if it can be merged
+ if (current_op.to_line!=p_line || current_op.to_column!=p_char) {
+ _push_current_op();
+ current_op=op;
+ return; //set as current op, return
+ }
+ //merge current op
+
+ current_op.text+=p_text;
+ current_op.to_column=retchar;
+ current_op.to_line=retline;
+ current_op.version=op.version;
+
+}
+
+void TextEdit::_remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) {
+
+ if (!setting_text)
+ idle_detect->start();
+
+ String text;
+ if (undo_enabled) {
+ _clear_redo();
+ text=_base_get_text(p_from_line,p_from_column,p_to_line,p_to_column);
+ }
+
+ _base_remove_text(p_from_line,p_from_column,p_to_line,p_to_column);
+
+ if (!undo_enabled)
+ return;
+
+ /* UNDO!! */
+ TextOperation op;
+ op.type=TextOperation::TYPE_REMOVE;
+ op.from_line=p_from_line;
+ op.from_column=p_from_column;
+ op.to_line=p_to_line;
+ op.to_column=p_to_column;
+ op.text=text;
+ op.version=++version;
+ op.chain_forward=false;
+ op.chain_backward=false;
+
+ //see if it shold just be set as current op
+ if (current_op.type!=op.type) {
+ _push_current_op();
+ current_op=op;
+ return; //set as current op, return
+ }
+ //see if it can be merged
+ if (current_op.from_line==p_to_line && current_op.from_column==p_to_column) {
+ //basckace or similar
+ current_op.text=text+current_op.text;
+ current_op.from_line=p_from_line;
+ current_op.from_column=p_from_column;
+ return; //update current op
+ }
+ if (current_op.from_line==p_from_line && current_op.from_column==p_from_column) {
+
+ //current_op.text=text+current_op.text;
+ //current_op.from_line=p_from_line;
+ //current_op.from_column=p_from_column;
+ //return; //update current op
+ }
+
+ _push_current_op();
+ current_op=op;
+
+}
+
+
+void TextEdit::_insert_text_at_cursor(const String& p_text) {
+
+ int new_column,new_line;
+ _insert_text(cursor.line,cursor.column,p_text,&new_line,&new_column);
+ cursor_set_line(new_line);
+ cursor_set_column(new_column);
+
+ update();
+}
+
+
+
+
+int TextEdit::get_char_count() {
+
+ int totalsize=0;
+
+ for (int i=0;i<text.size();i++) {
+
+ if (i>0)
+ totalsize++; // incliude \n
+ totalsize+=text[i].length();
+ }
+
+ return totalsize; // omit last \n
+}
+
+Size2 TextEdit::get_minimum_size() {
+
+ return cache.style_normal->get_minimum_size();
+}
+int TextEdit::get_visible_rows() const {
+
+ int total=cache.size.height;
+ total-=cache.style_normal->get_minimum_size().height;
+ total/=get_row_height();
+ return total;
+}
+void TextEdit::adjust_viewport_to_cursor() {
+
+ if (cursor.line_ofs>cursor.line)
+ cursor.line_ofs=cursor.line;
+
+ int visible_width=cache.size.width-cache.style_normal->get_minimum_size().width-cache.line_number_w;
+ if (v_scroll->is_visible())
+ visible_width-=v_scroll->get_combined_minimum_size().width;
+ visible_width-=20; // give it a little more space
+
+
+ //printf("rowofs %i, visrows %i, cursor.line %i\n",cursor.line_ofs,get_visible_rows(),cursor.line);
+
+ int visible_rows = get_visible_rows();
+ if (h_scroll->is_visible())
+ visible_rows-=((h_scroll->get_combined_minimum_size().height-1)/get_row_height());
+
+ if (cursor.line>=(cursor.line_ofs+visible_rows))
+ cursor.line_ofs=cursor.line-visible_rows+1;
+ if (cursor.line<cursor.line_ofs)
+ cursor.line_ofs=cursor.line;
+
+ int cursor_x = get_column_x_offset( cursor.column, text[cursor.line] );
+
+ if (cursor_x>(cursor.x_ofs+visible_width))
+ cursor.x_ofs=cursor_x-visible_width+1;
+
+ if (cursor_x < cursor.x_ofs)
+ cursor.x_ofs=cursor_x;
+
+ update();
+/*
+ get_range()->set_max(text.size());
+
+ get_range()->set_page(get_visible_rows());
+
+ get_range()->set((int)cursor.line_ofs);
+*/
+
+
+}
+
+
+void TextEdit::cursor_set_column(int p_col) {
+
+ if (p_col<0)
+ p_col=0;
+
+ cursor.column=p_col;
+ if (cursor.column > get_line( cursor.line ).length())
+ cursor.column=get_line( cursor.line ).length();
+
+ cursor.last_fit_x=get_column_x_offset(cursor.column,get_line(cursor.line));
+
+ adjust_viewport_to_cursor();
+
+ if (!cursor_changed_dirty) {
+
+ if (is_inside_scene())
+ MessageQueue::get_singleton()->push_call(this,"_cursor_changed_emit");
+ cursor_changed_dirty=true;
+ }
+
+}
+
+
+void TextEdit::cursor_set_line(int p_row) {
+
+ if (setting_row)
+ return;
+
+ setting_row=true;
+ if (p_row<0)
+ p_row=0;
+
+
+ if (p_row>=(int)text.size())
+ p_row=(int)text.size()-1;
+
+ cursor.line=p_row;
+ cursor.column=get_char_pos_for( cursor.last_fit_x, get_line( cursor.line) );
+
+
+ adjust_viewport_to_cursor();
+
+ setting_row=false;
+
+
+ if (!cursor_changed_dirty) {
+
+ if (is_inside_scene())
+ MessageQueue::get_singleton()->push_call(this,"_cursor_changed_emit");
+ cursor_changed_dirty=true;
+ }
+
+}
+
+
+int TextEdit::cursor_get_column() const {
+
+ return cursor.column;
+}
+
+
+int TextEdit::cursor_get_line() const {
+
+ return cursor.line;
+}
+
+
+
+void TextEdit::_scroll_moved(double p_to_val) {
+
+ if (updating_scrolls)
+ return;
+
+ if (h_scroll->is_visible())
+ cursor.x_ofs=h_scroll->get_val();
+ if (v_scroll->is_visible())
+ cursor.line_ofs=v_scroll->get_val();
+ update();
+}
+
+
+
+
+
+int TextEdit::get_row_height() const {
+
+ return cache.font->get_height()+cache.line_spacing;
+}
+
+int TextEdit::get_char_pos_for(int p_px,String p_str) const {
+
+ int px=0;
+ int c=0;
+
+ int tab_w = cache.font->get_char_size(' ').width*tab_size;
+
+ while (c<p_str.length()) {
+
+ int w=0;
+
+ if (p_str[c]=='\t') {
+
+ int left = px%tab_w;
+ if (left==0)
+ w=tab_w;
+ else
+ w=tab_w-px%tab_w; // is right...
+
+ } else {
+
+ w=cache.font->get_char_size(p_str[c],p_str[c+1]).width;
+ }
+
+ if (p_px<(px+w/2))
+ break;
+ px+=w;
+ c++;
+ }
+
+ return c;
+}
+
+int TextEdit::get_column_x_offset(int p_char,String p_str) {
+
+ int px=0;
+
+ int tab_w = cache.font->get_char_size(' ').width*tab_size;
+
+ for (int i=0;i<p_char;i++) {
+
+ if (i>=p_str.length())
+ break;
+
+ if (p_str[i]=='\t') {
+
+ int left = px%tab_w;
+ if (left==0)
+ px+=tab_w;
+ else
+ px+=tab_w-px%tab_w; // is right...
+
+ } else {
+ px+=cache.font->get_char_size(p_str[i],p_str[i+1]).width;
+ }
+ }
+
+ return px;
+
+}
+
+void TextEdit::insert_text_at_cursor(const String& p_text) {
+
+ if (selection.active) {
+
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_NONE;
+
+ }
+
+ _insert_text_at_cursor(p_text);
+ update();
+
+}
+
+void TextEdit::set_text(String p_text){
+
+ setting_text=true;
+ _clear();
+ _insert_text_at_cursor(p_text);
+
+ cursor.column=0;
+ cursor.line=0;
+ cursor.x_ofs=0;
+ cursor.line_ofs=0;
+ cursor.last_fit_x=0;
+ cursor_set_line(0);
+ cursor_set_column(0);
+ update();
+ setting_text=false;
+
+ //get_range()->set(0);
+};
+
+String TextEdit::get_text() {
+ String longthing;
+ int len = text.size();
+ for (int i=0;i<len;i++) {
+
+
+ longthing+=text[i];
+ if (i!=len-1)
+ longthing+="\n";
+ }
+
+ return longthing;
+
+};
+
+
+String TextEdit::get_line(int line) const {
+
+ if (line<0 || line>=text.size())
+ return "";
+
+ return text[line];
+
+};
+
+void TextEdit::_clear() {
+
+ clear_undo_history();
+ text.clear();
+ cursor.column=0;
+ cursor.line=0;
+ cursor.x_ofs=0;
+ cursor.line_ofs=0;
+ cursor.last_fit_x=0;
+}
+
+
+
+void TextEdit::clear() {
+
+ setting_text=true;
+ _clear();
+ setting_text=false;
+
+};
+
+void TextEdit::set_readonly(bool p_readonly) {
+
+
+ readonly=p_readonly;
+}
+
+void TextEdit::set_wrap(bool p_wrap) {
+
+ wrap=p_wrap;
+}
+
+void TextEdit::set_max_chars(int p_max_chars) {
+
+ max_chars=p_max_chars;
+}
+
+void TextEdit::_update_caches() {
+
+ cache.style_normal=get_stylebox("normal");
+ cache.style_focus=get_stylebox("focus");
+ cache.font=get_font("font");
+ cache.font_color=get_color("font_color");
+ cache.font_selected_color=get_color("font_selected_color");
+ cache.keyword_color=get_color("keyword_color");
+ cache.selection_color=get_color("selection_color");
+ cache.mark_color=get_color("mark_color");
+ cache.current_line_color=get_color("current_line_color");
+ cache.breakpoint_color=get_color("breakpoint_color");
+ cache.line_spacing=get_constant("line_spacing");
+ cache.row_height = cache.font->get_height() + cache.line_spacing;
+ cache.tab_icon=get_icon("tab");
+ text.set_font(cache.font);
+
+}
+
+
+void TextEdit::clear_colors() {
+
+ keywords.clear();
+ color_regions.clear();;
+ text.clear_caches();
+ custom_bg_color=Color(0,0,0,0);
+}
+
+void TextEdit::set_custom_bg_color(const Color& p_color) {
+
+ custom_bg_color=p_color;
+ update();
+}
+
+void TextEdit::add_keyword_color(const String& p_keyword,const Color& p_color) {
+
+ keywords[p_keyword]=p_color;
+ update();
+
+}
+
+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));
+ text.clear_caches();
+ update();
+
+}
+
+void TextEdit::set_symbol_color(const Color& p_color) {
+
+ symbol_color=p_color;
+ update();
+}
+
+void TextEdit::set_syntax_coloring(bool p_enabled) {
+
+ syntax_coloring=p_enabled;
+ update();
+}
+
+bool TextEdit::is_syntax_coloring_enabled() const {
+
+ return syntax_coloring;
+}
+
+void TextEdit::cut() {
+
+ if (!selection.active)
+ return;
+
+ String clipboard = _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ OS::get_singleton()->set_clipboard(clipboard);
+
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_NONE;
+ update();
+
+}
+
+void TextEdit::copy() {
+
+ if (!selection.active)
+ return;
+
+ String clipboard = _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ OS::get_singleton()->set_clipboard(clipboard);
+
+}
+void TextEdit::paste() {
+
+ if (selection.active) {
+
+ cursor_set_line(selection.from_line);
+ cursor_set_column(selection.from_column);
+
+ _remove_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+ selection.active=false;
+ selection.selecting_mode=Selection::MODE_NONE;
+
+ }
+
+ String clipboard = OS::get_singleton()->get_clipboard();
+ _insert_text_at_cursor(clipboard);
+ update();
+
+}
+
+void TextEdit::select_all() {
+
+ if (text.size()==1 && text[0].length()==0)
+ return;
+ selection.active=true;
+ selection.from_line=0;
+ selection.from_column=0;
+ selection.to_line=text.size()-1;
+ selection.to_column=text[selection.to_line].size();
+ selection.selecting_mode=Selection::MODE_NONE;
+ update();
+
+}
+
+
+void TextEdit::deselect() {
+
+ selection.active=false;
+ update();
+}
+
+void TextEdit::select(int p_from_line,int p_from_column,int p_to_line,int p_to_column) {
+
+ if (p_from_line>=text.size())
+ p_from_line=text.size()-1;
+ if (p_from_column>=text[p_from_line].length())
+ p_from_column=text[p_from_line].length();
+
+ if (p_to_line>=text.size())
+ p_to_line=text.size()-1;
+ if (p_to_column>=text[p_to_line].length())
+ p_to_column=text[p_to_line].length();
+
+ selection.from_line=p_from_line;
+ selection.from_column=p_from_column;
+ selection.to_line=p_to_line;
+ selection.to_column=p_to_column;
+
+ selection.active=true;
+
+ if (selection.from_line==selection.to_line) {
+
+ if (selection.from_column==selection.to_column) {
+
+ selection.active=false;
+
+ } else if (selection.from_column>selection.to_column) {
+
+ SWAP( selection.from_column, selection.to_column );
+ }
+ } else if (selection.from_line>selection.to_line) {
+
+ SWAP( selection.from_line, selection.to_line );
+ SWAP( selection.from_column, selection.to_column );
+ }
+
+
+ update();
+}
+
+bool TextEdit::is_selection_active() const {
+
+ return selection.active;
+}
+int TextEdit::get_selection_from_line() const {
+
+ ERR_FAIL_COND_V(!selection.active,-1);
+ return selection.from_line;
+
+}
+int TextEdit::get_selection_from_column() const {
+
+ ERR_FAIL_COND_V(!selection.active,-1);
+ return selection.from_column;
+
+}
+int TextEdit::get_selection_to_line() const {
+
+ ERR_FAIL_COND_V(!selection.active,-1);
+ return selection.to_line;
+
+}
+int TextEdit::get_selection_to_column() const {
+
+ ERR_FAIL_COND_V(!selection.active,-1);
+ return selection.to_column;
+
+}
+
+String TextEdit::get_selection_text() const {
+
+ if (!selection.active)
+ return "";
+
+ return _base_get_text(selection.from_line,selection.from_column,selection.to_line,selection.to_column);
+
+}
+
+
+DVector<int> TextEdit::_search_bind(const String &p_key,uint32_t p_search_flags, int p_from_line,int p_from_column) const {
+
+ int col,line;
+ if (search(p_key,p_search_flags,p_from_line,p_from_column,col,line)) {
+ DVector<int> result;
+ result.resize(2);
+ result.set(0,line);
+ result.set(1,col);
+ return result;
+
+ } else {
+
+ return DVector<int>();
+ }
+}
+
+bool TextEdit::search(const String &p_key,uint32_t p_search_flags, int p_from_line, int p_from_column,int &r_line,int &r_column) const {
+
+ if (p_key.length()==0)
+ return false;
+ 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
+
+ int line=-1;
+ int pos=-1;
+
+ for(int i=0;i<text.size()+1;i++) {
+ //backwards is broken...
+ int idx=(p_search_flags&SEARCH_BACKWARDS)?(text.size()-i):i; //do backwards seearch
+ line = (idx+p_from_line)%text.size();
+
+ String text_line = text[line];
+ int from_column=(idx==0)?p_from_column:0;
+ if (idx==text.size()) {
+ text_line=text_line.substr(0,p_from_column); //wrap around for missing begining.
+ }
+
+ pos=-1;
+
+ if (!(p_search_flags&SEARCH_BACKWARDS)) {
+ pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.find(p_key,from_column):text_line.findn(p_key,from_column);
+ } else {
+
+ pos = (p_search_flags&SEARCH_MATCH_CASE)?text_line.rfind(p_key,from_column):text_line.rfindn(p_key,from_column);
+ }
+
+ if (pos!=-1 && (p_search_flags&SEARCH_WHOLE_WORDS)) {
+ //validate for whole words
+ if (pos>0 && _is_text_char(text_line[pos-1]))
+ pos=-1;
+ else if (_is_text_char(text_line[pos+p_key.length()]))
+ pos=-1;
+ }
+
+ if (pos!=-1)
+ break;
+
+ }
+
+ if (pos==-1) {
+ r_line=-1;
+ r_column=-1;
+ return false;
+ }
+
+ r_line=line;
+ r_column=pos;
+
+
+ return true;
+}
+
+void TextEdit::_cursor_changed_emit() {
+
+ emit_signal("cursor_changed");
+ cursor_changed_dirty=false;
+}
+
+void TextEdit::_text_changed_emit() {
+
+ emit_signal("text_changed");
+ text_changed_dirty=false;
+}
+
+void TextEdit::set_line_as_marked(int p_line,bool p_marked) {
+
+ ERR_FAIL_INDEX(p_line,text.size());
+ text.set_marked(p_line,p_marked);
+ update();
+}
+
+bool TextEdit::is_line_set_as_breakpoint(int p_line) const {
+
+ ERR_FAIL_INDEX_V(p_line,text.size(),false);
+ return text.is_breakpoint(p_line);
+
+}
+
+void TextEdit::set_line_as_breakpoint(int p_line,bool p_breakpoint) {
+
+
+ ERR_FAIL_INDEX(p_line,text.size());
+ text.set_breakpoint(p_line,p_breakpoint);
+ update();
+}
+
+void TextEdit::get_breakpoints(List<int> *p_breakpoints) const {
+
+ for(int i=0;i<text.size();i++) {
+ if (text.is_breakpoint(i))
+ p_breakpoints->push_back(i);
+ }
+}
+
+int TextEdit::get_line_count() const {
+
+ return text.size();
+}
+
+void TextEdit::_do_text_op(const TextOperation& p_op, bool p_reverse) {
+
+ ERR_FAIL_COND(p_op.type==TextOperation::TYPE_NONE);
+
+ bool insert = p_op.type==TextOperation::TYPE_INSERT;
+ if (p_reverse)
+ insert=!insert;
+
+ if (insert) {
+
+ int check_line;
+ int check_column;
+ _base_insert_text(p_op.from_line,p_op.from_column,p_op.text,check_line,check_column);
+ ERR_FAIL_COND( check_line != p_op.to_line ); // BUG
+ ERR_FAIL_COND( check_column != p_op.to_column ); // BUG
+ } else {
+
+ _base_remove_text(p_op.from_line,p_op.from_column,p_op.to_line,p_op.to_column);
+ }
+
+}
+
+void TextEdit::_clear_redo() {
+
+ if (undo_stack_pos==NULL)
+ return; //nothing to clear
+
+ _push_current_op();
+
+ while (undo_stack_pos) {
+ List<TextOperation>::Element *elem = undo_stack_pos;
+ undo_stack_pos=undo_stack_pos->next();
+ undo_stack.erase(elem);
+ }
+}
+
+
+void TextEdit::undo() {
+
+ _push_current_op();
+
+ if (undo_stack_pos==NULL) {
+
+ if (!undo_stack.size())
+ return; //nothing to undo
+
+ undo_stack_pos=undo_stack.back();
+
+ } else if (undo_stack_pos==undo_stack.front())
+ return; // at the bottom of the undo stack
+ else
+ undo_stack_pos=undo_stack_pos->prev();
+
+
+ _do_text_op( undo_stack_pos->get(),true);
+
+ cursor_set_line(undo_stack_pos->get().from_line);
+ cursor_set_column(undo_stack_pos->get().from_column);
+ update();
+}
+
+void TextEdit::redo() {
+
+ _push_current_op();
+
+ if (undo_stack_pos==NULL)
+ return; //nothing to do.
+
+
+ _do_text_op( undo_stack_pos->get(),false);
+ cursor_set_line(undo_stack_pos->get().from_line);
+ cursor_set_column(undo_stack_pos->get().from_column);
+ undo_stack_pos=undo_stack_pos->next();
+ update();
+}
+
+void TextEdit::clear_undo_history() {
+
+ saved_version=0;
+ current_op.type=TextOperation::TYPE_NONE;
+ undo_stack_pos=NULL;
+ undo_stack.clear();;
+
+}
+
+
+void TextEdit::_push_current_op() {
+
+ if (current_op.type==TextOperation::TYPE_NONE)
+ return; // do nothing
+
+ undo_stack.push_back(current_op);
+ current_op.type=TextOperation::TYPE_NONE;
+ current_op.text="";
+
+}
+
+void TextEdit::set_draw_tabs(bool p_draw) {
+
+ draw_tabs=p_draw;
+}
+
+bool TextEdit::is_drawing_tabs() const{
+
+ return draw_tabs;
+}
+
+uint32_t TextEdit::get_version() const {
+ return current_op.version;
+}
+uint32_t TextEdit::get_saved_version() const {
+
+ return saved_version;
+}
+void TextEdit::tag_saved_version() {
+
+ saved_version=get_version();
+}
+
+int TextEdit::get_v_scroll() const {
+
+ return v_scroll->get_val();
+}
+void TextEdit::set_v_scroll(int p_scroll) {
+
+ v_scroll->set_val(p_scroll);
+ cursor.line_ofs=p_scroll;
+}
+
+int TextEdit::get_h_scroll() const {
+
+ return h_scroll->get_val();
+}
+void TextEdit::set_h_scroll(int p_scroll) {
+
+ h_scroll->set_val(p_scroll);
+}
+
+void TextEdit::set_completion(bool p_enabled,const Vector<String>& p_prefixes) {
+
+ completion_prefixes.clear();
+ completion_enabled=p_enabled;
+ for(int i=0;i<p_prefixes.size();i++)
+ completion_prefixes.insert(p_prefixes[i]);
+}
+
+void TextEdit::_confirm_completion() {
+
+ String remaining=completion_current.substr(completion_base.length(),completion_current.length()-completion_base.length());
+ String l = text[cursor.line];
+ bool same=true;
+ //if what is going to be inserted is the same as what it is, don't change it
+ for(int i=0;i<remaining.length();i++) {
+ int c=i+cursor.column;
+ if (c>=l.length() || l[c]!=remaining[i]) {
+ same=false;
+ break;
+ }
+ }
+
+ if (same)
+ cursor_set_column(cursor.column+remaining.length());
+ else
+ insert_text_at_cursor(remaining);
+
+ _cancel_completion();
+}
+
+void TextEdit::_cancel_completion() {
+
+ if (!completion_active)
+ return;
+
+ completion_active=false;
+ update();
+
+}
+
+void TextEdit::_update_completion_candidates() {
+
+ String l = text[cursor.line];
+ int cofs = CLAMP(cursor.column,0,l.length());
+
+ String s;
+ while(cofs>0 && l[cofs-1]>32 && !_is_symbol(l[cofs-1])) {
+ s=String::chr(l[cofs-1])+s;
+ cofs--;
+ }
+
+ update();
+
+ if (s=="" && (cofs==0 || !completion_prefixes.has(String::chr(l[cofs-1])))) {
+ //none to complete, cancel
+ _cancel_completion();
+ return;
+ }
+
+ completion_options.clear();
+ completion_index=0;
+ completion_base=s;
+ int ci_match=0;
+ for(int i=0;i<completion_strings.size();i++) {
+ if (completion_strings[i].begins_with(s)) {
+ completion_options.push_back(completion_strings[i]);
+ int m=0;
+ int max=MIN(completion_current.length(),completion_strings[i].length());
+ if (max<ci_match)
+ continue;
+ for(int j=0;j<max;j++) {
+
+ if (j>=completion_strings[i].length())
+ break;
+ if (completion_current[j]!=completion_strings[i][j])
+ break;
+ m++;
+ }
+ if (m>ci_match) {
+ ci_match=m;
+ completion_index=completion_options.size()-1;
+ }
+
+ }
+ }
+
+
+
+ if (completion_options.size()==0) {
+ //no options to complete, cancel
+ _cancel_completion();
+ return;
+
+ }
+
+ completion_current=completion_options[completion_index];
+
+ if (completion_options.size()==1) {
+ //one option to complete, just complete it automagically
+ _confirm_completion();
+// insert_text_at_cursor(completion_options[0].substr(s.length(),completion_options[0].length()-s.length()));
+ _cancel_completion();
+ return;
+
+ }
+
+ completion_enabled=true;
+
+
+
+}
+
+void TextEdit::query_code_comple() {
+
+ String l = text[cursor.line];
+ int ofs = CLAMP(cursor.column,0,l.length());
+ String cs;
+ while(ofs>0 && l[ofs-1]>32) {
+
+ if (_is_symbol(l[ofs-1])) {
+ String s;
+ while(ofs>0 && l[ofs-1]>32 && _is_symbol(l[ofs-1])) {
+ s=String::chr(l[ofs-1])+s;
+ ofs--;
+ }
+ if (completion_prefixes.has(s))
+ cs=s+cs;
+ else
+ break;
+ } else {
+
+ cs=String::chr(l[ofs-1])+cs;
+ ofs--;
+ }
+
+ }
+
+ if (cs!="") {
+ emit_signal("request_completion",cs,cursor.line);
+
+ }
+
+}
+
+void TextEdit::code_complete(const Vector<String> &p_strings) {
+
+
+ completion_strings=p_strings;
+ completion_active=true;
+ completion_current="";
+ completion_index=0;
+ _update_completion_candidates();
+//
+}
+
+
+String TextEdit::get_tooltip(const Point2& p_pos) const {
+
+ if (!tooltip_obj)
+ return Control::get_tooltip(p_pos);
+ int row,col;
+ if (!_get_mouse_pos(p_pos, row,col)) {
+ return Control::get_tooltip(p_pos);
+ }
+
+ String s = text[row];
+ if (s.length()==0)
+ return Control::get_tooltip(p_pos);
+ int beg=CLAMP(col,0,s.length());
+ int end=beg;
+
+
+ if (s[beg]>32 || beg==s.length()) {
+
+ bool symbol = beg < s.length() && _is_symbol(s[beg]); //not sure if right but most editors behave like this
+
+ while(beg>0 && s[beg-1]>32 && (symbol==_is_symbol(s[beg-1]))) {
+ beg--;
+ }
+ while(end<s.length() && s[end+1]>32 && (symbol==_is_symbol(s[end+1]))) {
+ end++;
+ }
+
+ if (end<s.length())
+ end+=1;
+
+ String tt = tooltip_obj->call(tooltip_func,s.substr(beg,end-beg),tooltip_ud);
+
+ return tt;
+
+ }
+
+ return Control::get_tooltip(p_pos);
+
+}
+
+void TextEdit::set_tooltip_request_func(Object *p_obj, const StringName& p_function,const Variant& p_udata) {
+
+ tooltip_obj=p_obj;
+ tooltip_func=p_function;
+ tooltip_ud=p_udata;
+}
+
+
+void TextEdit::set_show_line_numbers(bool p_show) {
+
+ line_numbers=p_show;
+ update();
+}
+
+
+void TextEdit::_bind_methods() {
+
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&TextEdit::_input_event);
+ ObjectTypeDB::bind_method(_MD("_scroll_moved"),&TextEdit::_scroll_moved);
+ ObjectTypeDB::bind_method(_MD("_cursor_changed_emit"),&TextEdit::_cursor_changed_emit);
+ ObjectTypeDB::bind_method(_MD("_text_changed_emit"),&TextEdit::_text_changed_emit);
+ ObjectTypeDB::bind_method(_MD("_push_current_op"),&TextEdit::_push_current_op);
+
+ BIND_CONSTANT( SEARCH_MATCH_CASE );
+ BIND_CONSTANT( SEARCH_WHOLE_WORDS );
+ BIND_CONSTANT( SEARCH_BACKWARDS );
+
+/*
+ ObjectTypeDB::bind_method(_MD("delete_char"),&TextEdit::delete_char);
+ ObjectTypeDB::bind_method(_MD("delete_line"),&TextEdit::delete_line);
+*/
+
+ ObjectTypeDB::bind_method(_MD("set_text","text"),&TextEdit::set_text);
+ ObjectTypeDB::bind_method(_MD("insert_text_at_cursor","text"),&TextEdit::insert_text_at_cursor);
+
+ ObjectTypeDB::bind_method(_MD("get_line_count"),&TextEdit::get_line_count);
+ ObjectTypeDB::bind_method(_MD("get_text"),&TextEdit::get_text);
+ ObjectTypeDB::bind_method(_MD("get_line"),&TextEdit::get_line);
+
+ ObjectTypeDB::bind_method(_MD("cursor_set_column","column"),&TextEdit::cursor_set_column);
+ ObjectTypeDB::bind_method(_MD("cursor_set_line","line"),&TextEdit::cursor_set_line);
+
+ ObjectTypeDB::bind_method(_MD("cursor_get_column"),&TextEdit::cursor_get_column);
+ ObjectTypeDB::bind_method(_MD("cursor_get_line"),&TextEdit::cursor_get_line);
+
+
+ ObjectTypeDB::bind_method(_MD("set_readonly","enable"),&TextEdit::set_readonly);
+ ObjectTypeDB::bind_method(_MD("set_wrap","enable"),&TextEdit::set_wrap);
+ ObjectTypeDB::bind_method(_MD("set_max_chars","amount"),&TextEdit::set_max_chars);
+
+ ObjectTypeDB::bind_method(_MD("cut"),&TextEdit::cut);
+ ObjectTypeDB::bind_method(_MD("copy"),&TextEdit::copy);
+ ObjectTypeDB::bind_method(_MD("paste"),&TextEdit::paste);
+ ObjectTypeDB::bind_method(_MD("select_all"),&TextEdit::select_all);
+ ObjectTypeDB::bind_method(_MD("select","from_line","from_column","to_line","to_column"),&TextEdit::select);
+
+ ObjectTypeDB::bind_method(_MD("is_selection_active"),&TextEdit::is_selection_active);
+ ObjectTypeDB::bind_method(_MD("get_selection_from_line"),&TextEdit::get_selection_from_line);
+ ObjectTypeDB::bind_method(_MD("get_selection_from_column"),&TextEdit::get_selection_from_column);
+ ObjectTypeDB::bind_method(_MD("get_selection_to_line"),&TextEdit::get_selection_to_line);
+ ObjectTypeDB::bind_method(_MD("get_selection_to_column"),&TextEdit::get_selection_to_column);
+ ObjectTypeDB::bind_method(_MD("get_selection_text"),&TextEdit::get_selection_text);
+ ObjectTypeDB::bind_method(_MD("search","flags","from_line","from_column","to_line","to_column"),&TextEdit::_search_bind);
+
+ ObjectTypeDB::bind_method(_MD("undo"),&TextEdit::undo);
+ ObjectTypeDB::bind_method(_MD("redo"),&TextEdit::redo);
+ ObjectTypeDB::bind_method(_MD("clear_undo_history"),&TextEdit::clear_undo_history);
+
+ ObjectTypeDB::bind_method(_MD("set_syntax_coloring","enable"),&TextEdit::set_syntax_coloring);
+ ObjectTypeDB::bind_method(_MD("is_syntax_coloring_enabled"),&TextEdit::is_syntax_coloring_enabled);
+
+
+ ObjectTypeDB::bind_method(_MD("add_keyword_color","keyword","color"),&TextEdit::add_keyword_color);
+ ObjectTypeDB::bind_method(_MD("add_color_region","begin_key","end_key","color","line_only"),&TextEdit::add_color_region,DEFVAL(false));
+ ObjectTypeDB::bind_method(_MD("set_symbol_color","color"),&TextEdit::set_symbol_color);
+ ObjectTypeDB::bind_method(_MD("set_custom_bg_color","color"),&TextEdit::set_custom_bg_color);
+ ObjectTypeDB::bind_method(_MD("clear_colors"),&TextEdit::clear_colors);
+
+
+ ADD_SIGNAL(MethodInfo("cursor_changed"));
+ ADD_SIGNAL(MethodInfo("text_changed"));
+ ADD_SIGNAL(MethodInfo("request_completion",PropertyInfo(Variant::STRING,"keyword"),PropertyInfo(Variant::INT,"line")));
+
+}
+
+TextEdit::TextEdit() {
+
+ readonly=false;
+ setting_row=false;
+ draw_tabs=false;
+ max_chars=0;
+ clear();
+ wrap=false;
+ set_focus_mode(FOCUS_ALL);
+ _update_caches();
+ cache.size=Size2(1,1);
+ tab_size=4;
+ text.set_tab_size(tab_size);
+ text.clear();
+// text.insert(1,"Mongolia..");
+// text.insert(2,"PAIS GENEROSO!!");
+ text.set_color_regions(&color_regions);
+
+ h_scroll = memnew( HScrollBar );
+ v_scroll = memnew( VScrollBar );
+
+ add_child(h_scroll);
+ add_child(v_scroll);
+
+ updating_scrolls=false;
+ selection.active=false;
+
+ h_scroll->connect("value_changed", this,"_scroll_moved");
+ v_scroll->connect("value_changed", this,"_scroll_moved");
+
+ cursor_changed_dirty=false;
+ text_changed_dirty=false;
+
+ selection.selecting_mode=Selection::MODE_NONE;
+ selection.selecting_line=0;
+ selection.selecting_column=0;
+ selection.selecting_test=false;
+ selection.active=false;
+ syntax_coloring=false;
+
+ custom_bg_color=Color(0,0,0,0);
+ idle_detect = memnew( Timer );
+ add_child(idle_detect);
+ idle_detect->set_one_shot(true);
+ idle_detect->set_wait_time(GLOBAL_DEF("display/text_edit_idle_detect_sec",3));
+ idle_detect->connect("timeout", this,"_push_current_op");
+
+#if 0
+ syntax_coloring=true;
+ keywords["void"]=Color(0.3,0.0,0.1);
+ keywords["int"]=Color(0.3,0.0,0.1);
+ keywords["function"]=Color(0.3,0.0,0.1);
+ keywords["class"]=Color(0.3,0.0,0.1);
+ keywords["extends"]=Color(0.3,0.0,0.1);
+ keywords["constructor"]=Color(0.3,0.0,0.1);
+ symbol_color=Color(0.1,0.0,0.3,1.0);
+
+ color_regions.push_back(ColorRegion("/*","*/",Color(0.4,0.6,0,4)));
+ color_regions.push_back(ColorRegion("//","",Color(0.6,0.6,0.4)));
+ color_regions.push_back(ColorRegion("\"","\"",Color(0.4,0.7,0.7)));
+ color_regions.push_back(ColorRegion("'","'",Color(0.4,0.8,0.8)));
+ color_regions.push_back(ColorRegion("#","",Color(0.2,1.0,0.2)));
+
+#endif
+
+ current_op.type=TextOperation::TYPE_NONE;
+ undo_enabled=true;
+ undo_stack_pos=NULL;
+ setting_text=false;
+ last_dblclk=0;
+ current_op.version=0;
+ version=0;
+ saved_version=0;
+
+ completion_enabled=false;
+ completion_active=false;
+ completion_line_ofs=0;
+ tooltip_obj=NULL;
+ line_numbers=false;
+}
+
+TextEdit::~TextEdit(){
+}
+
+
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
new file mode 100644
index 0000000000..5826c84c80
--- /dev/null
+++ b/scene/gui/text_edit.h
@@ -0,0 +1,368 @@
+/*************************************************************************/
+/* text_edit.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TEXT_EDIT_H
+#define TEXT_EDIT_H
+
+#include "scene/gui/control.h"
+#include "scene/gui/scroll_bar.h"
+#include "scene/main/timer.h"
+
+
+class TextEdit : public Control {
+
+ OBJ_TYPE( TextEdit, Control );
+
+ struct Cursor {
+ int last_fit_x;
+ int line,column; ///< cursor
+ int x_ofs,line_ofs;
+ } cursor;
+
+ struct Selection {
+
+ enum Mode {
+
+ MODE_NONE,
+ MODE_SHIFT,
+ MODE_POINTER
+ };
+
+ Mode selecting_mode;
+ int selecting_line,selecting_column;
+ bool selecting_test;
+
+
+ bool active;
+
+ int from_line,from_column;
+ int to_line,to_column;
+
+
+ } selection;
+
+ struct Cache {
+
+ Ref<Texture> tab_icon;
+ Ref<StyleBox> style_normal;
+ Ref<StyleBox> style_focus;
+ Ref<Font> font;
+ Color font_color;
+ Color font_selected_color;
+ Color keyword_color;
+ Color selection_color;
+ Color mark_color;
+ Color breakpoint_color;
+ Color current_line_color;
+
+ int row_height;
+ int line_spacing;
+ int line_number_w;
+ 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;
+ Map<int,ColorRegionInfo> region_info;
+ String data;
+ };
+ private:
+ const Vector<ColorRegion> *color_regions;
+ mutable Vector<Line> text;
+ Ref<Font> font;
+ int tab_size;
+
+ void _update_line_cache(int p_line) const;
+
+ public:
+
+
+ void set_tab_size(int p_tab_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() const;
+ const Map<int,ColorRegionInfo>& get_color_region_info(int p_line);
+ void set(int p_line,const String& p_string);
+ 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 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() { tab_size=4; }
+ };
+
+ struct TextOperation {
+
+ enum Type {
+ TYPE_NONE,
+ TYPE_INSERT,
+ TYPE_REMOVE
+ };
+
+ Type type;
+ int from_line,from_column;
+ int to_line, to_column;
+ String text;
+ uint32_t version;
+ bool chain_forward;
+ bool chain_backward;
+ };
+
+ TextOperation current_op;
+
+ List<TextOperation> undo_stack;
+ List<TextOperation>::Element *undo_stack_pos;
+
+ void _clear_redo();
+ void _do_text_op(const TextOperation& p_op, bool p_reverse);
+
+
+ //syntax coloring
+ Color symbol_color;
+ HashMap<String,Color> keywords;
+ Color custom_bg_color;
+
+ Vector<ColorRegion> color_regions;
+
+ Set<String> completion_prefixes;
+ bool completion_enabled;
+ Vector<String> completion_strings;
+ Vector<String> completion_options;
+ bool completion_active;
+ String completion_current;
+ String completion_base;
+ int completion_index;
+ Rect2i completion_rect;
+ int completion_line_ofs;
+
+ bool setting_text;
+
+ // data
+ Text text;
+
+ uint32_t version;
+ uint32_t saved_version;
+
+ int max_chars;
+ bool readonly;
+ bool syntax_coloring;
+ int tab_size;
+
+ bool setting_row;
+ bool wrap;
+ bool draw_tabs;
+ bool cursor_changed_dirty;
+ bool text_changed_dirty;
+ bool undo_enabled;
+ bool line_numbers;
+
+ uint64_t last_dblclk;
+
+ Timer *idle_detect;
+ HScrollBar *h_scroll;
+ VScrollBar *v_scroll;
+ bool updating_scrolls;
+
+
+ Object *tooltip_obj;
+ StringName tooltip_func;
+ Variant tooltip_ud;
+
+ int get_visible_rows() const;
+
+ int get_char_count();
+
+ int get_char_pos_for(int p_px,String p_pos) const;
+ int get_column_x_offset(int p_column,String p_pos);
+
+ void adjust_viewport_to_cursor();
+ void _scroll_moved(double);
+ void _update_scrollbars();
+
+ void _pre_shift_selection();
+ void _post_shift_selection();
+
+// void mouse_motion(const Point& p_pos, const Point& p_rel, int p_button_mask);
+ Size2 get_minimum_size();
+
+ int get_row_height() const;
+
+ void _update_caches();
+ void _cursor_changed_emit();
+ void _text_changed_emit();
+
+ void _push_current_op();
+
+ /* super internal api, undo/redo builds on it */
+
+ void _base_insert_text(int p_line, int p_column,const String& p_text,int &r_end_line,int &r_end_column);
+ String _base_get_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column) const;
+ void _base_remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column);
+
+ DVector<int> _search_bind(const String &p_key,uint32_t p_search_flags, int p_from_line,int p_from_column) const;
+
+ void _clear();
+ void _cancel_completion();
+ void _confirm_completion();
+ void _update_completion_candidates();
+
+ bool _get_mouse_pos(const Point2i& p_mouse, int &r_row, int &r_col) const;
+
+protected:
+
+ virtual String get_tooltip(const Point2& p_pos) const;
+
+ void _insert_text(int p_line, int p_column,const String& p_text,int *r_end_line=NULL,int *r_end_char=NULL);
+ void _remove_text(int p_from_line, int p_from_column,int p_to_line,int p_to_column);
+ void _insert_text_at_cursor(const String& p_text);
+ void _input_event(const InputEvent& p_input);
+ void _notification(int p_what);
+ static void _bind_methods();
+
+
+
+public:
+
+ enum SearchFlags {
+
+ SEARCH_MATCH_CASE=1,
+ SEARCH_WHOLE_WORDS=2,
+ SEARCH_BACKWARDS=4
+ };
+
+ //void delete_char();
+ //void delete_line();
+
+ void set_text(String p_text);
+ void insert_text_at_cursor(const String& p_text);
+ int get_line_count() const;
+ void set_line_as_marked(int p_line,bool p_marked);
+ void set_line_as_breakpoint(int p_line,bool p_breakpoint);
+ bool is_line_set_as_breakpoint(int p_line) const;
+ void get_breakpoints(List<int> *p_breakpoints) const;
+ String get_text();
+ String get_line(int line) const;
+ void backspace_at_cursor();
+
+
+ void cursor_set_column(int p_col);
+ void cursor_set_line(int p_row);
+
+ int cursor_get_column() const;
+ int cursor_get_line() const;
+
+ void set_readonly(bool p_readonly);
+
+ void set_max_chars(int p_max_chars);
+ void set_wrap(bool p_wrap);
+
+ void clear();
+
+ void set_syntax_coloring(bool p_enabled);
+ bool is_syntax_coloring_enabled() const;
+
+ void cut();
+ void copy();
+ void paste();
+ void select_all();
+ void select(int p_from_line,int p_from_column,int p_to_line,int p_to_column);
+ void deselect();
+
+ bool is_selection_active() const;
+ int get_selection_from_line() const;
+ int get_selection_from_column() const;
+ int get_selection_to_line() const;
+ int get_selection_to_column() const;
+ String get_selection_text() const;
+
+ bool search(const String &p_key,uint32_t p_search_flags, int p_from_line, int p_from_column,int &r_line,int &r_column) const;
+
+ void undo();
+ void redo();
+ void clear_undo_history();
+
+
+ void set_draw_tabs(bool p_draw);
+ bool is_drawing_tabs() const;
+
+ void add_keyword_color(const String& p_keyword,const Color& p_color);
+ 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 set_symbol_color(const Color& p_color);
+ void set_custom_bg_color(const Color& p_color);
+ void clear_colors();
+
+ int get_v_scroll() const;
+ void set_v_scroll(int p_scroll);
+
+ int get_h_scroll() const;
+ void set_h_scroll(int p_scroll);
+
+ uint32_t get_version() const;
+ uint32_t get_saved_version() const;
+ void tag_saved_version();
+
+ void set_show_line_numbers(bool p_show);
+
+ void set_tooltip_request_func(Object *p_obj, const StringName& p_function, const Variant& p_udata);
+
+ void set_completion(bool p_enabled,const Vector<String>& p_prefixes);
+ void code_complete(const Vector<String> &p_strings);
+ void query_code_comple();
+
+ TextEdit();
+ ~TextEdit();
+};
+
+
+#endif // TEXT_EDIT_H
diff --git a/scene/gui/texture_button.cpp b/scene/gui/texture_button.cpp
new file mode 100644
index 0000000000..7954ac65df
--- /dev/null
+++ b/scene/gui/texture_button.cpp
@@ -0,0 +1,211 @@
+/*************************************************************************/
+/* texture_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "texture_button.h"
+
+
+Size2 TextureButton::get_minimum_size() const {
+
+ if (normal.is_null()) {
+ if (pressed.is_null()) {
+ if (hover.is_null())
+ if (click_mask.is_null())
+ return Size2();
+ else
+ return click_mask->get_size();
+ else
+ return hover->get_size();
+ } else
+ return pressed->get_size();
+
+ } else
+ return normal->get_size();
+}
+
+
+bool TextureButton::has_point(const Point2& p_point) const {
+
+ if (click_mask.is_valid()) {
+
+ Point2i p =p_point;
+ if (p.x<0 || p.x>=click_mask->get_size().width || p.y<0 || p.y>=click_mask->get_size().height)
+ return false;
+
+ return click_mask->get_bit(p);
+ }
+
+ return Control::has_point(p_point);
+}
+
+void TextureButton::_notification(int p_what) {
+
+ switch( p_what ) {
+
+ case NOTIFICATION_DRAW: {
+ RID canvas_item = get_canvas_item();
+ DrawMode draw_mode = get_draw_mode();
+// if (normal.is_null())
+// break;
+ switch (draw_mode) {
+ case DRAW_NORMAL: {
+
+ if (normal.is_valid())
+ normal->draw(canvas_item,Point2());
+ } break;
+ case DRAW_PRESSED: {
+
+ if (pressed.is_null()) {
+ if (hover.is_null()) {
+ if (normal.is_valid())
+ normal->draw(canvas_item,Point2());
+ } else
+ hover->draw(canvas_item,Point2());
+
+ } else
+ pressed->draw(canvas_item,Point2());
+ } break;
+ case DRAW_HOVER: {
+
+ if (hover.is_null()) {
+ if (pressed.is_valid() && is_pressed())
+ pressed->draw(canvas_item,Point2());
+ else if (normal.is_valid())
+ normal->draw(canvas_item,Point2());
+ } else
+ hover->draw(canvas_item,Point2());
+ } break;
+ case DRAW_DISABLED: {
+
+ if (disabled.is_null()) {
+ if (normal.is_valid())
+ normal->draw(canvas_item,Point2());
+ } else
+ disabled->draw(canvas_item,Point2());
+ } break;
+ }
+ if (has_focus() && focused.is_valid()) {
+
+ focused->draw(canvas_item, Point2());
+ };
+
+ } break;
+ }
+}
+
+void TextureButton::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_normal_texture","texture:Texture"),&TextureButton::set_normal_texture);
+ ObjectTypeDB::bind_method(_MD("set_pressed_texture","texture:Texture"),&TextureButton::set_pressed_texture);
+ ObjectTypeDB::bind_method(_MD("set_hover_texture","texture:Texture"),&TextureButton::set_hover_texture);
+ ObjectTypeDB::bind_method(_MD("set_disabled_texture","texture:Texture"),&TextureButton::set_disabled_texture);
+ ObjectTypeDB::bind_method(_MD("set_focused_texture","texture:Texture"),&TextureButton::set_focused_texture);
+ ObjectTypeDB::bind_method(_MD("set_click_mask","mask:BitMap"),&TextureButton::set_click_mask);
+
+ ObjectTypeDB::bind_method(_MD("get_normal_texture:Texture"),&TextureButton::get_normal_texture);
+ ObjectTypeDB::bind_method(_MD("get_pressed_texture:Texture"),&TextureButton::get_pressed_texture);
+ ObjectTypeDB::bind_method(_MD("get_hover_texture:Texture"),&TextureButton::get_hover_texture);
+ ObjectTypeDB::bind_method(_MD("get_disabled_texture:Texture"),&TextureButton::get_disabled_texture);
+ ObjectTypeDB::bind_method(_MD("get_focused_texture:Texture"),&TextureButton::get_focused_texture);
+ ObjectTypeDB::bind_method(_MD("get_click_mask:BitMap"),&TextureButton::get_click_mask);
+
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/normal",PROPERTY_HINT_RESOURCE_TYPE,"Texture"), _SCS("set_normal_texture"), _SCS("get_normal_texture"));
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/pressed",PROPERTY_HINT_RESOURCE_TYPE,"Texture"), _SCS("set_pressed_texture"), _SCS("get_pressed_texture"));
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/hover",PROPERTY_HINT_RESOURCE_TYPE,"Texture"), _SCS("set_hover_texture"), _SCS("get_hover_texture"));
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/disabled",PROPERTY_HINT_RESOURCE_TYPE,"Texture"), _SCS("set_disabled_texture"), _SCS("get_disabled_texture"));
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/focused",PROPERTY_HINT_RESOURCE_TYPE,"Texture"), _SCS("set_focused_texture"), _SCS("get_focused_texture"));
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT,"textures/click_mask",PROPERTY_HINT_RESOURCE_TYPE,"BitMap"), _SCS("set_click_mask"), _SCS("get_click_mask")) ;
+
+}
+
+
+void TextureButton::set_normal_texture(const Ref<Texture>& p_normal) {
+
+ normal=p_normal;
+ update();
+ minimum_size_changed();
+
+}
+
+void TextureButton::set_pressed_texture(const Ref<Texture>& p_pressed) {
+
+ pressed=p_pressed;
+ update();
+
+}
+void TextureButton::set_hover_texture(const Ref<Texture>& p_hover) {
+
+ hover=p_hover;
+ update();
+
+}
+void TextureButton::set_disabled_texture(const Ref<Texture>& p_disabled) {
+
+ disabled=p_disabled;
+ update();
+
+}
+void TextureButton::set_click_mask(const Ref<BitMap>& p_click_mask) {
+
+ click_mask=p_click_mask;
+ update();
+}
+
+Ref<Texture> TextureButton::get_normal_texture() const {
+
+ return normal;
+}
+Ref<Texture> TextureButton::get_pressed_texture() const {
+
+ return pressed;
+}
+Ref<Texture> TextureButton::get_hover_texture() const {
+
+ return hover;
+}
+Ref<Texture> TextureButton::get_disabled_texture() const {
+
+ return disabled;
+}
+Ref<BitMap> TextureButton::get_click_mask() const {
+
+ return click_mask;
+}
+
+Ref<Texture> TextureButton::get_focused_texture() const {
+
+ return focused;
+};
+
+void TextureButton::set_focused_texture(const Ref<Texture>& p_focused) {
+
+ focused = p_focused;
+};
+
+
+TextureButton::TextureButton() {
+}
diff --git a/scene/gui/texture_button.h b/scene/gui/texture_button.h
new file mode 100644
index 0000000000..d186966cb1
--- /dev/null
+++ b/scene/gui/texture_button.h
@@ -0,0 +1,73 @@
+/*************************************************************************/
+/* texture_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TEXTURE_BUTTON_H
+#define TEXTURE_BUTTON_H
+
+#include "scene/gui/base_button.h"
+#include "scene/resources/bit_mask.h"
+class TextureButton : public BaseButton {
+
+ OBJ_TYPE( TextureButton, BaseButton );
+
+ Ref<Texture> normal;
+ Ref<Texture> pressed;
+ Ref<Texture> hover;
+ Ref<Texture> disabled;
+ Ref<Texture> focused;
+ Ref<BitMap> click_mask;
+
+
+protected:
+
+ virtual bool has_point(const Point2& p_point) const;
+ virtual Size2 get_minimum_size() const;
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+
+ void set_normal_texture(const Ref<Texture>& p_normal);
+ void set_pressed_texture(const Ref<Texture>& p_pressed);
+ void set_hover_texture(const Ref<Texture>& p_hover);
+ void set_disabled_texture(const Ref<Texture>& p_disabled);
+ void set_focused_texture(const Ref<Texture>& p_focused);
+ void set_click_mask(const Ref<BitMap>& p_image);
+
+ Ref<Texture> get_normal_texture() const;
+ Ref<Texture> get_pressed_texture() const;
+ Ref<Texture> get_hover_texture() const;
+ Ref<Texture> get_disabled_texture() const;
+ Ref<Texture> get_focused_texture() const;
+ Ref<BitMap> get_click_mask() const;
+
+
+ TextureButton();
+};
+
+#endif // TEXTURE_BUTTON_H
diff --git a/scene/gui/texture_frame.cpp b/scene/gui/texture_frame.cpp
new file mode 100644
index 0000000000..26f4d32965
--- /dev/null
+++ b/scene/gui/texture_frame.cpp
@@ -0,0 +1,137 @@
+/*************************************************************************/
+/* texture_frame.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "texture_frame.h"
+#include "servers/visual_server.h"
+
+void TextureFrame::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ if (texture.is_null())
+ return;
+
+
+ Size2 s=expand?get_size():texture->get_size();
+ RID ci = get_canvas_item();
+ draw_texture_rect(texture,Rect2(Point2(),s),false,modulate);
+
+/*
+ Vector<Point2> points;
+ points.resize(4);
+ points[0]=Point2(0,0);
+ points[1]=Point2(s.x,0);
+ points[2]=Point2(s.x,s.y);
+ points[3]=Point2(0,s.y);
+ Vector<Point2> uvs;
+ uvs.resize(4);
+ uvs[0]=Point2(0,0);
+ uvs[1]=Point2(1,0);
+ uvs[2]=Point2(1,1);
+ uvs[3]=Point2(0,1);
+
+ VisualServer::get_singleton()->canvas_item_add_primitive(ci,points,Vector<Color>(),uvs,texture->get_rid());
+*/
+ }
+}
+
+Size2 TextureFrame::get_minimum_size() const {
+
+ if (!expand && !texture.is_null())
+ return texture->get_size();
+ else
+ return Size2();
+}
+void TextureFrame::_bind_methods() {
+
+
+ ObjectTypeDB::bind_method(_MD("set_texture","texture"), & TextureFrame::set_texture );
+ ObjectTypeDB::bind_method(_MD("get_texture"), & TextureFrame::get_texture );
+ ObjectTypeDB::bind_method(_MD("set_modulate","modulate"), & TextureFrame::set_modulate );
+ ObjectTypeDB::bind_method(_MD("get_modulate"), & TextureFrame::get_modulate );
+ ObjectTypeDB::bind_method(_MD("set_expand","enable"), & TextureFrame::set_expand );
+ ObjectTypeDB::bind_method(_MD("has_expand"), & TextureFrame::has_expand );
+
+ ADD_PROPERTY( PropertyInfo( Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture"), _SCS("set_texture"),_SCS("get_texture") );
+ ADD_PROPERTY( PropertyInfo( Variant::COLOR, "modulate"), _SCS("set_modulate"),_SCS("get_modulate") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "expand" ), _SCS("set_expand"),_SCS("has_expand") );
+
+}
+
+
+void TextureFrame::set_texture(const Ref<Texture>& p_tex) {
+
+ texture=p_tex;
+ update();
+ if (texture.is_valid())
+ texture->set_flags(texture->get_flags()&(~Texture::FLAG_REPEAT)); //remove repeat from texture, it looks bad in sprites
+ minimum_size_changed();
+}
+
+Ref<Texture> TextureFrame::get_texture() const {
+
+ return texture;
+}
+
+void TextureFrame::set_modulate(const Color& p_tex) {
+
+ modulate=p_tex;
+ update();
+}
+
+Color TextureFrame::get_modulate() const{
+
+ return modulate;
+}
+
+
+void TextureFrame::set_expand(bool p_expand) {
+
+ expand=p_expand;
+ update();
+ minimum_size_changed();
+}
+bool TextureFrame::has_expand() const {
+
+ return expand;
+}
+
+TextureFrame::TextureFrame() {
+
+
+ expand=false;
+ modulate=Color(1,1,1,1);
+ set_ignore_mouse(true);
+}
+
+
+TextureFrame::~TextureFrame()
+{
+}
+
+
diff --git a/scene/gui/texture_frame.h b/scene/gui/texture_frame.h
new file mode 100644
index 0000000000..9f75e1c2c0
--- /dev/null
+++ b/scene/gui/texture_frame.h
@@ -0,0 +1,65 @@
+/*************************************************************************/
+/* texture_frame.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TEXTURE_FRAME_H
+#define TEXTURE_FRAME_H
+
+#include "scene/gui/control.h"
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+class TextureFrame : public Control {
+
+ OBJ_TYPE(TextureFrame,Control);
+
+ bool expand;
+ Color modulate;
+ Ref<Texture> texture;
+protected:
+
+ void _notification(int p_what);
+ virtual Size2 get_minimum_size() const;
+ static void _bind_methods();
+
+public:
+
+ void set_texture(const Ref<Texture>& p_tex);
+ Ref<Texture> get_texture() const;
+
+ void set_modulate(const Color& p_tex);
+ Color get_modulate() const;
+
+ void set_expand(bool p_expand);
+ bool has_expand() const;
+
+ TextureFrame();
+ ~TextureFrame();
+
+};
+
+#endif // TEXTURE_FRAME_H
diff --git a/scene/gui/texture_progress.cpp b/scene/gui/texture_progress.cpp
new file mode 100644
index 0000000000..0ce7df5d20
--- /dev/null
+++ b/scene/gui/texture_progress.cpp
@@ -0,0 +1,124 @@
+/*************************************************************************/
+/* texture_progress.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "texture_progress.h"
+
+
+void TextureProgress::set_under_texture(const Ref<Texture>& p_texture) {
+
+ under=p_texture;
+ update();
+ minimum_size_changed();
+}
+
+Ref<Texture> TextureProgress::get_under_texture() const{
+
+ return under;
+
+}
+
+void TextureProgress::set_over_texture(const Ref<Texture>& p_texture) {
+
+ over=p_texture;
+ update();
+ minimum_size_changed();
+}
+
+Ref<Texture> TextureProgress::get_over_texture() const{
+
+ return over;
+
+}
+
+Size2 TextureProgress::get_minimum_size() const {
+
+ if (under.is_valid())
+ return under->get_size();
+ else if (over.is_valid())
+ return over->get_size();
+ else if (progress.is_valid())
+ return progress->get_size();
+
+ return Size2(1,1);
+}
+
+void TextureProgress::set_progress_texture(const Ref<Texture>& p_texture) {
+
+ progress=p_texture;
+ update();
+ minimum_size_changed();
+}
+
+Ref<Texture> TextureProgress::get_progress_texture() const{
+
+ return progress;
+
+}
+
+
+void TextureProgress::_notification(int p_what){
+
+ switch(p_what) {
+
+ case NOTIFICATION_DRAW: {
+
+
+ if (under.is_valid())
+ draw_texture(under,Point2());
+ if (progress.is_valid()) {
+ Size2 s = progress->get_size();
+ draw_texture_rect_region(progress,Rect2(Point2(),Size2(s.x*get_unit_value(),s.y)),Rect2(Point2(),Size2(s.x*get_unit_value(),s.y)));
+ }
+ if (over.is_valid())
+ draw_texture(over,Point2());
+
+ } break;
+ }
+}
+
+void TextureProgress::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_under_texture","tex"),&TextureProgress::set_under_texture);
+ ObjectTypeDB::bind_method(_MD("get_under_texture"),&TextureProgress::get_under_texture);
+
+ ObjectTypeDB::bind_method(_MD("set_progress_texture","tex"),&TextureProgress::set_progress_texture);
+ ObjectTypeDB::bind_method(_MD("get_progress_texture"),&TextureProgress::get_progress_texture);
+
+ ObjectTypeDB::bind_method(_MD("set_over_texture","tex"),&TextureProgress::set_over_texture);
+ ObjectTypeDB::bind_method(_MD("get_over_texture"),&TextureProgress::get_over_texture);
+
+ ADD_PROPERTY( PropertyInfo(Variant::OBJECT,"texture/under",PROPERTY_HINT_RESOURCE_TYPE,"Texture"),_SCS("set_under_texture"),_SCS("get_under_texture"));
+ ADD_PROPERTY( PropertyInfo(Variant::OBJECT,"texture/over",PROPERTY_HINT_RESOURCE_TYPE,"Texture"),_SCS("set_over_texture"),_SCS("get_over_texture"));
+ ADD_PROPERTY( PropertyInfo(Variant::OBJECT,"texture/progress",PROPERTY_HINT_RESOURCE_TYPE,"Texture"),_SCS("set_progress_texture"),_SCS("get_progress_texture"));
+
+}
+
+
+TextureProgress::TextureProgress()
+{
+}
diff --git a/scene/gui/texture_progress.h b/scene/gui/texture_progress.h
new file mode 100644
index 0000000000..93a0d1046c
--- /dev/null
+++ b/scene/gui/texture_progress.h
@@ -0,0 +1,62 @@
+/*************************************************************************/
+/* texture_progress.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TEXTURE_PROGRESS_H
+#define TEXTURE_PROGRESS_H
+
+#include "scene/gui/range.h"
+
+class TextureProgress : public Range {
+
+ OBJ_TYPE( TextureProgress, Range );
+
+ Ref<Texture> under;
+ Ref<Texture> progress;
+ Ref<Texture> over;
+
+protected:
+
+ static void _bind_methods();
+ void _notification(int p_what);
+public:
+
+ void set_under_texture(const Ref<Texture>& p_texture);
+ Ref<Texture> get_under_texture() const;
+
+ void set_progress_texture(const Ref<Texture>& p_texture);
+ Ref<Texture> get_progress_texture() const;
+
+ void set_over_texture(const Ref<Texture>& p_texture);
+ Ref<Texture> get_over_texture() const;
+
+ Size2 get_minimum_size() const;
+
+ TextureProgress();
+};
+
+#endif // TEXTURE_PROGRESS_H
diff --git a/scene/gui/tool_button.cpp b/scene/gui/tool_button.cpp
new file mode 100644
index 0000000000..e0ba30a202
--- /dev/null
+++ b/scene/gui/tool_button.cpp
@@ -0,0 +1,33 @@
+/*************************************************************************/
+/* tool_button.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "tool_button.h"
+
+ToolButton::ToolButton() {
+ set_flat(true);
+}
diff --git a/scene/gui/tool_button.h b/scene/gui/tool_button.h
new file mode 100644
index 0000000000..9b1664c3fb
--- /dev/null
+++ b/scene/gui/tool_button.h
@@ -0,0 +1,40 @@
+/*************************************************************************/
+/* tool_button.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TOOL_BUTTON_H
+#define TOOL_BUTTON_H
+
+#include "scene/gui/button.h"
+
+class ToolButton : public Button {
+ OBJ_TYPE(ToolButton,Button);
+public:
+ ToolButton();
+};
+
+#endif // TOOL_BUTTON_H
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
new file mode 100644
index 0000000000..ca6a1e812e
--- /dev/null
+++ b/scene/gui/tree.cpp
@@ -0,0 +1,3185 @@
+/*************************************************************************/
+/* tree.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "tree.h"
+#include "print_string.h"
+#include "os/os.h"
+#include "os/keyboard.h"
+#include "globals.h"
+
+
+
+
+void TreeItem::move_to_top() {
+
+ if (!parent || parent->childs==this)
+ return; //already on top
+ TreeItem *prev = get_prev();
+ prev->next=next;
+ next=parent->childs;
+ parent->childs=this;
+}
+
+void TreeItem::move_to_bottom() {
+
+ if (!parent || !next)
+ return;
+
+ while(next) {
+
+ if (parent->childs==this)
+ parent->childs=next;
+ TreeItem *n=next;
+ next=n->next;
+ n->next=this;
+ }
+}
+
+
+Size2 TreeItem::Cell::get_icon_size() const {
+
+ if (icon.is_null())
+ return Size2();
+ if (icon_region==Rect2i())
+ return icon->get_size();
+ else
+ return icon_region.size;
+}
+void TreeItem::Cell::draw_icon(const RID& p_where, const Point2& p_pos, const Size2& p_size) const{
+
+ if (icon.is_null())
+ return;
+
+ Size2i dsize=(p_size==Size2()) ? icon->get_size() : p_size;
+
+ if (icon_region==Rect2i()) {
+
+ icon->draw_rect_region(p_where,Rect2(p_pos,dsize),Rect2(Point2(),icon->get_size()));
+ } else {
+
+ icon->draw_rect_region(p_where,Rect2(p_pos,dsize),icon_region);
+ }
+
+}
+
+
+void TreeItem::_changed_notify(int p_cell) {
+
+ tree->item_changed(p_cell,this);
+}
+
+void TreeItem::_changed_notify() {
+
+ tree->item_changed(-1,this);
+}
+
+void TreeItem::_cell_selected(int p_cell) {
+
+ tree->item_selected(p_cell,this);
+}
+
+void TreeItem::_cell_deselected(int p_cell) {
+
+ tree->item_deselected(p_cell,this);
+}
+
+/* cell mode */
+void TreeItem::set_cell_mode( int p_column, TreeCellMode p_mode ) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ Cell&c=cells[p_column];
+ c.mode=p_mode;
+ c.min=0;
+ c.max=100;
+ c.step=1;
+ c.val=0;
+ c.checked=false;
+ c.icon=Ref<Texture>();
+ c.text="";
+ c.icon_max_w=0;
+ _changed_notify(p_column);
+}
+
+TreeItem::TreeCellMode TreeItem::get_cell_mode( int p_column ) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), TreeItem::CELL_MODE_STRING );
+ return cells[p_column].mode;
+}
+
+
+/* check mode */
+void TreeItem::set_checked(int p_column,bool p_checked) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].checked=p_checked;
+ _changed_notify(p_column);
+
+}
+
+bool TreeItem::is_checked(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), false );
+ return cells[p_column].checked;
+}
+
+
+void TreeItem::set_text(int p_column,String p_text) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].text=p_text;
+
+ if (cells[p_column].mode==TreeItem::CELL_MODE_RANGE) {
+
+ cells[p_column].min=0;
+ cells[p_column].max=p_text.get_slice_count(",");
+ cells[p_column].step=0;
+ }
+ _changed_notify(p_column);
+
+}
+
+String TreeItem::get_text(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), "" );
+ return cells[p_column].text;
+
+}
+
+
+void TreeItem::set_icon(int p_column,const Ref<Texture>& p_icon) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].icon=p_icon;
+ _changed_notify(p_column);
+
+}
+
+Ref<Texture> TreeItem::get_icon(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), Ref<Texture>() );
+ return cells[p_column].icon;
+
+}
+
+void TreeItem::set_icon_region(int p_column,const Rect2& p_icon_region) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].icon_region=p_icon_region;
+ _changed_notify(p_column);
+}
+
+Rect2 TreeItem::get_icon_region(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), Rect2() );
+ return cells[p_column].icon_region;
+}
+
+void TreeItem::set_icon_max_width(int p_column,int p_max) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].icon_max_w=p_max;
+ _changed_notify(p_column);
+}
+
+int TreeItem::get_icon_max_width(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), 0);
+ return cells[p_column].icon_max_w;
+
+}
+
+
+/* range works for mode number or mode combo */
+void TreeItem::set_range(int p_column,double p_value) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ if (cells[p_column].step>0)
+ p_value=Math::stepify( p_value, cells[p_column].step );
+ if (p_value<cells[p_column].min)
+ p_value=cells[p_column].min;
+ if (p_value>cells[p_column].max)
+ p_value=cells[p_column].max;
+
+ cells[p_column].val=p_value;
+ _changed_notify(p_column);
+
+}
+
+double TreeItem::get_range(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), 0 );
+ return cells[p_column].val;
+}
+
+
+bool TreeItem::is_range_exponential(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), false);
+ return cells[p_column].expr;
+
+}
+void TreeItem::set_range_config(int p_column,double p_min,double p_max,double p_step,bool p_exp) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].min=p_min;
+ cells[p_column].max=p_max;
+ cells[p_column].step=p_step;
+ cells[p_column].expr=p_exp;
+ _changed_notify(p_column);
+
+}
+
+void TreeItem::get_range_config(int p_column,double& r_min,double& r_max,double &r_step) const {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ r_min=cells[p_column].min;
+ r_max=cells[p_column].max;
+ r_step=cells[p_column].step;
+
+}
+
+void TreeItem::set_metadata(int p_column,const Variant& p_meta) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].meta=p_meta;
+
+}
+
+Variant TreeItem::get_metadata(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), Variant() );
+
+ return cells[p_column].meta;
+}
+
+void TreeItem::set_custom_draw(int p_column,Object *p_object,const StringName& p_callback) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ ERR_FAIL_NULL(p_object);
+
+ cells[p_column].custom_draw_obj=p_object->get_instance_ID();
+ cells[p_column].custom_draw_callback=p_callback;
+
+}
+
+void TreeItem::set_collapsed(bool p_collapsed) {
+
+ if (collapsed==p_collapsed)
+ return;
+ collapsed=p_collapsed;
+ TreeItem *ci = tree->selected_item;
+ if (ci) {
+
+ while (ci && ci!=this) {
+
+ ci=ci->parent;
+ }
+ if (ci) { // collapsing cursor/selectd, move it!
+
+ if (tree->select_mode==Tree::SELECT_MULTI) {
+
+ tree->selected_item=this;
+ emit_signal("cell_selected");
+ } else {
+
+ select(tree->selected_col);
+ }
+
+ tree->update();
+ }
+
+ }
+
+ _changed_notify();
+ if (tree)
+ tree->emit_signal("item_collapsed",this);
+
+}
+
+bool TreeItem::is_collapsed() {
+
+ return collapsed;
+}
+
+
+TreeItem *TreeItem::get_next() {
+
+ return next;
+}
+
+TreeItem *TreeItem::get_prev() {
+
+ if (!parent || parent->childs==this)
+ return NULL;
+
+ TreeItem *prev = parent->childs;
+ while(prev && prev->next!=this)
+ prev=prev->next;
+
+ return prev;
+}
+
+TreeItem *TreeItem::get_parent() {
+
+ return parent;
+}
+
+TreeItem *TreeItem::get_children() {
+
+ return childs;
+}
+
+
+TreeItem *TreeItem::get_prev_visible() {
+
+ TreeItem *current=this;
+
+ TreeItem *prev = current->get_prev();
+
+
+ if (!prev) {
+
+ current=current->parent;
+ if (!current || (current==tree->root && tree->hide_root))
+ return NULL;
+ } else {
+
+
+ current=prev;
+ while( !current->collapsed && current->childs ) {
+ //go to the very end
+
+ current = current->childs;
+ while (current->next)
+ current=current->next;
+ }
+
+
+ }
+
+ return current;
+}
+
+
+TreeItem *TreeItem::get_next_visible() {
+
+ TreeItem *current=this;
+
+
+ if (!current->collapsed && current->childs) {
+
+ current=current->childs;
+
+ } else if (current->next) {
+
+ current=current->next;
+ } else {
+
+ while(current && !current->next) {
+
+ current=current->parent;
+ }
+
+ if (current==NULL)
+ return NULL;
+ else
+ current=current->next;
+ }
+
+ return current;
+}
+
+void TreeItem::remove_child(TreeItem *p_item) {
+
+ ERR_FAIL_NULL(p_item);
+ TreeItem **c=&childs;
+
+ while (*c) {
+
+ if ( (*c) == p_item ) {
+
+ TreeItem *aux = *c;
+
+ *c=(*c)->next;
+
+ aux->parent = NULL;
+ return;
+ }
+
+ c=&(*c)->next;
+ }
+
+ ERR_FAIL();
+}
+
+
+void TreeItem::set_selectable(int p_column,bool p_selectable) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].selectable=p_selectable;
+}
+
+bool TreeItem::is_selectable(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), false );
+ return cells[p_column].selectable;
+
+}
+
+bool TreeItem::is_selected(int p_column) {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), false );
+ return cells[p_column].selectable && cells[p_column].selected;
+}
+
+void TreeItem::set_as_cursor(int p_column) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ if (!tree)
+ return;
+ if (tree->select_mode!=Tree::SELECT_MULTI)
+ return;
+ tree->selected_item=this;
+ tree->selected_col=p_column;
+ tree->update();
+}
+
+void TreeItem::select(int p_column) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ _cell_selected(p_column);
+}
+
+void TreeItem::deselect(int p_column) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ _cell_deselected(p_column);
+}
+
+void TreeItem::add_button(int p_column,const Ref<Texture>& p_button,int p_id) {
+
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ ERR_FAIL_COND(!p_button.is_valid());
+ TreeItem::Cell::Button button;
+ button.texture=p_button;
+ if (p_id<0)
+ p_id=cells[p_column].buttons.size();
+ button.id=p_id;
+ cells[p_column].buttons.push_back(button);
+ _changed_notify(p_column);
+}
+
+int TreeItem::get_button_count(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), -1 );
+ return cells[p_column].buttons.size();
+
+}
+Ref<Texture> TreeItem::get_button(int p_column,int p_idx) const {
+ ERR_FAIL_INDEX_V( p_column, cells.size(), Ref<Texture>() );
+ ERR_FAIL_INDEX_V( p_idx, cells[p_column].buttons.size(), Ref<Texture>() );
+ return cells[p_column].buttons[p_idx].texture;
+
+}
+int TreeItem::get_button_id(int p_column,int p_idx) const {
+ ERR_FAIL_INDEX_V( p_column, cells.size(), -1 );
+ ERR_FAIL_INDEX_V( p_idx, cells[p_column].buttons.size(), -1 );
+ return cells[p_column].buttons[p_idx].id;
+
+}
+void TreeItem::erase_button(int p_column,int p_idx) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ ERR_FAIL_INDEX( p_idx, cells[p_column].buttons.size() );
+ cells[p_column].buttons.remove(p_idx);
+ _changed_notify(p_column);
+}
+
+int TreeItem::get_button_by_id(int p_column,int p_id) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(),-1 );
+ for(int i=0;i<cells[p_column].buttons.size();i++) {
+
+ if (cells[p_column].buttons[i].id==p_id)
+ return i;
+ }
+
+ return -1;
+}
+void TreeItem::set_button(int p_column,int p_idx,const Ref<Texture>& p_button){
+
+ ERR_FAIL_COND( p_button.is_null() );
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ ERR_FAIL_INDEX( p_idx, cells[p_column].buttons.size() );
+ cells[p_column].buttons[p_idx].texture=p_button;
+ _changed_notify(p_column);
+
+}
+
+void TreeItem::set_editable(int p_column,bool p_editable) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].editable=p_editable;
+ _changed_notify(p_column);
+}
+
+bool TreeItem::is_editable(int p_column) {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), false );
+ return cells[p_column].editable;
+}
+
+
+void TreeItem::set_custom_color(int p_column,const Color& p_color) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].custom_color=true;
+ cells[p_column].color=p_color;
+ _changed_notify(p_column);
+}
+
+void TreeItem::clear_custom_color(int p_column) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].custom_color=false;
+ cells[p_column].color=Color();;
+ _changed_notify(p_column);
+}
+
+
+void TreeItem::set_tooltip(int p_column, const String& p_tooltip) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].tooltip=p_tooltip;
+
+}
+
+String TreeItem::get_tooltip(int p_column) const{
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), "" );
+ return cells[p_column].tooltip;
+}
+
+void TreeItem::set_custom_bg_color(int p_column,const Color& p_color) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].custom_bg_color=true;
+ cells[p_column].bg_color=p_color;
+ _changed_notify(p_column);
+}
+
+void TreeItem::clear_custom_bg_color(int p_column) {
+
+ ERR_FAIL_INDEX( p_column, cells.size() );
+ cells[p_column].custom_bg_color=false;
+ cells[p_column].bg_color=Color();;
+ _changed_notify(p_column);
+}
+
+Color TreeItem::get_custom_bg_color(int p_column) const {
+
+ ERR_FAIL_INDEX_V( p_column, cells.size(), Color() );
+ if (!cells[p_column].custom_bg_color)
+ return Color();
+ return cells[p_column].bg_color;
+
+}
+
+void TreeItem::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_cell_mode","column","mode"),&TreeItem::set_cell_mode);
+ ObjectTypeDB::bind_method(_MD("get_cell_mode","column"),&TreeItem::get_cell_mode);
+
+ ObjectTypeDB::bind_method(_MD("set_checked","column","checked"),&TreeItem::set_checked);
+ ObjectTypeDB::bind_method(_MD("is_checked","column"),&TreeItem::is_checked);
+
+ ObjectTypeDB::bind_method(_MD("set_text","column","text"),&TreeItem::set_text);
+ ObjectTypeDB::bind_method(_MD("get_text","column"),&TreeItem::get_text);
+
+ ObjectTypeDB::bind_method(_MD("set_icon","column","texture:Texture"),&TreeItem::set_icon);
+ ObjectTypeDB::bind_method(_MD("get_icon:Texture","column"),&TreeItem::get_icon);
+
+ ObjectTypeDB::bind_method(_MD("set_icon_region","column","region"),&TreeItem::set_icon_region);
+ ObjectTypeDB::bind_method(_MD("get_icon_region","column"),&TreeItem::get_icon_region);
+
+ ObjectTypeDB::bind_method(_MD("set_icon_max_width","column","width"),&TreeItem::set_icon_max_width);
+ ObjectTypeDB::bind_method(_MD("get_icon_max_width","column"),&TreeItem::get_icon_max_width);
+
+ ObjectTypeDB::bind_method(_MD("set_range","column","value"),&TreeItem::set_range);
+ ObjectTypeDB::bind_method(_MD("get_range","column"),&TreeItem::get_range);
+ ObjectTypeDB::bind_method(_MD("set_range_config","column","min","max","step","expr"),&TreeItem::set_range_config,DEFVAL(false));
+ ObjectTypeDB::bind_method(_MD("get_range_config","column"),&TreeItem::_get_range_config);
+
+ ObjectTypeDB::bind_method(_MD("set_metadata","column","meta"),&TreeItem::set_metadata);
+ ObjectTypeDB::bind_method(_MD("get_metadata","column"),&TreeItem::get_metadata);
+
+ ObjectTypeDB::bind_method(_MD("set_custom_draw","column","object","callback"),&TreeItem::set_custom_draw);
+
+ ObjectTypeDB::bind_method(_MD("set_collapsed","enable"),&TreeItem::set_collapsed);
+ ObjectTypeDB::bind_method(_MD("is_collapsed"),&TreeItem::is_collapsed);
+
+ ObjectTypeDB::bind_method(_MD("get_next:TreeItem"),&TreeItem::get_next);
+ ObjectTypeDB::bind_method(_MD("get_prev:TreeItem"),&TreeItem::get_prev);
+ ObjectTypeDB::bind_method(_MD("get_parent:TreeItem"),&TreeItem::get_parent);
+ ObjectTypeDB::bind_method(_MD("get_children:TreeItem"),&TreeItem::get_children);
+
+ ObjectTypeDB::bind_method(_MD("get_next_visible:TreeItem"),&TreeItem::get_next_visible);
+ ObjectTypeDB::bind_method(_MD("get_prev_visible:TreeItem"),&TreeItem::get_prev_visible);
+
+ ObjectTypeDB::bind_method(_MD("remove_child:TreeItem","child"),&TreeItem::_remove_child);
+
+ ObjectTypeDB::bind_method(_MD("set_selectable","column","selectable"),&TreeItem::set_selectable);
+ ObjectTypeDB::bind_method(_MD("is_selectable","column"),&TreeItem::is_selectable);
+
+ ObjectTypeDB::bind_method(_MD("is_selected","column"),&TreeItem::is_selected);
+ ObjectTypeDB::bind_method(_MD("select","column"),&TreeItem::select);
+ ObjectTypeDB::bind_method(_MD("deselect","column"),&TreeItem::deselect);
+
+ ObjectTypeDB::bind_method(_MD("set_editable","column","enabled"),&TreeItem::set_editable);
+ ObjectTypeDB::bind_method(_MD("is_editable","column"),&TreeItem::is_editable);
+
+ ObjectTypeDB::bind_method(_MD("set_custom_color","column","color"),&TreeItem::set_custom_color);
+ ObjectTypeDB::bind_method(_MD("clear_custom_color","column"),&TreeItem::clear_custom_color);
+
+ ObjectTypeDB::bind_method(_MD("set_custom_bg_color","column","color"),&TreeItem::set_custom_bg_color);
+ ObjectTypeDB::bind_method(_MD("clear_custom_bg_color","column"),&TreeItem::clear_custom_bg_color);
+ ObjectTypeDB::bind_method(_MD("get_custom_bg_color","column"),&TreeItem::get_custom_bg_color);
+
+ ObjectTypeDB::bind_method(_MD("add_button","column","button:Texture"),&TreeItem::add_button);
+ ObjectTypeDB::bind_method(_MD("get_button_count","column"),&TreeItem::get_button_count);
+ ObjectTypeDB::bind_method(_MD("get_button:Texture","column","button_idx"),&TreeItem::get_button);
+ ObjectTypeDB::bind_method(_MD("erase_button","column","button_idx"),&TreeItem::erase_button);
+
+ ObjectTypeDB::bind_method(_MD("set_tooltip","column","tooltip"),&TreeItem::set_tooltip);
+ ObjectTypeDB::bind_method(_MD("get_tooltip","column"),&TreeItem::get_tooltip);
+
+ ObjectTypeDB::bind_method(_MD("move_to_top"),&TreeItem::move_to_top);
+ ObjectTypeDB::bind_method(_MD("move_to_bottom"),&TreeItem::move_to_bottom);
+
+ BIND_CONSTANT( CELL_MODE_STRING );
+ BIND_CONSTANT( CELL_MODE_CHECK );
+ BIND_CONSTANT( CELL_MODE_RANGE );
+ BIND_CONSTANT( CELL_MODE_ICON );
+ BIND_CONSTANT( CELL_MODE_CUSTOM );
+
+
+}
+
+void TreeItem::clear_children() {
+
+ TreeItem *c=childs;
+ while (c) {
+
+ TreeItem *aux=c;
+ c=c->get_next();
+ aux->parent=0; // so it wont try to recursively autoremove from me in here
+ memdelete( aux );
+ }
+
+ childs = 0;
+};
+
+TreeItem::TreeItem(Tree *p_tree) {
+
+ tree=p_tree;
+ collapsed=false;
+
+ parent=0; // parent item
+ next=0; // next in list
+ childs=0; //child items
+
+}
+
+TreeItem::~TreeItem() {
+
+ clear_children();
+
+ if (parent)
+ parent->remove_child(this);
+
+ if (tree && tree->root==this) {
+
+ tree->root=0;
+ }
+
+ if (tree && tree->popup_edited_item==this)
+ tree->popup_edited_item=NULL;
+
+ if (tree && tree->selected_item==this)
+ tree->selected_item=NULL;
+
+ if (tree && tree->edited_item==this)
+ tree->edited_item=NULL;
+
+}
+
+/**********************************************/
+/**********************************************/
+/**********************************************/
+/**********************************************/
+/**********************************************/
+/**********************************************/
+
+
+
+
+
+void Tree::update_cache() {
+
+ cache.font = get_font("font");
+ cache.tb_font = get_font("title_button_font");
+ cache.bg = get_stylebox("bg");
+ cache.selected= get_stylebox("selected");
+ cache.selected_focus= get_stylebox("selected_focus");
+ cache.cursor = get_stylebox("cursor");
+ cache.cursor_unfocus = get_stylebox("cursor_unfocused");
+ cache.button_pressed= get_stylebox("button_pressed");
+
+ cache.checked=get_icon("checked");
+ cache.unchecked=get_icon("unchecked");
+ cache.arrow_collapsed=get_icon("arrow_collapsed");
+ cache.arrow =get_icon("arrow");
+ cache.select_arrow =get_icon("select_arrow");
+ cache.updown=get_icon("updown");
+
+ cache.font_color=get_color("font_color");
+ cache.font_color_selected=get_color("font_color_selected");
+ cache.guide_color=get_color("guide_color");
+ cache.hseparation=get_constant("hseparation");
+ cache.vseparation=get_constant("vseparation");
+ cache.item_margin=get_constant("item_margin");
+ cache.button_margin=get_constant("button_margin");
+ cache.guide_width=get_constant("guide_width");
+
+ Ref<StyleBox> title_button;
+ Ref<StyleBox> title_button_hover;
+ Ref<StyleBox> title_button_pressed;
+ Color title_button_color;
+
+ cache.title_button = get_stylebox("title_button_normal");
+ cache.title_button_pressed = get_stylebox("title_button_pressed");
+ cache.title_button_hover = get_stylebox("title_button_hover");
+ cache.title_button_color = get_color("title_button_color");
+
+ v_scroll->set_custom_step(cache.font->get_height());
+
+}
+
+int Tree::compute_item_height(TreeItem *p_item) const {
+
+ if (p_item==root && hide_root)
+ return 0;
+
+ int height=cache.font->get_height();
+
+
+ for (int i=0;i<columns.size();i++) {
+
+
+ for(int j=0;j<p_item->cells[i].buttons.size();j++) {
+
+
+ Size2i s;// = cache.button_pressed->get_minimum_size();
+ s+= p_item->cells[i].buttons[j].texture->get_size();
+ if (s.height>height)
+ height=s.height;
+ }
+
+ switch(p_item->cells[i].mode) {
+
+ case TreeItem::CELL_MODE_CHECK: {
+
+ int check_icon_h = cache.checked->get_height();
+ if (height<check_icon_h)
+ height=check_icon_h;
+
+
+
+ }
+ case TreeItem::CELL_MODE_STRING:
+ case TreeItem::CELL_MODE_CUSTOM:
+ case TreeItem::CELL_MODE_ICON: {
+
+ Ref<Texture> icon = p_item->cells[i].icon;
+ if (!icon.is_null()) {
+
+ Size2i s = p_item->cells[i].get_icon_size();
+ if (p_item->cells[i].icon_max_w>0 && s.width > p_item->cells[i].icon_max_w ) {
+ s.height=s.height * p_item->cells[i].icon_max_w / s.width;
+ }
+ if (s.height > height )
+ height=s.height;
+ }
+
+ } break;
+ default: {}
+ }
+ }
+
+
+ height += cache.vseparation;
+
+ return height;
+
+}
+
+int Tree::get_item_height(TreeItem *p_item) const {
+
+ int height=compute_item_height(p_item);
+ height+=cache.vseparation;
+
+ if (!p_item->collapsed) { /* if not collapsed, check the childs */
+
+ TreeItem *c=p_item->childs;
+
+ while (c) {
+
+ height += get_item_height( c );
+
+ c=c->next;
+ }
+ }
+
+ return height;
+}
+
+
+void Tree::draw_item_rect(const TreeItem::Cell& p_cell,const Rect2i& p_rect,const Color& p_color) {
+
+ Rect2i rect=p_rect;
+
+ RID ci = get_canvas_item();
+ if (!p_cell.icon.is_null()) {
+ Size2i bmsize = p_cell.get_icon_size();
+
+ if (p_cell.icon_max_w>0 && bmsize.width > p_cell.icon_max_w) {
+ bmsize.height = bmsize.height * p_cell.icon_max_w / bmsize.width;
+ bmsize.width=p_cell.icon_max_w;
+ }
+
+ p_cell.draw_icon(ci,rect.pos + Size2i(0,Math::floor((rect.size.y-bmsize.y)/2)),bmsize);
+ rect.pos.x+=bmsize.x+cache.hseparation;
+ rect.size.x-=bmsize.x+cache.hseparation;
+
+ }
+
+// if (p_tool)
+// rect.size.x-=Math::floor(rect.size.y/2);
+
+ Ref<Font> font = cache.font;
+
+ rect.pos.y+=Math::floor((rect.size.y-font->get_height())/2.0) +font->get_ascent();
+ font->draw(ci,rect.pos,p_cell.text,p_color,rect.size.x);
+
+}
+
+#if 0
+void Tree::draw_item_text(String p_text,const Ref<Texture>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color) {
+
+ RID ci = get_canvas_item();
+ if (!p_icon.is_null()) {
+ Size2i bmsize = p_icon->get_size();
+ if (p_icon_max_w>0 && bmsize.width > p_icon_max_w) {
+ bmsize.height = bmsize.height * p_icon_max_w / bmsize.width;
+ bmsize.width=p_icon_max_w;
+ }
+
+ draw_texture_rect(p_icon,Rect2(p_rect.pos + Size2i(0,Math::floor((p_rect.size.y-bmsize.y)/2)),bmsize));
+ p_rect.pos.x+=bmsize.x+cache.hseparation;
+ p_rect.size.x-=bmsize.x+cache.hseparation;
+
+ }
+
+ if (p_tool)
+ p_rect.size.x-=Math::floor(p_rect.size.y/2);
+
+ Ref<Font> font = cache.font;
+
+ p_rect.pos.y+=Math::floor((p_rect.size.y-font->get_height())/2.0) +font->get_ascent();
+ font->draw(ci,p_rect.pos,p_text,p_color,p_rect.size.x);
+}
+#endif
+int Tree::draw_item(const Point2i& p_pos,const Point2& p_draw_ofs, const Size2& p_draw_size,TreeItem *p_item) {
+
+ if (p_pos.y-cache.offset.y > (p_draw_size.height))
+ return -1; //draw no more!
+
+ RID ci = get_canvas_item();
+
+ int htotal=0;
+
+ int label_h=compute_item_height( p_item );
+
+ /* Calculate height of the label part */
+ label_h+=cache.vseparation;
+
+ /* Draw label, if height fits */
+
+ Point2i guide_from;
+
+ bool skip=(p_item==root && hide_root);
+ // printf("skip (%p == %p && %i) %i\n",p_item,root,hide_root,skip);
+
+
+ if (!skip && (p_pos.y+label_h-cache.offset.y)>0) {
+
+ // printf("entering\n");
+
+ int height=label_h;
+
+ Point2i guide_space=Point2i( cache.guide_width , height );
+
+ if (p_item->childs) { //has childs, draw the guide box
+
+ Ref<Texture> arrow;
+
+ if (p_item->collapsed) {
+
+ arrow=cache.arrow_collapsed;
+ } else {
+ arrow=cache.arrow;
+
+ }
+
+ arrow->draw( ci , p_pos+p_draw_ofs+Point2i(0,(label_h-arrow->get_height())/2)-cache.offset);
+
+
+ }
+
+ //draw separation.
+// if (p_item->get_parent()!=root || !hide_root)
+
+ Ref<Font> font = cache.font;
+
+ int font_ascent=font->get_ascent();
+
+ int ofs = p_pos.x + cache.item_margin;
+ for (int i=0;i<columns.size();i++) {
+
+ int w = get_column_width(i);
+
+ if (i==0) {
+
+ w-=ofs;
+
+ if (w<=0) {
+
+ ofs=get_column_width(0);
+ continue;
+ }
+ } else {
+
+ ofs+=cache.hseparation;
+ w-=cache.hseparation;
+ }
+
+ int bw=0;
+ for(int j=p_item->cells[i].buttons.size()-1;j>=0;j--) {
+ Ref<Texture> b=p_item->cells[i].buttons[j].texture;
+ Size2 s = b->get_size() + cache.button_pressed->get_minimum_size();
+
+ Point2i o = Point2i( ofs+w-s.width, p_pos.y )-cache.offset+p_draw_ofs;
+
+ if (cache.click_type==Cache::CLICK_BUTTON && cache.click_item==p_item && cache.click_column==i) {
+ //being pressed
+ cache.button_pressed->draw(get_canvas_item(),Rect2(o,s));
+ }
+
+ o.y+=(label_h-s.height)/2;
+ o+=cache.button_pressed->get_offset();
+ b->draw(ci,o);
+ w-=s.width+cache.button_margin;
+ bw+=s.width+cache.button_margin;
+ }
+
+ Rect2i item_rect = Rect2i( Point2i( ofs, p_pos.y )-cache.offset+p_draw_ofs, Size2i( w, label_h ));
+ Rect2i cell_rect=item_rect;
+ if (i!=0) {
+ cell_rect.pos.x-=cache.hseparation;
+ cell_rect.size.x+=cache.hseparation;
+ }
+
+
+
+ VisualServer::get_singleton()->canvas_item_add_line(ci,Point2i(cell_rect.pos.x,cell_rect.pos.y+cell_rect.size.height),cell_rect.pos+cell_rect.size,cache.guide_color,1);
+
+ if (i==0) {
+
+ if (p_item->cells[0].selected && select_mode==SELECT_ROW) {
+ Rect2i row_rect = Rect2i( Point2i( cache.bg->get_margin(MARGIN_LEFT), item_rect.pos.y), Size2i( get_size().width-cache.bg->get_minimum_size().width, item_rect.size.y ));
+ //Rect2 r = Rect2i(row_rect.pos,row_rect.size);
+ //r.grow(cache.selected->get_margin(MARGIN_LEFT));
+ if (has_focus())
+ cache.selected_focus->draw(ci,row_rect );
+ else
+ cache.selected->draw(ci,row_rect );
+ }
+
+ }
+
+ if (p_item->cells[i].selected && select_mode!=SELECT_ROW) {
+
+ Rect2i r(item_rect.pos,item_rect.size);
+ //r.grow(cache.selected->get_margin(MARGIN_LEFT));
+ if (has_focus())
+ cache.selected_focus->draw(ci,r );
+ else
+ cache.selected->draw(ci,r );
+ }
+
+ if (p_item->cells[i].custom_bg_color) {
+
+ VisualServer::get_singleton()->canvas_item_add_rect(ci,cell_rect,p_item->cells[i].bg_color);
+ }
+
+ Color col=p_item->cells[i].custom_color?p_item->cells[i].color:get_color( p_item->cells[i].selected?"font_color_selected":"font_color");
+
+ Point2i text_pos=item_rect.pos;
+ text_pos.y+=Math::floor((item_rect.size.y-font->get_height())/2) + font_ascent;
+
+ switch (p_item->cells[i].mode) {
+
+ case TreeItem::CELL_MODE_STRING: {
+
+ draw_item_rect(p_item->cells[i],item_rect,col);
+ } break;
+ case TreeItem::CELL_MODE_CHECK: {
+
+ Ref<Texture> checked = cache.checked;
+ Ref<Texture> unchecked = cache.unchecked;
+ Point2i check_ofs=item_rect.pos;
+ check_ofs.y+=Math::floor((item_rect.size.y-checked->get_height())/2);
+
+ if (p_item->cells[i].checked) {
+
+ checked->draw( ci, check_ofs );
+ } else {
+ unchecked->draw( ci, check_ofs );
+
+ }
+
+ int check_w = checked->get_width()+cache.hseparation;
+
+ text_pos.x+=check_w;
+
+ item_rect.size.x-=check_w;
+ item_rect.pos.x+=check_w;
+
+ draw_item_rect(p_item->cells[i],item_rect,col);
+
+ //font->draw( ci, text_pos, p_item->cells[i].text, col,item_rect.size.x-check_w );
+
+ } break;
+ case TreeItem::CELL_MODE_RANGE: {
+
+ if (p_item->cells[i].text!="") {
+
+ if (!p_item->cells[i].editable)
+ break;
+
+ int option = (int)p_item->cells[i].val;
+
+ String s = p_item->cells[i].text;
+ s=s.get_slice(",",option);
+
+ Ref<Texture> downarrow = cache.select_arrow;
+
+ font->draw(ci, text_pos, s, col,item_rect.size.x-downarrow->get_width() );
+
+ //?
+ Point2i arrow_pos=item_rect.pos;
+ arrow_pos.x+=item_rect.size.x-downarrow->get_width();
+ arrow_pos.y+=Math::floor(((item_rect.size.y-downarrow->get_height()))/2.0);
+
+ downarrow->draw( ci, arrow_pos );
+ } else {
+
+ Ref<Texture> updown = cache.updown;
+
+ String valtext = String::num( p_item->cells[i].val, Math::decimals( p_item->cells[i].step ) );
+ font->draw( ci, text_pos, valtext, col, item_rect.size.x-updown->get_width());
+
+ if (!p_item->cells[i].editable)
+ break;
+
+ Point2i updown_pos=item_rect.pos;
+ updown_pos.x+=item_rect.size.x-updown->get_width();
+ updown_pos.y+=Math::floor(((item_rect.size.y-updown->get_height()))/2.0);
+
+ updown->draw( ci, updown_pos );
+ }
+
+ } break;
+ case TreeItem::CELL_MODE_ICON: {
+
+ if (p_item->cells[i].icon.is_null())
+ break;
+ Size2i icon_size = p_item->cells[i].get_icon_size();
+ if (p_item->cells[i].icon_max_w>0 && icon_size.width >p_item->cells[i].icon_max_w) {
+ icon_size.height = icon_size.height * p_item->cells[i].icon_max_w / icon_size.width;
+ icon_size.width=p_item->cells[i].icon_max_w;
+ }
+
+ Point2i icon_ofs = (item_rect.size-icon_size)/2;
+ icon_ofs+=item_rect.pos;
+
+
+ draw_texture_rect(p_item->cells[i].icon,Rect2(icon_ofs,icon_size));;
+ //p_item->cells[i].icon->draw(ci, icon_ofs);
+
+ } break;
+ case TreeItem::CELL_MODE_CUSTOM: {
+
+ // int option = (int)p_item->cells[i].val;
+
+
+
+ if (p_item->cells[i].custom_draw_obj) {
+
+ Object* cdo = ObjectDB::get_instance(p_item->cells[i].custom_draw_obj);
+ if (cdo)
+ cdo->call(p_item->cells[i].custom_draw_callback,p_item,Rect2(item_rect));
+ }
+
+ if (!p_item->cells[i].editable) {
+
+ draw_item_rect(p_item->cells[i],item_rect,col);
+ break;
+ }
+
+ Ref<Texture> downarrow = cache.select_arrow;
+
+ Rect2i ir=item_rect;
+ ir.size.width-=downarrow->get_width();
+ draw_item_rect(p_item->cells[i],ir,col);
+
+ Point2i arrow_pos=item_rect.pos;
+ arrow_pos.x+=item_rect.size.x-downarrow->get_width();
+ arrow_pos.y+=Math::floor(((item_rect.size.y-downarrow->get_height()))/2.0);
+
+ downarrow->draw( ci, arrow_pos );
+
+ } break;
+ }
+
+ if (i==0) {
+
+ ofs=get_column_width(0);
+ } else {
+
+ ofs+=w+bw;
+ }
+
+ if (select_mode==SELECT_MULTI && selected_item==p_item && selected_col==i) {
+
+ if (has_focus())
+ cache.cursor->draw(ci,cell_rect);
+ else
+ cache.cursor_unfocus->draw(ci,cell_rect);
+ }
+
+ }
+
+ //separator
+ //get_painter()->draw_fill_rect( Point2i(0,pos.y),Size2i(get_size().width,1),color( COLOR_TREE_GRID) );
+
+ //pos=p_pos; //reset pos
+
+ }
+
+
+ Point2 children_pos=p_pos;
+
+ if (!skip) {
+ children_pos.x+=cache.item_margin;
+ htotal+=label_h;
+ children_pos.y+=htotal;
+
+ }
+
+
+ if (!p_item->collapsed) { /* if not collapsed, check the childs */
+
+ TreeItem *c=p_item->childs;
+
+ while (c) {
+
+ int child_h=draw_item(children_pos, p_draw_ofs, p_draw_size, c );
+
+ if (child_h<0)
+ return -1; // break, stop drawing, no need to anymore
+
+ htotal+=child_h;
+ children_pos.y+=child_h;
+ c=c->next;
+ }
+ }
+
+
+ return htotal;
+
+
+}
+
+void Tree::select_single_item(TreeItem *p_selected,TreeItem *p_current,int p_col,TreeItem *p_prev,bool *r_in_range) {
+
+ TreeItem::Cell &selected_cell=p_selected->cells[p_col];
+
+ bool switched=false;
+ if (r_in_range && !*r_in_range && (p_current==p_selected || p_current==p_prev)) {
+ *r_in_range=true;
+ switched=true;
+ }
+
+ for (int i=0;i<columns.size();i++) {
+
+ TreeItem::Cell &c=p_current->cells[i];
+
+ if (!c.selectable)
+ continue;
+
+ if (select_mode==SELECT_ROW) {
+
+
+ if (p_selected==p_current) {
+
+ if (!c.selected) {
+
+ c.selected=true;
+ selected_item=p_selected;
+ selected_col=0;
+ selected_item=p_selected;
+ emit_signal("item_selected");
+ //if (p_col==i)
+ // p_current->selected_signal.call(p_col);
+ }
+
+ } else {
+
+ if (c.selected) {
+
+ c.selected=false;
+ //p_current->deselected_signal.call(p_col);
+ }
+
+ }
+
+ } else if (select_mode==SELECT_SINGLE || select_mode==SELECT_MULTI) {
+
+ if (&selected_cell==&c) {
+
+
+ if (!selected_cell.selected) {
+
+ selected_cell.selected=true;
+
+ selected_item=p_selected;
+ selected_col=i;
+ emit_signal("cell_selected");
+ if (select_mode==SELECT_MULTI)
+ emit_signal("multi_selected",p_current,i,true);
+
+ } else if (select_mode==SELECT_MULTI && (selected_item!=p_selected || selected_col!=i)) {
+
+ selected_item=p_selected;
+ selected_col=i;
+ emit_signal("cell_selected");
+
+ }
+ } else {
+
+
+ if (r_in_range && *r_in_range) {
+
+ if (!c.selected && c.selectable) {
+ c.selected=true;
+ emit_signal("multi_selected",p_current,i,true);
+ }
+
+ } else if (!r_in_range){
+ if (select_mode==SELECT_MULTI && c.selected)
+ emit_signal("multi_selected",p_current,i,false);
+ c.selected=false;
+ }
+ //p_current->deselected_signal.call(p_col);
+ }
+ }
+
+ }
+
+ if (!switched && r_in_range && *r_in_range && (p_current==p_selected || p_current==p_prev)) {
+ *r_in_range=false;
+ }
+
+ TreeItem *c=p_current->childs;
+
+ while (c) {
+
+ select_single_item(p_selected,c,p_col,p_prev,r_in_range);
+ c=c->next;
+ }
+
+}
+
+
+Rect2 Tree::search_item_rect(TreeItem *p_from, TreeItem *p_item) {
+
+
+ return Rect2();
+}
+
+
+
+
+
+int Tree::propagate_mouse_event(const Point2i &p_pos,int x_ofs,int y_ofs,bool p_doubleclick,TreeItem *p_item,int p_button,const InputModifierState& p_mod) {
+
+ int item_h=compute_item_height( p_item )+cache.vseparation;
+
+ bool skip=(p_item==root && hide_root);
+
+ if (!skip && p_pos.y<item_h) {
+ // check event!
+
+ if (p_pos.x >=x_ofs && p_pos.x < (x_ofs+cache.item_margin) ) {
+
+
+ if (p_item->childs)
+ p_item->set_collapsed( ! p_item->is_collapsed() );
+
+ return -1; //handled!
+ }
+
+ int x=p_pos.x;
+ /* find clicked column */
+ int col=-1;
+ int col_ofs=0;
+ int col_width=0;
+ for (int i=0;i<columns.size();i++) {
+
+ col_width=get_column_width(i);
+ if (x>col_width) {
+ col_ofs+=col_width;
+ x-=col_width;
+ continue;
+ }
+
+ col=i;
+ break;
+ }
+
+
+
+ if (col==-1)
+ return -1;
+ else if (col==0) {
+ int margin=x_ofs+cache.item_margin;//-cache.hseparation;
+ //int lm = cache.bg->get_margin(MARGIN_LEFT);
+ col_width-=margin;
+ col_ofs+=margin;
+ x-=margin;
+ } else {
+
+ col_width-=cache.hseparation;
+ x-=cache.hseparation;
+
+ }
+
+ TreeItem::Cell &c = p_item->cells[col];
+
+
+ bool already_selected=c.selected;
+ bool already_cursor=(p_item==selected_item) && col == selected_col;
+
+
+ for(int j=c.buttons.size()-1;j>=0;j--) {
+ Ref<Texture> b=c.buttons[j].texture;
+ int w = b->get_size().width + cache.button_pressed->get_minimum_size().width;
+ if (x>col_width-w) {
+ pressed_button=j;
+ cache.click_type=Cache::CLICK_BUTTON;
+ cache.click_index=j;
+ cache.click_id=c.buttons[j].id;
+ cache.click_item=p_item;
+ cache.click_column=col;
+ update();
+ //emit_signal("button_pressed");
+ return -1;
+ }
+ col_width-=w+cache.button_margin;
+ }
+
+ if (p_button==BUTTON_LEFT) {
+ /* process selection */
+
+ if (p_doubleclick && (!c.editable || c.mode==TreeItem::CELL_MODE_CUSTOM || c.mode==TreeItem::CELL_MODE_ICON)) {
+
+
+ emit_signal("item_activated");
+ return -1;
+ }
+
+ if (select_mode==SELECT_MULTI && p_mod.command && c.selectable) {
+
+ if (!c.selected) {
+
+ p_item->select(col);
+ emit_signal("multi_selected",p_item,col,true);
+
+
+ //p_item->selected_signal.call(col);
+ } else {
+
+ p_item->deselect(col);
+ emit_signal("multi_selected",p_item,col,false);
+ //p_item->deselected_signal.call(col);
+ }
+
+ } else {
+
+ if (c.selectable) {
+
+ if (select_mode==SELECT_MULTI && p_mod.shift && selected_item && selected_item!=p_item) {
+
+ bool inrange=false;
+ print_line("SELECT MULTI AND SHIFT AND ALL");
+ select_single_item( p_item, root, col,selected_item,&inrange );
+ } else {
+ select_single_item( p_item, root, col );
+ }
+
+ //if (!c.selected && select_mode==SELECT_MULTI) {
+ // emit_signal("multi_selected",p_item,col,true);
+ //}
+ update();
+ }
+
+
+ }
+ }
+
+
+
+ if (!c.editable)
+ return -1; // if cell is not editable, don't bother
+
+ /* editing */
+
+ bool bring_up_editor=c.selected && already_selected;
+ bool bring_up_value_editor=false;
+ String editor_text=c.text;
+
+ switch (c.mode) {
+
+ case TreeItem::CELL_MODE_STRING: {
+ //nothing in particular
+
+ if (select_mode==SELECT_MULTI && (get_scene()->get_last_event_id() == focus_in_id || !already_cursor)) {
+ bring_up_editor=false;
+ }
+
+ } break;
+ case TreeItem::CELL_MODE_CHECK: {
+
+ Ref<Texture> checked = cache.checked;
+ bring_up_editor=false; //checkboxes are not edited with editor
+ if (x>=0 && x<= checked->get_width()+cache.hseparation ) {
+
+
+ p_item->set_checked(col,!c.checked);
+ item_edited(col,p_item);
+ click_handled=true;
+ //p_item->edited_signal.call(col);
+ }
+
+ } break;
+ case TreeItem::CELL_MODE_RANGE: {
+
+
+ if (c.text!="") {
+ //if (x >= (get_column_width(col)-item_h/2)) {
+
+ popup_menu->clear();
+ for (int i=0;i<c.text.get_slice_count(",");i++) {
+
+ String s = c.text.get_slice(",",i);
+ popup_menu->add_item(s,i);
+
+ }
+
+ popup_menu->set_size(Size2(col_width,0));
+ popup_menu->set_pos( get_global_pos() + Point2i(col_ofs,_get_title_button_height()+y_ofs+item_h)-cache.offset );
+ popup_menu->popup();
+ popup_edited_item=p_item;
+ popup_edited_item_col=col;
+ //}
+ bring_up_editor=false;
+ } else {
+
+ Ref<Texture> updown = cache.updown;
+
+
+ if (x >= (col_width-item_h/2)) {
+
+ /* touching the combo */
+ bool up=p_pos.y < (item_h /2);
+
+ if (p_button==BUTTON_LEFT) {
+ p_item->set_range( col, c.val + (up?1.0:-1.0) * c.step );
+
+ item_edited(col,p_item);
+ } else if (p_button==BUTTON_RIGHT) {
+
+ p_item->set_range( col, (up?c.max:c.min) );
+ item_edited(col,p_item);
+ } else if (p_button==BUTTON_WHEEL_UP) {
+
+ p_item->set_range( col, c.val + c.step );
+ item_edited(col,p_item);
+ } else if (p_button==BUTTON_WHEEL_DOWN) {
+
+ p_item->set_range( col, c.val - c.step );
+ item_edited(col,p_item);
+ }
+
+ //p_item->edited_signal.call(col);
+ bring_up_editor=false;
+
+
+ } else {
+
+ editor_text=String::num( p_item->cells[col].val, Math::decimals( p_item->cells[col].step ) );
+ bring_up_value_editor=true;
+ if (select_mode==SELECT_MULTI && get_scene()->get_last_event_id() == focus_in_id)
+ bring_up_editor=false;
+
+ }
+
+ }
+ click_handled=true;
+
+ } break;
+ case TreeItem::CELL_MODE_ICON: {
+ bring_up_editor=false;
+ } break;
+ case TreeItem::CELL_MODE_CUSTOM: {
+ edited_item=p_item;
+ edited_col=col;
+ custom_popup_rect=Rect2i(get_global_pos() + Point2i(col_ofs,_get_title_button_height()+y_ofs+item_h-v_scroll->get_val()), Size2(get_column_width(col),item_h));
+ emit_signal("custom_popup_edited",((bool)(x >= (col_width-item_h/2))));
+
+ bring_up_editor=false;
+ item_edited(col,p_item);
+ click_handled=true;
+ return -1;
+ } break;
+
+ };
+
+ if (!bring_up_editor || p_button!=BUTTON_LEFT)
+ return -1;
+
+
+ click_handled=true;
+ popup_edited_item=p_item;
+ popup_edited_item_col=col;
+ text_editor->set_pos(get_global_pos() + Point2i(col_ofs,_get_title_button_height()+y_ofs)-cache.offset );
+ text_editor->set_size( Size2(col_width,item_h));
+ text_editor->clear();
+ text_editor->set_text( editor_text );
+ text_editor->select_all();
+
+ if (bring_up_value_editor) {
+
+ value_editor->set_pos(get_global_pos() + Point2i(col_ofs,_get_title_button_height()+y_ofs)-cache.offset+Point2i(0,text_editor->get_size().height) );
+ value_editor->set_size( Size2(col_width,1));
+ value_editor->show_modal();
+ updating_value_editor=true;
+ value_editor->set_min( c.min );
+ value_editor->set_max( c.max );
+ value_editor->set_step( c.step );
+ value_editor->set_val( c.val );
+ value_editor->set_exp_unit_value( c.expr );
+ updating_value_editor=false;
+ }
+
+ text_editor->show_modal();
+ text_editor->grab_focus();
+
+ return -1; //select
+ } else {
+
+ Point2i new_pos=p_pos;
+
+ if (!skip) {
+ x_ofs+=cache.item_margin;
+ //new_pos.x-=cache.item_margin;
+ y_ofs+=item_h;
+ new_pos.y-=item_h;
+ }
+
+
+ if (!p_item->collapsed) { /* if not collapsed, check the childs */
+
+ TreeItem *c=p_item->childs;
+
+ while (c) {
+
+ int child_h=propagate_mouse_event( new_pos,x_ofs,y_ofs,p_doubleclick,c,p_button,p_mod);
+
+ if (child_h<0)
+ return -1; // break, stop propagating, no need to anymore
+
+ new_pos.y-=child_h;
+ y_ofs+=child_h;
+ c=c->next;
+ item_h+=child_h;
+ }
+ }
+
+
+
+ }
+
+ return item_h; // nothing found
+
+}
+
+
+void Tree::text_editor_enter(String p_text) {
+
+
+ text_editor->hide();
+
+ if (!popup_edited_item)
+ return;
+
+ if (popup_edited_item_col<0 || popup_edited_item_col>columns.size())
+ return;
+
+ TreeItem::Cell &c=popup_edited_item->cells[popup_edited_item_col];
+ switch( c.mode ) {
+
+ case TreeItem::CELL_MODE_STRING: {
+
+ c.text=p_text;
+ //popup_edited_item->edited_signal.call( popup_edited_item_col );
+ } break;
+ case TreeItem::CELL_MODE_RANGE: {
+
+ c.val=p_text.to_double();
+ //popup_edited_item->edited_signal.call( popup_edited_item_col );
+ } break;
+ default: { ERR_FAIL(); }
+ }
+
+ item_edited(popup_edited_item_col,popup_edited_item);
+ update();
+
+}
+
+void Tree::value_editor_changed(double p_value) {
+
+ if (updating_value_editor) {
+ return;
+ }
+ if (!popup_edited_item) {
+ return;
+ }
+
+ TreeItem::Cell &c=popup_edited_item->cells[popup_edited_item_col];
+ c.val=p_value;
+ item_edited(popup_edited_item_col,popup_edited_item);
+ update();
+}
+
+void Tree::popup_select(int p_option) {
+
+ if (!popup_edited_item)
+ return;
+
+ if (popup_edited_item_col<0 || popup_edited_item_col>columns.size())
+ return;
+
+
+ popup_edited_item->cells[popup_edited_item_col].val=p_option;
+ //popup_edited_item->edited_signal.call( popup_edited_item_col );
+ update();
+ item_edited(popup_edited_item_col,popup_edited_item);
+}
+
+
+
+void Tree::_input_event(InputEvent p_event) {
+
+ switch (p_event.type) {
+
+ case InputEvent::KEY: {
+
+ if (!p_event.key.pressed)
+ break;
+ if (p_event.key.mod.alt || p_event.key.mod.command || (p_event.key.mod.shift && p_event.key.unicode==0) || p_event.key.mod.meta)
+ break;
+ if (!root)
+ return;
+
+ if (hide_root && !root->get_next_visible())
+ return;
+
+ switch(p_event.key.scancode) {
+#define EXIT_BREAK { if (!cursor_can_exit_tree) accept_event(); break; }
+ case KEY_RIGHT: {
+
+ //TreeItem *next = NULL;
+ if (!selected_item)
+ break;
+ if (select_mode==SELECT_ROW)
+ EXIT_BREAK;
+ if (selected_col>=(columns.size()-1))
+ EXIT_BREAK;
+ if (select_mode==SELECT_MULTI) {
+ selected_col++;
+ emit_signal("cell_selected");
+ } else {
+
+ selected_item->select(selected_col+1);
+ }
+
+ update();
+ ensure_cursor_is_visible();
+ accept_event();
+
+ } break;
+ case KEY_LEFT: {
+
+// TreeItem *next = NULL;
+ if (!selected_item)
+ break;
+ if (select_mode==SELECT_ROW)
+ EXIT_BREAK;
+ if (selected_col<=0)
+ EXIT_BREAK;
+ if (select_mode==SELECT_MULTI) {
+ selected_col--;
+ emit_signal("cell_selected");
+ } else {
+
+ selected_item->select(selected_col-1);
+ }
+
+ update();
+ accept_event();
+
+ } break;
+ case KEY_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 (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;
+ }
+
+ }
+ }
+
+ if (select_mode==SELECT_MULTI) {
+
+ if (!next)
+ EXIT_BREAK;
+
+ selected_item=next;
+ emit_signal("cell_selected");
+ update();
+ } else {
+
+ int col=selected_col<0?0:selected_col;
+
+ while (next && !next->cells[col].selectable)
+ next=next->get_next_visible();
+ if (!next)
+ EXIT_BREAK; // do nothing..
+ next->select(col);
+
+ }
+
+ ensure_cursor_is_visible();
+ accept_event();
+
+ } break;
+ case KEY_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)
+ break;
+ selected_item=prev;
+ emit_signal("cell_selected");
+ update();
+ } else {
+
+ 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);
+
+ }
+
+ ensure_cursor_is_visible();
+ accept_event();
+
+ } break;
+ case KEY_PAGEDOWN: {
+
+ TreeItem *next = NULL;
+ if (!selected_item)
+ break;
+ next=selected_item;
+
+ for(int i=0;i<10;i++) {
+
+ TreeItem *_n = next->get_next_visible();
+ if (_n) {
+ next=_n;
+ } else {
+
+ break;
+ }
+ }
+ if (next==selected_item)
+ break;
+
+ if (select_mode==SELECT_MULTI) {
+
+
+ selected_item=next;
+ emit_signal("cell_selected");
+ update();
+ } else {
+
+ while (next && !next->cells[selected_col].selectable)
+ next=next->get_next_visible();
+ if (!next)
+ EXIT_BREAK; // do nothing..
+ next->select(selected_col);
+
+ }
+
+ ensure_cursor_is_visible();
+ } break;
+ case KEY_PAGEUP: {
+
+ TreeItem *prev = NULL;
+ if (!selected_item)
+ break;
+ prev=selected_item;
+
+ for(int i=0;i<10;i++) {
+
+ TreeItem *_n = prev->get_prev_visible();
+ if (_n) {
+ prev=_n;
+ } else {
+
+ break;
+ }
+ }
+ if (prev==selected_item)
+ break;
+
+ if (select_mode==SELECT_MULTI) {
+
+
+ selected_item=prev;
+ emit_signal("cell_selected");
+ update();
+ } else {
+
+ while (prev && !prev->cells[selected_col].selectable)
+ prev=prev->get_prev_visible();
+ if (!prev)
+ EXIT_BREAK; // do nothing..
+ prev->select(selected_col);
+
+ }
+
+ ensure_cursor_is_visible();
+
+ } break;
+ case KEY_F2:
+ case KEY_RETURN:
+ case KEY_ENTER: {
+
+ if (selected_item) {
+ //bring up editor if possible
+ if (!edit_selected()) {
+ emit_signal("item_activated");
+ }
+
+ }
+ accept_event();
+
+ } 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();
+
+ } break;
+ default: {
+
+ if (p_event.key.unicode>0) {
+
+ _do_incr_search(String::chr(p_event.key.unicode));
+ accept_event();
+
+ return;
+ } else {
+ if (p_event.key.scancode!=KEY_SHIFT)
+ last_keypress=0;
+ }
+ } break;
+
+ last_keypress=0;
+ }
+
+ } break;
+
+ case InputEvent::MOUSE_MOTION: {
+
+ if (cache.font.is_null()) // avoid a strange case that may fuckup stuff
+ update_cache();
+ const InputEventMouseMotion& b=p_event.mouse_motion;
+
+ Ref<StyleBox> bg = cache.bg;
+
+ Point2 pos = Point2(b.x,b.y) - bg->get_offset();
+
+ Cache::ClickType old_hover = cache.hover_type;
+ int old_index = cache.hover_index;
+
+
+ cache.hover_type=Cache::CLICK_NONE;
+ cache.hover_index=0;
+ if (show_column_titles) {
+ pos.y-=_get_title_button_height();
+ if (pos.y<0) {
+ pos.x+=cache.offset.x;
+ int len=0;
+ for(int i=0;i<columns.size();i++) {
+
+ len+=get_column_width(i);
+ if (pos.x<len) {
+
+ cache.hover_type=Cache::CLICK_TITLE;
+ cache.hover_index=i;
+ update();
+ break;
+ }
+ }
+
+ }
+
+ }
+
+ if (cache.hover_type!=old_hover || cache.hover_index!=old_index) {
+ update();
+ }
+
+ if (drag_touching && ! drag_touching_deaccel) {
+
+
+ drag_accum-=b.relative_y;
+ v_scroll->set_val(drag_from+drag_accum);
+ drag_speed=-b.speed_y;
+
+ }
+ } break;
+ case InputEvent::MOUSE_BUTTON: {
+
+
+
+ if (cache.font.is_null()) // avoid a strange case that may fuckup stuff
+ update_cache();
+ const InputEventMouseButton& b=p_event.mouse_button;
+
+
+ if (!b.pressed) {
+
+ if (b.button_index==BUTTON_LEFT) {
+
+ if (cache.click_type==Cache::CLICK_BUTTON) {
+ emit_signal("button_pressed",cache.click_item,cache.click_column,cache.click_id);
+
+ }
+ cache.click_type=Cache::CLICK_NONE;
+ cache.click_index=-1;
+ cache.click_id=-1;
+ cache.click_item=NULL;
+ cache.click_column=0;
+
+ if (drag_touching) {
+
+
+ if (drag_speed==0) {
+ drag_touching_deaccel=false;
+ drag_touching=false;
+ set_fixed_process(false);
+ } else {
+
+ drag_touching_deaccel=true;
+ }
+
+ }
+ update();
+ }
+ break;
+
+ }
+
+ switch(b.button_index) {
+ case BUTTON_LEFT: {
+ Ref<StyleBox> bg = cache.bg;
+
+ Point2 pos = Point2(b.x,b.y) - bg->get_offset();
+ cache.click_type=Cache::CLICK_NONE;
+ if (show_column_titles) {
+ pos.y-=_get_title_button_height();
+
+ if (pos.y<0) {
+ pos.x+=cache.offset.x;
+ int len=0;
+ for(int i=0;i<columns.size();i++) {
+
+ len+=get_column_width(i);
+ if (pos.x<len) {
+
+ cache.click_type=Cache::CLICK_TITLE;
+ cache.click_index=i;
+ //cache.click_id=;
+ update();
+ break;
+ }
+ }
+ break;
+ }
+
+ }
+ if (!root)
+ break;
+
+ click_handled=false;
+
+ blocked++;
+ bool handled = propagate_mouse_event(pos+cache.offset,0,0,b.doubleclick,root,b.button_index,b.mod);
+ blocked--;
+
+
+
+ if (drag_touching) {
+ set_fixed_process(false);
+ drag_touching_deaccel=false;
+ drag_touching=false;
+ drag_speed=0;
+ drag_from=0;
+ }
+
+ if (!click_handled) {
+ drag_speed=0;
+ drag_accum=0;
+// last_drag_accum=0;
+ drag_from=v_scroll->get_val();
+ drag_touching=OS::get_singleton()->has_touchscreen_ui_hint();
+ drag_touching_deaccel=false;
+ if (drag_touching) {
+ set_fixed_process(true);
+ }
+ }
+
+
+ } break;
+ case BUTTON_WHEEL_UP: {
+ v_scroll->set_val( v_scroll->get_val()-v_scroll->get_page()/8 );
+ } break;
+ case BUTTON_WHEEL_DOWN: {
+
+ v_scroll->set_val( v_scroll->get_val()+v_scroll->get_page()/8 );
+ } break;
+ }
+
+ } break;
+ }
+
+}
+
+
+bool Tree::edit_selected() {
+
+ TreeItem *s = get_selected();
+ ERR_EXPLAIN("No item selected!");
+ ERR_FAIL_COND_V(!s,false);
+ ensure_cursor_is_visible();
+ int col = get_selected_column();
+ ERR_EXPLAIN("No item column selected!");
+ ERR_FAIL_INDEX_V(col,columns.size(),false);
+
+ if (!s->cells[col].editable)
+ return false;
+
+ Rect2 rect;
+ rect.pos.y = get_item_offset(s) - v_scroll->get_val();
+
+ for(int i=0;i<col;i++) {
+
+ rect.pos.x+=get_column_width(i);
+ }
+
+ rect.size.width=get_column_width(col);
+ rect.size.height=compute_item_height(s)+cache.vseparation;
+
+ popup_edited_item=s;
+ popup_edited_item_col=col;
+
+ TreeItem::Cell &c = s->cells[col];
+
+
+
+ if (c.mode==TreeItem::CELL_MODE_CUSTOM) {
+
+ edited_item=s;
+ edited_col=col;
+ custom_popup_rect=Rect2i( get_global_pos() + rect.pos, rect.size );
+ emit_signal("custom_popup_edited",false);
+ item_edited(col,s);
+
+ return true;
+ } else if (c.mode==TreeItem::CELL_MODE_RANGE && c.text!="") {
+
+ popup_menu->clear();
+ for (int i=0;i<c.text.get_slice_count(",");i++) {
+
+ String s = c.text.get_slice(",",i);
+ popup_menu->add_item(s,i);
+
+ }
+
+ popup_menu->set_size(Size2(rect.size.width,0));
+ popup_menu->set_pos( get_global_pos() + rect.pos + Point2i(0,rect.size.height) );
+ popup_menu->popup();
+ popup_edited_item=s;
+ popup_edited_item_col=col;
+ return true;
+
+ } else if (c.mode==TreeItem::CELL_MODE_STRING || c.mode==TreeItem::CELL_MODE_RANGE) {
+
+ Point2i textedpos=get_global_pos() + rect.pos;
+ text_editor->set_pos( textedpos );
+ text_editor->set_size( rect.size);
+ text_editor->clear();
+ text_editor->set_text( c.mode==TreeItem::CELL_MODE_STRING?c.text:rtos(c.val) );
+ text_editor->select_all();
+
+ if (c.mode==TreeItem::CELL_MODE_RANGE) {
+
+ value_editor->set_pos(textedpos + Point2i(0,text_editor->get_size().height) );
+ value_editor->set_size( Size2(rect.size.width,1));
+ value_editor->show_modal();
+ updating_value_editor=true;
+ value_editor->set_min( c.min );
+ value_editor->set_max( c.max );
+ value_editor->set_step( c.step );
+ value_editor->set_val( c.val );
+ value_editor->set_exp_unit_value( c.expr );
+ updating_value_editor=false;
+ }
+
+ text_editor->show_modal();
+ text_editor->grab_focus();
+ return true;
+ }
+
+ return false;
+}
+
+Size2 Tree::get_internal_min_size() const {
+
+ Size2i size=cache.bg->get_offset();
+ if (root)
+ size.height+=get_item_height(root);
+ for (int i=0;i<columns.size();i++) {
+
+ size.width+=columns[i].min_width;
+ }
+
+ return size;
+}
+
+void Tree::update_scrollbars() {
+
+ Size2 size = get_size();
+ int tbh;
+ if (show_column_titles) {
+ tbh=_get_title_button_height();
+ } else {
+
+ tbh=0;
+ }
+
+ Size2 hmin = h_scroll->get_combined_minimum_size();
+ Size2 vmin = v_scroll->get_combined_minimum_size();
+
+
+
+ v_scroll->set_begin( Point2(size.width - vmin.width , cache.bg->get_margin(MARGIN_TOP)) );
+ v_scroll->set_end( Point2(size.width, size.height-cache.bg->get_margin(MARGIN_TOP)-cache.bg->get_margin(MARGIN_BOTTOM)) );
+
+ h_scroll->set_begin( Point2( 0, size.height - hmin.height) );
+ h_scroll->set_end( Point2(size.width-vmin.width, size.height) );
+
+
+ Size2 min = get_internal_min_size();
+
+ if (min.height < size.height - hmin.height) {
+
+ v_scroll->hide();
+ cache.offset.y=0;
+ } else {
+
+ v_scroll->show();
+ v_scroll->set_max(min.height);
+ v_scroll->set_page(size.height - hmin.height - tbh);
+ cache.offset.y=v_scroll->get_val();
+ }
+
+ if (min.width < size.width - vmin.width) {
+
+ h_scroll->hide();
+ cache.offset.x=0;
+ } else {
+
+ h_scroll->show();
+ h_scroll->set_max(min.width);
+ h_scroll->set_page(size.width - vmin.width);
+ cache.offset.x=h_scroll->get_val();
+ }
+}
+
+
+int Tree::_get_title_button_height() const {
+
+ return show_column_titles?cache.font->get_height() + cache.title_button->get_minimum_size().height:0;
+}
+
+void Tree::_notification(int p_what) {
+
+ if (p_what==NOTIFICATION_FOCUS_ENTER) {
+
+ focus_in_id=get_scene()->get_last_event_id();
+ }
+ if (p_what==NOTIFICATION_MOUSE_EXIT) {
+
+ if (cache.hover_type!=Cache::CLICK_NONE) {
+ cache.hover_type=Cache::CLICK_NONE;
+ update();
+ }
+ }
+
+ if (p_what==NOTIFICATION_ENTER_SCENE) {
+
+ update_cache();;
+ }
+ if (p_what==NOTIFICATION_FIXED_PROCESS) {
+
+ if (drag_touching) {
+
+ if (drag_touching_deaccel) {
+
+ float pos = v_scroll->get_val();
+ pos+=drag_speed*get_fixed_process_delta_time();
+
+ bool turnoff=false;
+ if (pos<0) {
+ pos=0;
+ turnoff=true;
+ set_fixed_process(false);
+ drag_touching=false;
+ drag_touching_deaccel=false;
+ }
+ if (pos > (v_scroll->get_max()-v_scroll->get_page())) {
+ pos=v_scroll->get_max()-v_scroll->get_page();
+ turnoff=true;
+
+ }
+
+ v_scroll->set_val(pos);
+ float sgn = drag_speed<0? -1 : 1;
+ float val = Math::abs(drag_speed);
+ val-=1000*get_fixed_process_delta_time();
+
+ if (val<0) {
+ turnoff=true;
+ }
+ drag_speed=sgn*val;
+
+ if (turnoff) {
+ set_fixed_process(false);
+ drag_touching=false;
+ drag_touching_deaccel=false;
+ }
+
+
+ } else {
+
+ }
+ }
+ }
+
+ if (p_what==NOTIFICATION_DRAW) {
+
+ update_cache();
+ update_scrollbars();
+ RID ci = get_canvas_item();
+
+ VisualServer::get_singleton()->canvas_item_set_clip(ci,true);
+
+ Ref<StyleBox> bg = cache.bg;
+ Ref<StyleBox> bg_focus = get_stylebox("bg_focus");
+
+ Point2 draw_ofs;
+ draw_ofs+=bg->get_offset();
+ Size2 draw_size=get_size()-bg->get_minimum_size();
+
+ bg->draw( ci, Rect2( Point2(), get_size()) );
+ if (has_focus()) {
+ VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci,true);
+ bg_focus->draw( ci, Rect2( Point2(), get_size()) );
+ VisualServer::get_singleton()->canvas_item_add_clip_ignore(ci,false);
+ }
+
+ int tbh = _get_title_button_height();
+
+ draw_ofs.y+=tbh;
+ draw_size.y-=tbh;
+
+ if (root) {
+
+
+ draw_item( Point2(),draw_ofs,draw_size,root);
+
+ }
+
+ int ofs=0;
+// int from_y=exposed.pos.y+bg->get_margin(MARGIN_TOP);
+// int size_y=exposed.size.height-bg->get_minimum_size().height;
+
+ for (int i=0;i<(columns.size()-1-1);i++) {
+
+ ofs+=get_column_width(i);
+ //get_painter()->draw_fill_rect( Point2(ofs+cache.hseparation/2, from_y), Size2( 1, size_y ),color( COLOR_TREE_GRID) );
+ }
+
+ if (show_column_titles) {
+
+ //title butons
+ int ofs=cache.bg->get_margin(MARGIN_LEFT);
+ for(int i=0;i<columns.size();i++) {
+
+ Ref<StyleBox> sb = (cache.click_type==Cache::CLICK_TITLE && cache.click_index==i)?cache.title_button_pressed:((cache.hover_type==Cache::CLICK_TITLE && cache.hover_index==i)?cache.title_button_hover:cache.title_button);
+ Ref<Font> f = cache.tb_font;
+ Rect2 tbrect = Rect2(ofs - cache.offset.x,bg->get_margin(MARGIN_TOP),get_column_width(i),tbh);
+ sb->draw(ci,tbrect);
+ ofs+=tbrect.size.width;
+ //text
+ int clip_w = tbrect.size.width - sb->get_minimum_size().width;
+ f->draw_halign(ci,tbrect.pos+Point2i(sb->get_offset().x,(tbrect.size.height-f->get_height())/2+f->get_ascent()),HALIGN_CENTER,clip_w,columns[i].title,cache.title_button_color);
+ }
+ }
+ }
+
+}
+
+
+
+
+Size2 Tree::get_minimum_size() const {
+
+ return Size2(1,1);
+}
+
+TreeItem *Tree::create_item(TreeItem *p_parent) {
+
+ ERR_FAIL_COND_V(blocked>0,NULL);
+
+ TreeItem *ti = memnew( TreeItem(this) );
+
+ ti->cells.resize( columns.size() );
+ ERR_FAIL_COND_V(!ti,NULL);
+
+ if (p_parent) {
+
+ /* Always append at the end */
+
+ TreeItem *last=0;
+ TreeItem *c=p_parent->childs;
+
+ while(c) {
+
+ last=c;
+ c=c->next;
+ }
+
+ if (last) {
+
+ last->next=ti;
+ } else {
+
+ p_parent->childs=ti;
+ }
+ ti->parent=p_parent;
+
+ } else {
+
+ if (root)
+ ti->childs=root;
+
+ root=ti;
+
+ }
+
+
+ return ti;
+}
+
+TreeItem* Tree::get_root() {
+
+ return root;
+}
+TreeItem* Tree::get_last_item() {
+
+ TreeItem *last=root;
+
+ while(last) {
+
+ if (last->next)
+ last=last->next;
+ else if (last->childs)
+ last=last->childs;
+ else
+ break;
+ }
+
+ return last;
+}
+
+void Tree::item_edited(int p_column,TreeItem *p_item) {
+
+ edited_item=p_item;
+ edited_col=p_column;
+ emit_signal("item_edited");
+}
+
+void Tree::item_changed(int p_column,TreeItem *p_item) {
+
+ update();
+}
+
+void Tree::item_selected(int p_column,TreeItem *p_item) {
+
+
+ if (select_mode==SELECT_MULTI) {
+
+ if (!p_item->cells[p_column].selectable)
+ return;
+
+ p_item->cells[p_column].selected=true;
+ //emit_signal("multi_selected",p_item,p_column,true); - NO this is for TreeItem::select
+
+ } else {
+ select_single_item(p_item,root,p_column);
+ }
+ update();
+}
+
+void Tree::item_deselected(int p_column,TreeItem *p_item) {
+
+ if (select_mode==SELECT_MULTI) {
+
+ p_item->cells[p_column].selected=false;
+ }
+ update();
+}
+
+
+void Tree::set_select_mode(SelectMode p_mode) {
+
+ select_mode=p_mode;
+}
+
+void Tree::clear() {
+
+ if (blocked>0) {
+
+ ERR_FAIL_COND(blocked>0);
+ }
+
+ if (root) {
+ memdelete( root );
+ root = NULL;
+ };
+
+ selected_item=NULL;
+ edited_item=NULL;
+ popup_edited_item=NULL;
+
+ update();
+};
+
+
+
+void Tree::set_hide_root(bool p_enabled) {
+
+
+
+ hide_root=p_enabled;
+ update();
+}
+
+void Tree::set_column_min_width(int p_column,int p_min_width) {
+
+
+ ERR_FAIL_INDEX(p_column,columns.size());
+
+ if (p_min_width<1)
+ return;
+ columns[p_column].min_width=p_min_width;
+ update();
+
+}
+void Tree::set_column_expand(int p_column,bool p_expand) {
+
+ ERR_FAIL_INDEX(p_column,columns.size());
+
+ columns[p_column].expand=p_expand;
+ update();
+}
+
+TreeItem *Tree::get_selected() const {
+
+ return selected_item;
+}
+
+int Tree::get_selected_column() const {
+
+ return selected_col;
+}
+
+TreeItem *Tree::get_edited() const {
+
+ return edited_item;
+}
+
+int Tree::get_edited_column() const {
+
+ return edited_col;
+}
+
+TreeItem* Tree::get_next_selected( TreeItem* p_item) {
+
+ //if (!p_item)
+ // return NULL;
+ if (!root)
+ return NULL;
+
+ while(true) {
+
+
+ if (!p_item) {
+ p_item=root;
+ } else {
+
+ if (p_item->childs) {
+
+ p_item=p_item->childs;
+
+ } else if (p_item->next) {
+
+ p_item=p_item->next;
+ } else {
+
+ while(!p_item->next) {
+
+ p_item=p_item->parent;
+ if (p_item==NULL)
+ return NULL;
+ }
+
+ p_item=p_item->next;
+ }
+
+ }
+
+ for (int i=0;i<columns.size();i++)
+ if (p_item->cells[i].selected)
+ return p_item;
+ }
+
+ return NULL;
+}
+
+int Tree::get_column_width(int p_column) const {
+
+ ERR_FAIL_INDEX_V(p_column,columns.size(),-1);
+
+
+ if (!columns[p_column].expand)
+ return columns[p_column].min_width;
+
+ Ref<StyleBox> bg = cache.bg;
+
+ int expand_area=get_size().width-(bg->get_margin(MARGIN_LEFT)+bg->get_margin(MARGIN_RIGHT));
+
+ if (v_scroll->is_visible())
+ expand_area-=v_scroll->get_combined_minimum_size().width;
+
+ int expanding_columns=0;
+ int expanding_total=0;
+
+ for (int i=0;i<columns.size();i++) {
+
+ if (!columns[i].expand) {
+ expand_area-=columns[i].min_width;
+ } else {
+ expanding_total+=columns[i].min_width;
+ expanding_columns++;
+ }
+ }
+
+ if (expand_area<expanding_total)
+ return columns[p_column].min_width;
+
+ ERR_FAIL_COND_V(expanding_columns==0,-1); // shouldnt happen
+
+ return expand_area * columns[p_column].min_width / expanding_total;
+}
+
+void Tree::propagate_set_columns(TreeItem *p_item) {
+
+ p_item->cells.resize( columns.size() );
+
+ TreeItem *c = p_item->get_children();
+ while(c) {
+
+ propagate_set_columns(c);
+ c=c->get_next();
+ }
+}
+
+void Tree::set_columns(int p_columns) {
+
+ ERR_FAIL_COND(p_columns<1);
+ ERR_FAIL_COND(blocked>0);
+ columns.resize(p_columns);
+
+ if (root)
+ propagate_set_columns(root);
+ if (selected_col>=p_columns)
+ selected_col=p_columns-1;
+ update();
+
+}
+
+int Tree::get_columns() const {
+
+ return columns.size();
+}
+
+void Tree::_scroll_moved(float) {
+
+ update();
+}
+
+Rect2 Tree::get_custom_popup_rect() const {
+
+ return custom_popup_rect;
+}
+
+int Tree::get_item_offset(TreeItem *p_item) const {
+
+ TreeItem *it=root;
+ int ofs=_get_title_button_height();
+ if (!it)
+ return 0;
+
+ while(true) {
+
+ if (it==p_item)
+ return ofs;
+
+ ofs+=compute_item_height(it)+cache.vseparation;
+
+ if (it->childs) {
+
+ it=it->childs;
+
+ } else if (it->next) {
+
+ it=it->next;
+ } else {
+
+ while(!it->next) {
+
+ it=it->parent;
+ if (it==NULL)
+ return 0;
+ }
+
+ it=it->next;
+ }
+ }
+
+ return -1; //not found
+}
+
+void Tree::ensure_cursor_is_visible() {
+
+ if (!is_inside_scene())
+ return;
+
+ TreeItem *selected = get_selected();
+ if (!selected)
+ return;
+ int ofs = get_item_offset(selected);
+ if (ofs==-1)
+ return;
+ int h = compute_item_height(selected)+cache.vseparation;
+ int screenh=get_size().height-h_scroll->get_combined_minimum_size().height;
+
+ if (ofs+h>v_scroll->get_val()+screenh)
+ v_scroll->set_val(ofs-screenh+h);
+ else if (ofs < v_scroll->get_val())
+ v_scroll->set_val(ofs);
+}
+
+int Tree::get_pressed_button() const {
+
+ return pressed_button;
+}
+
+
+Rect2 Tree::get_item_rect(TreeItem *p_item,int p_column) const {
+
+ ERR_FAIL_NULL_V(p_item,Rect2());
+ ERR_FAIL_COND_V(p_item->tree!=this,Rect2());
+ if (p_column!=-1) {
+ ERR_FAIL_INDEX_V(p_column,columns.size(),Rect2());
+ }
+
+ int ofs = get_item_offset(p_item);
+ int height = compute_item_height(p_item);
+ Rect2 r;
+ r.pos.y=ofs;
+ r.size.height=height;
+
+ if (p_column==-1) {
+ r.pos.x=0;
+ r.size.x=get_size().width;
+ } else {
+
+ int accum=0;
+ for(int i=0;i<p_column;i++) {
+ accum+=get_column_width(i);
+ }
+ r.pos.x=accum;
+ r.size.x=get_column_width(p_column);
+ }
+
+ return r;
+}
+
+void Tree::set_column_titles_visible(bool p_show) {
+
+ show_column_titles=p_show;
+ update();
+}
+
+bool Tree::are_column_titles_visible() const {
+
+ return show_column_titles;
+}
+
+void Tree::set_column_title(int p_column,const String& p_title) {
+
+ ERR_FAIL_INDEX(p_column,columns.size());
+ columns[p_column].title=p_title;
+ update();
+}
+
+String Tree::get_column_title(int p_column) const {
+
+ ERR_FAIL_INDEX_V(p_column,columns.size(),"");
+ return columns[p_column].title;
+}
+
+Point2 Tree::get_scroll() const {
+
+ Point2 ofs;
+ if (h_scroll->is_visible())
+ ofs.x=h_scroll->get_val();
+ if (v_scroll->is_visible())
+ ofs.y=v_scroll->get_val();
+ return ofs;
+
+}
+
+TreeItem* Tree::_search_item_text(TreeItem *p_at, const String& p_find,int *r_col,bool p_selectable,bool p_backwards) {
+
+
+
+ while(p_at) {
+
+ for(int i=0;i<columns.size();i++) {
+ if (p_at->get_text(i).findn(p_find)==0 && (!p_selectable || p_at->is_selectable(i))) {
+ if (r_col)
+ *r_col=i;
+ return p_at;
+ }
+ }
+
+ if (p_backwards)
+ p_at=p_at->get_prev_visible();
+ else
+ p_at=p_at->get_next_visible();
+ }
+
+ return NULL;
+
+}
+
+
+TreeItem* Tree::search_item_text(const String& p_find,int *r_col,bool p_selectable) {
+
+ if (!root)
+ return NULL;
+
+ return _search_item_text(root,p_find,r_col,p_selectable);
+
+}
+
+void Tree::_do_incr_search(const String& p_add) {
+
+ uint64_t time = OS::get_singleton()->get_ticks_usec() / 1000; // convert to msec
+ uint64_t diff = time - last_keypress;
+ if (diff > uint64_t(GLOBAL_DEF("gui/incr_search_max_interval_msec",2000)))
+ incr_search=p_add;
+ else
+ incr_search+=p_add;
+
+
+ last_keypress=time;
+ int col;
+ TreeItem *item = search_item_text(incr_search,&col,true);
+ if (!item)
+ return;
+
+ item->select(col);
+ ensure_cursor_is_visible();
+
+
+}
+
+TreeItem* Tree::_find_item_at_pos(TreeItem*p_item, const Point2& p_pos,int& r_column,int &h) const {
+
+ Point2 pos = p_pos;
+
+
+ if (root!=p_item || ! hide_root) {
+
+ h = compute_item_height(p_item)+cache.vseparation;;
+ if (pos.y<h) {
+
+ for(int i=0;i<columns.size();i++) {
+
+ int w = get_column_width(i);
+ if (pos.x < w) {
+ r_column=i;
+ return p_item;
+ }
+ pos.x-=w;
+ }
+ return NULL;
+ } else {
+
+ pos.y-=h;
+ }
+ } else {
+
+ h=0;
+ }
+
+ if (p_item->is_collapsed())
+ return NULL; // do not try childs, it's collapsed
+
+ TreeItem *n = p_item->get_children();
+ while(n) {
+
+
+ int ch;
+ TreeItem *r = _find_item_at_pos(n,pos,r_column,ch);
+ pos.y-=ch;
+ h+=ch;
+ if (r)
+ return r;
+ n=n->get_next();
+ }
+
+ return NULL;
+
+}
+
+String Tree::get_tooltip(const Point2& p_pos) const {
+
+ if (root) {
+ Point2 pos=p_pos;
+ pos -= cache.bg->get_offset();
+ pos.y-=_get_title_button_height();
+ if (pos.y<0)
+ return Control::get_tooltip(p_pos);
+
+ pos.x+=h_scroll->get_val();
+ pos.y+=v_scroll->get_val();
+
+ int col,h;
+ TreeItem *it = _find_item_at_pos(root,pos,col,h);
+
+ if (it) {
+
+
+ String ret;
+ if (it->get_tooltip(col)=="")
+ ret=it->get_text(col);
+ else
+ ret=it->get_tooltip(col);
+ return ret;
+ }
+ }
+
+ return Control::get_tooltip(p_pos);
+}
+
+void Tree::set_cursor_can_exit_tree(bool p_enable) {
+
+ cursor_can_exit_tree=p_enable;
+}
+
+bool Tree::can_cursor_exit_tree() const {
+
+ return cursor_can_exit_tree;
+}
+
+
+void Tree::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("_input_event"),&Tree::_input_event);
+ ObjectTypeDB::bind_method(_MD("_popup_select"),&Tree::popup_select);
+ ObjectTypeDB::bind_method(_MD("_text_editor_enter"),&Tree::text_editor_enter);
+ ObjectTypeDB::bind_method(_MD("_value_editor_changed"),&Tree::value_editor_changed);
+ ObjectTypeDB::bind_method(_MD("_scroll_moved"),&Tree::_scroll_moved);
+
+ ObjectTypeDB::bind_method(_MD("clear"),&Tree::clear);
+ ObjectTypeDB::bind_method(_MD("create_item:TreeItem","parent:TreeItem"),&Tree::_create_item,DEFVAL((Object*)NULL));
+
+ ObjectTypeDB::bind_method(_MD("get_root:TreeItem"),&Tree::get_root);
+ ObjectTypeDB::bind_method(_MD("set_column_min_width"),&Tree::set_column_min_width);
+ ObjectTypeDB::bind_method(_MD("set_column_expand"),&Tree::set_column_expand);
+ ObjectTypeDB::bind_method(_MD("get_column_width"),&Tree::get_column_width);
+
+ ObjectTypeDB::bind_method(_MD("set_hide_root"),&Tree::set_hide_root);
+ ObjectTypeDB::bind_method(_MD("get_next_selected:TreeItem","from:TreeItem"),&Tree::_get_next_selected);
+ ObjectTypeDB::bind_method(_MD("get_selected:TreeItem"),&Tree::get_selected);
+ ObjectTypeDB::bind_method(_MD("get_selected_column"),&Tree::get_selected_column);
+ ObjectTypeDB::bind_method(_MD("get_pressed_button"),&Tree::get_pressed_button);
+ ObjectTypeDB::bind_method(_MD("set_select_mode","mode"),&Tree::set_select_mode);
+
+ ObjectTypeDB::bind_method(_MD("set_columns","amount"),&Tree::set_columns);
+ ObjectTypeDB::bind_method(_MD("get_columns"),&Tree::get_columns);
+
+ ObjectTypeDB::bind_method(_MD("get_edited:TreeItem"),&Tree::get_edited);
+ ObjectTypeDB::bind_method(_MD("get_edited_column"),&Tree::get_edited_column);
+ ObjectTypeDB::bind_method(_MD("get_custom_popup_rect"),&Tree::get_custom_popup_rect);
+ ObjectTypeDB::bind_method(_MD("get_item_area_rect","item:TreeItem","column"),&Tree::_get_item_rect,DEFVAL(-1));
+
+ ObjectTypeDB::bind_method(_MD("ensure_cursor_is_visible"),&Tree::ensure_cursor_is_visible);
+
+ ObjectTypeDB::bind_method(_MD("set_column_titles_visible","visible"),&Tree::set_column_titles_visible);
+ ObjectTypeDB::bind_method(_MD("are_column_titles_visible"),&Tree::are_column_titles_visible);
+
+ ObjectTypeDB::bind_method(_MD("set_column_title","column","title"),&Tree::set_column_title);
+ ObjectTypeDB::bind_method(_MD("get_column_title","column"),&Tree::get_column_title);
+ ObjectTypeDB::bind_method(_MD("get_scroll"),&Tree::get_scroll);
+
+
+ ADD_SIGNAL( MethodInfo("item_selected"));
+ ADD_SIGNAL( MethodInfo("cell_selected"));
+ ADD_SIGNAL( MethodInfo("multi_selected",PropertyInfo(Variant::OBJECT,"item"),PropertyInfo(Variant::INT,"column"),PropertyInfo(Variant::BOOL,"selected")) );
+ ADD_SIGNAL( MethodInfo("item_edited"));
+ ADD_SIGNAL( MethodInfo("item_collapsed",PropertyInfo(Variant::OBJECT,"item")));
+ //ADD_SIGNAL( MethodInfo("item_doubleclicked" ) );
+ ADD_SIGNAL( MethodInfo("button_pressed",PropertyInfo(Variant::OBJECT,"item"),PropertyInfo(Variant::INT,"column"),PropertyInfo(Variant::INT,"id")));
+ ADD_SIGNAL( MethodInfo("custom_popup_edited",PropertyInfo(Variant::BOOL,"arrow_clicked") ) );
+ ADD_SIGNAL( MethodInfo("item_activated"));
+
+ BIND_CONSTANT( SELECT_SINGLE );
+ BIND_CONSTANT( SELECT_ROW );
+ BIND_CONSTANT( SELECT_MULTI );
+}
+
+Tree::Tree() {
+
+ selected_col=0;
+ columns.resize(1);
+ selected_item=NULL;
+ edited_item=NULL;
+ selected_col=-1;
+ edited_col=-1;
+
+ hide_root=false;
+ select_mode=SELECT_SINGLE;
+ root=0;
+ popup_menu=NULL;
+ popup_edited_item=NULL;
+ text_editor=NULL;
+ set_focus_mode(FOCUS_ALL);
+
+
+ popup_menu = memnew( PopupMenu );
+ popup_menu->hide();
+ add_child(popup_menu);
+ popup_menu->set_as_toplevel(true);
+ text_editor = memnew( LineEdit );
+ add_child(text_editor);
+ text_editor->set_as_toplevel(true);
+ text_editor->hide();
+ value_editor = memnew( HSlider );
+ add_child(value_editor);
+ value_editor->set_as_toplevel(true);
+ value_editor->hide();
+
+ h_scroll = memnew( HScrollBar );
+ v_scroll = memnew( VScrollBar );
+
+ add_child(h_scroll);
+ add_child(v_scroll);
+
+ h_scroll->connect("value_changed", this,"_scroll_moved");
+ v_scroll->connect("value_changed", this,"_scroll_moved");
+ text_editor->connect("text_entered", this,"_text_editor_enter");
+ popup_menu->connect("item_pressed", this,"_popup_select");
+ value_editor->connect("value_changed", this,"_value_editor_changed");
+
+ value_editor->set_as_toplevel(true);
+ text_editor->set_as_toplevel(true);
+
+ updating_value_editor=false;
+ pressed_button=-1;
+ show_column_titles=false;
+
+ cache.click_type = Cache::CLICK_NONE;
+ cache.hover_type = Cache::CLICK_NONE;
+ cache.hover_index = -1;
+ cache.click_index=-1;
+ cache.click_id=-1;
+ cache.click_item=NULL;
+ cache.click_column=0;
+ last_keypress=0;
+ focus_in_id=0;
+
+ blocked=0;
+
+ cursor_can_exit_tree=true;
+ set_stop_mouse(true);
+
+ drag_speed=0;
+ drag_touching=false;
+ drag_touching_deaccel=false;
+
+}
+
+
+Tree::~Tree() {
+
+ if (root) {
+ memdelete( root );
+ }
+
+}
+
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
new file mode 100644
index 0000000000..3ffbececb2
--- /dev/null
+++ b/scene/gui/tree.h
@@ -0,0 +1,464 @@
+/*************************************************************************/
+/* tree.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef TREE_H
+#define TREE_H
+
+#include "scene/gui/control.h"
+#include "scene/gui/popup_menu.h"
+#include "scene/gui/line_edit.h"
+#include "scene/gui/scroll_bar.h"
+#include "scene/gui/slider.h"
+
+/**
+ @author Juan Linietsky <reduzio@gmail.com>
+*/
+
+
+class Tree;
+
+class TreeItem : public Object {
+
+ OBJ_TYPE(TreeItem,Object);
+public:
+
+ enum TreeCellMode {
+
+ CELL_MODE_STRING, ///< just a string
+ CELL_MODE_CHECK, ///< string + check
+ CELL_MODE_RANGE, ///< Contains a range
+ CELL_MODE_ICON, ///< Contains a icon, not editable
+ CELL_MODE_CUSTOM, ///< Contains a custom value, show a string, and an edit button
+ };
+
+private:
+friend class Tree;
+
+
+ struct Cell {
+
+ TreeCellMode mode;
+
+ Ref<Texture> icon;
+ Rect2i icon_region;
+ String text;
+ double min,max,step,val;
+ int icon_max_w;
+ bool expr;
+ bool checked;
+ bool editable;
+ bool selected;
+ bool selectable;
+ bool custom_color;
+ Color color;
+ bool custom_bg_color;
+ Color bg_color;
+ Variant meta;
+ String tooltip;
+
+ ObjectID custom_draw_obj;
+ StringName custom_draw_callback;
+
+ struct Button {
+ int id;
+ Ref<Texture> texture;
+ Button() { id=0; }
+ };
+
+ Vector< Button > buttons;
+
+ Cell() {
+
+ custom_draw_obj=0;
+ mode=TreeItem::CELL_MODE_STRING;
+ min=0;
+ max=100;
+ step=1;
+ val=0;
+ checked=false;
+ editable=false;
+ selected=false;
+ selectable=true;
+ custom_color=false;
+ custom_bg_color=false;
+ expr=false;
+ icon_max_w=0;
+ }
+
+
+ Size2 get_icon_size() const;
+ void draw_icon(const RID& p_where, const Point2& p_pos, const Size2& p_size=Size2()) const;
+
+ };
+
+ Vector<Cell> cells;
+
+ bool collapsed; // wont show childs
+
+ TreeItem *parent; // parent item
+ TreeItem *next; // next in list
+ TreeItem *childs; //child items
+ Tree *tree; //tree (for reference)
+
+
+
+ TreeItem(Tree *p_tree);
+
+
+ void _changed_notify(int p_cell);
+ void _changed_notify();
+ void _cell_selected(int p_cell);
+ void _cell_deselected(int p_cell);
+protected:
+
+ static void _bind_methods();
+ //bind helpers
+ Dictionary _get_range_config( int p_column ) {
+ Dictionary d;
+ double min,max,step;
+ get_range_config(p_column,min,max,step);
+ d["min"]=min;
+ d["max"]=max;
+ d["step"]=step;
+ d["expr"]=false;
+
+ return d;
+ }
+ void _remove_child(Object *p_child) { remove_child( p_child->cast_to<TreeItem>() ); }
+public:
+
+ /* cell mode */
+ void set_cell_mode( int p_column, TreeCellMode p_mode );
+ TreeCellMode get_cell_mode( int p_column ) const;
+
+ /* check mode */
+ void set_checked(int p_column,bool p_checked);
+ bool is_checked(int p_column) const;
+
+ void set_text(int p_column,String p_text);
+ String get_text(int p_column) const;
+
+ void set_icon(int p_column,const Ref<Texture>& p_icon);
+ Ref<Texture> get_icon(int p_column) const;
+
+ void set_icon_region(int p_column,const Rect2& p_icon_region);
+ Rect2 get_icon_region(int p_column) const;
+
+ void set_icon_max_width(int p_column,int p_max);
+ int get_icon_max_width(int p_column) const;
+
+ void add_button(int p_column,const Ref<Texture>& p_button,int p_id=-1);
+ int get_button_count(int p_column) const;
+ Ref<Texture> get_button(int p_column,int p_idx) const;
+ int get_button_id(int p_column,int p_idx) const;
+ void erase_button(int p_column,int p_idx);
+ int get_button_by_id(int p_column,int p_id) const;
+ void set_button(int p_column,int p_idx,const Ref<Texture>& p_button);
+
+ /* range works for mode number or mode combo */
+
+ void set_range(int p_column,double p_value);
+ double get_range(int p_column) const;
+
+ void set_range_config(int p_column,double p_min,double p_max,double p_step,bool p_exp=false);
+ void get_range_config(int p_column,double& r_min,double& r_max,double &r_step) const;
+ bool is_range_exponential(int p_column) const;
+
+ void set_metadata(int p_column,const Variant& p_meta);
+ Variant get_metadata(int p_column) const;
+
+ void set_custom_draw(int p_column,Object *p_object,const StringName& p_callback);
+
+ void set_collapsed(bool p_collapsed);
+ bool is_collapsed();
+
+ TreeItem *get_prev();
+ TreeItem *get_next();
+ TreeItem *get_parent();
+ TreeItem *get_children();
+
+ TreeItem *get_prev_visible();
+ TreeItem *get_next_visible();
+
+ void remove_child(TreeItem *p_item);
+
+ void set_selectable(int p_column,bool p_selectable);
+ bool is_selectable(int p_column) const;
+
+ bool is_selected(int p_column);
+ void select(int p_column);
+ void deselect(int p_column);
+ void set_as_cursor(int p_column);
+
+ void set_editable(int p_column,bool p_editable);
+ bool is_editable(int p_column);
+
+ void set_custom_color(int p_column,const Color& p_color);
+ void clear_custom_color(int p_column);
+
+ void set_custom_bg_color(int p_column,const Color& p_color);
+ void clear_custom_bg_color(int p_column);
+ Color get_custom_bg_color(int p_column) const;
+
+ void set_tooltip(int p_column, const String& p_tooltip);
+ String get_tooltip(int p_column) const;
+
+ void clear_children();
+
+ void move_to_top();
+ void move_to_bottom();
+
+ ~TreeItem();
+
+};
+
+
+VARIANT_ENUM_CAST( TreeItem::TreeCellMode );
+
+
+class Tree : public Control {
+
+ OBJ_TYPE( Tree, Control );
+public:
+ enum SelectMode {
+ SELECT_SINGLE,
+ SELECT_ROW,
+ SELECT_MULTI
+ };
+
+private:
+friend class TreeItem;
+
+ TreeItem *root;
+ TreeItem *popup_edited_item;
+ TreeItem *selected_item;
+ TreeItem *edited_item;
+ int pressed_button;
+
+ //TreeItem *cursor_item;
+ //int cursor_column;
+
+ Rect2 custom_popup_rect;
+ int edited_col;
+ int selected_col;
+ int popup_edited_item_col;
+ bool hide_root;
+ SelectMode select_mode;
+
+ int blocked;
+
+ struct ColumnInfo {
+
+ int min_width;
+ bool expand;
+ String title;
+ ColumnInfo() { min_width=1; expand=true; }
+ };
+
+ bool show_column_titles;
+ LineEdit *text_editor;
+ HSlider *value_editor;
+ bool updating_value_editor;
+ uint32_t focus_in_id;
+ PopupMenu *popup_menu;
+
+ Vector<ColumnInfo> columns;
+
+ int compute_item_height(TreeItem *p_item) const;
+ int get_item_height(TreeItem *p_item) const;
+// void draw_item_text(String p_text,const Ref<Texture>& p_icon,int p_icon_max_w,bool p_tool,Rect2i p_rect,const Color& p_color);
+ void draw_item_rect(const TreeItem::Cell& p_cell,const Rect2i& p_rect,const Color& p_color);
+ int draw_item(const Point2i& p_pos,const Point2& p_draw_ofs, const Size2& p_draw_size,TreeItem *p_item);
+ void select_single_item(TreeItem *p_selected,TreeItem *p_current,int p_col,TreeItem *p_prev=NULL,bool *r_in_range=NULL);
+ int propagate_mouse_event(const Point2i &p_pos,int x_ofs,int y_ofs,bool p_doubleclick,TreeItem *p_item,int p_button,const InputModifierState& p_mod);
+ void text_editor_enter(String p_text);
+ void value_editor_changed(double p_value);
+
+ void popup_select(int p_option);
+
+ void _input_event(InputEvent p_event);
+ void _notification(int p_what);
+
+ Size2 get_minimum_size() const;
+
+ void item_edited(int p_column,TreeItem *p_item);
+ void item_changed(int p_column,TreeItem *p_item);
+ void item_selected(int p_column,TreeItem *p_item);
+ void item_deselected(int p_column,TreeItem *p_item);
+
+ void propagate_set_columns(TreeItem *p_item);
+
+ struct Cache {
+
+ Ref<Font> font;
+ Ref<Font> tb_font;
+ Ref<StyleBox> bg;
+ Ref<StyleBox> selected;
+ Ref<StyleBox> selected_focus;
+ Ref<StyleBox> cursor;
+ Ref<StyleBox> cursor_unfocus;
+ Ref<StyleBox> button_pressed;
+ Ref<StyleBox> title_button;
+ Ref<StyleBox> title_button_hover;
+ Ref<StyleBox> title_button_pressed;
+ Color title_button_color;
+
+ Ref<Texture> checked;
+ Ref<Texture> unchecked;
+ Ref<Texture> arrow_collapsed;
+ Ref<Texture> arrow;
+ Ref<Texture> select_arrow;
+ Ref<Texture> updown;
+
+ Color font_color;
+ Color font_color_selected;
+ Color guide_color;
+ int hseparation;
+ int vseparation;
+ int item_margin;
+ int guide_width;
+ int button_margin;
+ Point2 offset;
+
+ enum ClickType {
+ CLICK_NONE,
+ CLICK_TITLE,
+ CLICK_BUTTON,
+
+ };
+
+ ClickType click_type;
+ ClickType hover_type;
+ int click_index;
+ int click_id;
+ TreeItem *click_item;
+ int click_column;
+ int hover_index;
+
+ } cache;
+
+
+ int _get_title_button_height() const;
+
+ void _scroll_moved(float p_value);
+ HScrollBar *h_scroll;
+ VScrollBar *v_scroll;
+
+ Size2 get_internal_min_size() const;
+ void update_cache();
+ void update_scrollbars();
+
+ Rect2 search_item_rect(TreeItem *p_from, TreeItem *p_item);
+ //Rect2 get_item_rect(TreeItem *p_item);
+ uint64_t last_keypress;
+ String incr_search;
+ bool cursor_can_exit_tree;
+ void _do_incr_search(const String& p_add);
+
+ TreeItem* _search_item_text(TreeItem *p_at, const String& p_find,int *r_col,bool p_selectable,bool p_backwards=false);
+
+ TreeItem* _find_item_at_pos(TreeItem *p_current, const Point2& p_pos,int& r_column,int &h) const;
+
+/* float drag_speed;
+ float drag_accum;
+
+ float last_drag_accum;
+ float last_drag_time;
+ float time_since_motion;*/
+
+ float drag_speed;
+ float drag_from;
+ float drag_accum;
+ Vector2 last_speed;
+ bool drag_touching;
+ bool drag_touching_deaccel;
+ bool click_handled;
+
+protected:
+ static void _bind_methods();
+
+ //bind helpers
+ Object* _create_item(Object *p_parent) { return create_item(p_parent->cast_to<TreeItem>() ); }
+ TreeItem *_get_next_selected(Object *p_item) { return get_next_selected(p_item->cast_to<TreeItem>() ); }
+ Rect2 _get_item_rect(Object *p_item,int p_column) const { return get_item_rect(p_item->cast_to<TreeItem>(),p_column ); }
+public:
+
+ virtual String get_tooltip(const Point2& p_pos) const;
+
+ void clear();
+
+ TreeItem* create_item(TreeItem *p_parent=0);
+ TreeItem* get_root();
+ TreeItem* get_last_item();
+
+ void set_column_min_width(int p_column,int p_min_width);
+ void set_column_expand(int p_column,bool p_expand);
+ int get_column_width(int p_column) const;
+
+ void set_hide_root(bool p_eanbled);
+ TreeItem *get_next_selected( TreeItem* p_item);
+ TreeItem *get_selected() const;
+ int get_selected_column() const;
+ int get_pressed_button() const;
+ void set_select_mode(SelectMode p_mode);
+
+ void set_columns(int p_columns);
+ int get_columns() const;
+
+ void set_column_title(int p_column,const String& p_title);
+ String get_column_title(int p_column) const;
+
+ void set_column_titles_visible(bool p_show);
+ bool are_column_titles_visible() const;
+
+ TreeItem *get_edited() const;
+ int get_edited_column() const;
+
+ void ensure_cursor_is_visible();
+
+ Rect2 get_custom_popup_rect() const;
+
+ int get_item_offset(TreeItem *p_item) const;
+ Rect2 get_item_rect(TreeItem *p_item,int p_column=-1) const;
+ bool edit_selected();
+
+ TreeItem* search_item_text(const String& p_find,int *r_col=NULL,bool p_selectable=false);
+
+ Point2 get_scroll() const;
+
+ void set_cursor_can_exit_tree(bool p_enable);
+ bool can_cursor_exit_tree() const;
+
+ Tree();
+ ~Tree();
+
+};
+
+VARIANT_ENUM_CAST( Tree::SelectMode );
+#endif
+
diff --git a/scene/gui/video_player.cpp b/scene/gui/video_player.cpp
new file mode 100644
index 0000000000..e3bb50a9af
--- /dev/null
+++ b/scene/gui/video_player.cpp
@@ -0,0 +1,280 @@
+/*************************************************************************/
+/* video_player.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#include "video_player.h"
+
+void VideoPlayer::_notification(int p_notification) {
+
+ switch (p_notification) {
+
+ case NOTIFICATION_ENTER_SCENE: {
+
+ //set_idle_process(false); //don't annoy
+ if (stream.is_valid() && autoplay && !get_scene()->is_editor_hint())
+ play();
+ } break;
+
+ case NOTIFICATION_PROCESS: {
+
+ if (stream.is_null())
+ return;
+ if (paused)
+ return;
+
+ while (stream->get_pending_frame_count()) {
+
+ Image img = stream->pop_frame();
+ if (texture->get_width() == 0) {
+ texture->create(img.get_width(),img.get_height(),img.get_format(),Texture::FLAG_VIDEO_SURFACE|Texture::FLAG_FILTER);
+ update();
+ minimum_size_changed();
+ } else {
+
+ if (stream->get_pending_frame_count() == 0)
+ texture->set_data(img);
+ };
+ };
+
+ } break;
+
+ case NOTIFICATION_DRAW: {
+
+ if (texture.is_null())
+ return;
+ if (texture->get_width() == 0)
+ return;
+
+ Size2 s=expand?get_size():texture->get_size();
+ RID ci = get_canvas_item();
+ printf("drawing with size %f, %f\n", s.x, s.y);
+ draw_texture_rect(texture,Rect2(Point2(),s),false);
+
+ } break;
+ };
+
+};
+
+Size2 VideoPlayer::get_minimum_size() const {
+
+ if (!expand && !texture.is_null())
+ return texture->get_size();
+ else
+ return Size2();
+}
+
+void VideoPlayer::set_expand(bool p_expand) {
+
+ expand=p_expand;
+ update();
+ minimum_size_changed();
+}
+
+bool VideoPlayer::has_expand() const {
+
+ return expand;
+}
+
+
+void VideoPlayer::set_stream(const Ref<VideoStream> &p_stream) {
+
+ stop();
+
+ if (stream_rid.is_valid())
+ AudioServer::get_singleton()->free(stream_rid);
+ stream_rid=RID();
+
+ texture = Ref<ImageTexture>(memnew(ImageTexture));
+
+ stream=p_stream;
+ if (!stream.is_null()) {
+
+ stream->set_loop(loops);
+ stream->set_paused(paused);
+ stream_rid=AudioServer::get_singleton()->audio_stream_create(stream->get_audio_stream());
+ }
+
+};
+
+Ref<VideoStream> VideoPlayer::get_stream() const {
+
+ return stream;
+};
+
+void VideoPlayer::play() {
+
+ ERR_FAIL_COND(!is_inside_scene());
+ if (stream.is_null())
+ return;
+ stream->play();
+ AudioServer::get_singleton()->stream_set_active(stream_rid,true);
+ AudioServer::get_singleton()->stream_set_volume_scale(stream_rid,volume);
+ set_process(true);
+};
+
+void VideoPlayer::stop() {
+
+ if (!is_inside_scene())
+ return;
+ if (stream.is_null())
+ return;
+
+ AudioServer::get_singleton()->stream_set_active(stream_rid,false);
+ stream->stop();
+ set_process(false);
+};
+
+bool VideoPlayer::is_playing() const {
+
+ if (stream.is_null())
+ return false;
+
+ return stream->is_playing();
+};
+
+void VideoPlayer::set_paused(bool p_paused) {
+
+ paused=p_paused;
+ if (stream.is_valid()) {
+ stream->set_paused(p_paused);
+ set_process(!p_paused);
+ };
+};
+
+bool VideoPlayer::is_paused() const {
+
+ return paused;
+};
+
+void VideoPlayer::set_volume(float p_vol) {
+
+ volume=p_vol;
+ if (stream_rid.is_valid())
+ AudioServer::get_singleton()->stream_set_volume_scale(stream_rid,volume);
+};
+
+float VideoPlayer::get_volume() const {
+
+ return volume;
+};
+
+void VideoPlayer::set_volume_db(float p_db) {
+
+ if (p_db<-79)
+ set_volume(0);
+ else
+ set_volume(Math::db2linear(p_db));
+};
+
+float VideoPlayer::get_volume_db() const {
+
+ if (volume==0)
+ return -80;
+ else
+ return Math::linear2db(volume);
+};
+
+
+String VideoPlayer::get_stream_name() const {
+
+ if (stream.is_null())
+ return "<No Stream>";
+ return stream->get_name();
+};
+
+float VideoPlayer::get_pos() const {
+
+ if (stream.is_null())
+ return 0;
+ return stream->get_pos();
+};
+
+void VideoPlayer::set_autoplay(bool p_enable) {
+
+ autoplay=p_enable;
+};
+
+bool VideoPlayer::has_autoplay() const {
+
+ return autoplay;
+};
+
+void VideoPlayer::_bind_methods() {
+
+ ObjectTypeDB::bind_method(_MD("set_stream","stream:Stream"),&VideoPlayer::set_stream);
+ ObjectTypeDB::bind_method(_MD("get_stream:Stream"),&VideoPlayer::get_stream);
+
+ ObjectTypeDB::bind_method(_MD("play"),&VideoPlayer::play);
+ ObjectTypeDB::bind_method(_MD("stop"),&VideoPlayer::stop);
+
+ ObjectTypeDB::bind_method(_MD("is_playing"),&VideoPlayer::is_playing);
+
+ ObjectTypeDB::bind_method(_MD("set_paused","paused"),&VideoPlayer::set_paused);
+ ObjectTypeDB::bind_method(_MD("is_paused"),&VideoPlayer::is_paused);
+
+ ObjectTypeDB::bind_method(_MD("set_volume","volume"),&VideoPlayer::set_volume);
+ ObjectTypeDB::bind_method(_MD("get_volume"),&VideoPlayer::get_volume);
+
+ ObjectTypeDB::bind_method(_MD("set_volume_db","db"),&VideoPlayer::set_volume_db);
+ ObjectTypeDB::bind_method(_MD("get_volume_db"),&VideoPlayer::get_volume_db);
+
+ ObjectTypeDB::bind_method(_MD("get_stream_name"),&VideoPlayer::get_stream_name);
+
+ ObjectTypeDB::bind_method(_MD("get_pos"),&VideoPlayer::get_pos);
+
+ ObjectTypeDB::bind_method(_MD("set_autoplay","enabled"),&VideoPlayer::set_autoplay);
+ ObjectTypeDB::bind_method(_MD("has_autoplay"),&VideoPlayer::has_autoplay);
+
+ ObjectTypeDB::bind_method(_MD("set_expand","enable"), &VideoPlayer::set_expand );
+ ObjectTypeDB::bind_method(_MD("has_expand"), &VideoPlayer::has_expand );
+
+
+ ADD_PROPERTY( PropertyInfo(Variant::OBJECT, "stream/stream", PROPERTY_HINT_RESOURCE_TYPE,"AudioStream"), _SCS("set_stream"), _SCS("get_stream") );
+// ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/loop"), _SCS("set_loop"), _SCS("has_loop") );
+ ADD_PROPERTY( PropertyInfo(Variant::REAL, "stream/volume_db", PROPERTY_HINT_RANGE,"-80,24,0.01"), _SCS("set_volume_db"), _SCS("get_volume_db") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/autoplay"), _SCS("set_autoplay"), _SCS("has_autoplay") );
+ ADD_PROPERTY( PropertyInfo(Variant::BOOL, "stream/paused"), _SCS("set_paused"), _SCS("is_paused") );
+ ADD_PROPERTY( PropertyInfo( Variant::BOOL, "expand" ), _SCS("set_expand"),_SCS("has_expand") );
+}
+
+
+VideoPlayer::VideoPlayer() {
+
+ volume=1;
+ loops=false;
+ paused=false;
+ autoplay=false;
+ expand = true;
+ loops = false;
+};
+
+VideoPlayer::~VideoPlayer() {
+
+ if (stream_rid.is_valid())
+ AudioServer::get_singleton()->free(stream_rid);
+};
+
diff --git a/scene/gui/video_player.h b/scene/gui/video_player.h
new file mode 100644
index 0000000000..db5f9a58a6
--- /dev/null
+++ b/scene/gui/video_player.h
@@ -0,0 +1,89 @@
+/*************************************************************************/
+/* video_player.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+#ifndef VIDEO_PLAYER_H
+#define VIDEO_PLAYER_H
+
+#include "scene/resources/video_stream.h"
+#include "scene/gui/control.h"
+
+class VideoPlayer : public Control {
+
+ OBJ_TYPE(VideoPlayer,Control);
+
+ Ref<VideoStream> stream;
+ RID stream_rid;
+
+ Ref<ImageTexture> texture;
+ Image last_frame;
+
+ bool paused;
+ bool autoplay;
+ float volume;
+ bool expand;
+ bool loops;
+
+protected:
+
+ static void _bind_methods();
+ void _notification(int p_notification);
+
+public:
+
+ Size2 get_minimum_size() const;
+ void set_expand(bool p_expand);
+ bool has_expand() const;
+
+
+ void set_stream(const Ref<VideoStream> &p_stream);
+ Ref<VideoStream> get_stream() const;
+
+ void play();
+ void stop();
+ bool is_playing() const;
+
+ void set_paused(bool p_paused);
+ bool is_paused() const;
+
+ void set_volume(float p_vol);
+ float get_volume() const;
+
+ void set_volume_db(float p_db);
+ float get_volume_db() const;
+
+ String get_stream_name() const;
+ float get_pos() const;
+
+ void set_autoplay(bool p_vol);
+ bool has_autoplay() const;
+
+ VideoPlayer();
+ ~VideoPlayer();
+};
+
+#endif