diff options
author | Juan Linietsky <reduzio@gmail.com> | 2014-02-09 22:10:30 -0300 |
---|---|---|
committer | Juan Linietsky <reduzio@gmail.com> | 2014-02-09 22:10:30 -0300 |
commit | 0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac (patch) | |
tree | 276c4d099e178eb67fbd14f61d77b05e3808e9e3 /scene/gui | |
parent | 0e49da1687bc8192ed210947da52c9e5c5f301bb (diff) | |
download | redot-engine-0b806ee0fc9097fa7bda7ac0109191c9c5e0a1ac.tar.gz |
GODOT IS OPEN SOURCE
Diffstat (limited to 'scene/gui')
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 |