2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* editor_spin_slider.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* 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. */
/**************************************************************************/
2018-05-16 19:19:33 +02:00
2018-05-15 22:12:35 +02:00
# include "editor_spin_slider.h"
2020-03-04 17:36:09 +01:00
2020-04-28 15:19:37 +02:00
# include "core/input/input.h"
2018-09-11 18:13:45 +02:00
# include "core/math/expression.h"
2021-08-02 12:43:43 +02:00
# include "core/os/keyboard.h"
2022-02-12 02:46:22 +01:00
# include "editor/editor_scale.h"
2021-07-21 00:45:29 +02:00
# include "editor/editor_settings.h"
2018-08-07 17:19:19 +02:00
String EditorSpinSlider : : get_tooltip ( const Point2 & p_pos ) const {
2020-05-06 02:38:15 +02:00
if ( grabber - > is_visible ( ) ) {
2022-11-14 15:27:26 +01:00
Key key = ( OS : : get_singleton ( ) - > has_feature ( " macos " ) | | OS : : get_singleton ( ) - > has_feature ( " web_macos " ) | | OS : : get_singleton ( ) - > has_feature ( " web_ios " ) ) ? Key : : META : Key : : CTRL ;
2022-08-31 11:34:43 +02:00
return TS - > format_number ( rtos ( get_value ( ) ) ) + " \n \n " + vformat ( TTR ( " Hold %s to round to integers. \n Hold Shift for more precise changes. " ) , find_keycode_name ( key ) ) ;
2020-05-06 02:38:15 +02:00
}
2020-09-03 13:22:16 +02:00
return TS - > format_number ( rtos ( get_value ( ) ) ) ;
2018-08-07 17:19:19 +02:00
}
2018-05-15 22:12:35 +02:00
String EditorSpinSlider : : get_text_value ( ) const {
2020-09-03 13:22:16 +02:00
return TS - > format_number ( String : : num ( get_value ( ) , Math : : range_step_decimals ( get_step ( ) ) ) ) ;
2018-05-15 22:12:35 +02:00
}
2019-07-23 17:27:55 +02:00
2021-08-22 17:37:22 +02:00
void EditorSpinSlider : : gui_input ( const Ref < InputEvent > & p_event ) {
2021-04-05 08:52:21 +02:00
ERR_FAIL_COND ( p_event . is_null ( ) ) ;
2020-05-14 16:41:43 +02:00
if ( read_only ) {
2018-06-07 17:46:14 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-06-07 17:46:14 +02:00
2018-05-15 22:12:35 +02:00
Ref < InputEventMouseButton > mb = p_event ;
2019-08-17 21:12:05 +02:00
if ( mb . is_valid ( ) ) {
2021-08-13 23:31:57 +02:00
if ( mb - > get_button_index ( ) = = MouseButton : : LEFT ) {
2019-08-17 21:12:05 +02:00
if ( mb - > is_pressed ( ) ) {
if ( updown_offset ! = - 1 & & mb - > get_position ( ) . x > updown_offset ) {
//there is an updown, so use it.
if ( mb - > get_position ( ) . y < get_size ( ) . height / 2 ) {
set_value ( get_value ( ) + get_step ( ) ) ;
} else {
set_value ( get_value ( ) - get_step ( ) ) ;
}
return ;
2018-05-15 22:12:35 +02:00
} else {
2019-08-17 21:12:05 +02:00
grabbing_spinner_attempt = true ;
grabbing_spinner_dist_cache = 0 ;
pre_grab_value = get_value ( ) ;
grabbing_spinner = false ;
2021-05-23 07:18:26 +02:00
grabbing_spinner_mouse_pos = get_global_mouse_position ( ) ;
2022-12-08 13:38:01 +01:00
emit_signal ( " grabbed " ) ;
2018-05-15 22:12:35 +02:00
}
} else {
2019-08-17 21:12:05 +02:00
if ( grabbing_spinner_attempt ) {
if ( grabbing_spinner ) {
2020-04-28 15:19:37 +02:00
Input : : get_singleton ( ) - > set_mouse_mode ( Input : : MOUSE_MODE_VISIBLE ) ;
2022-03-27 11:17:36 +02:00
Input : : get_singleton ( ) - > warp_mouse ( grabbing_spinner_mouse_pos ) ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2022-12-08 13:38:01 +01:00
emit_signal ( " ungrabbed " ) ;
2019-08-17 21:12:05 +02:00
} else {
_focus_entered ( ) ;
}
2018-05-15 22:12:35 +02:00
2019-08-17 21:12:05 +02:00
grabbing_spinner = false ;
grabbing_spinner_attempt = false ;
2018-05-15 22:12:35 +02:00
}
}
2021-08-13 23:31:57 +02:00
} else if ( mb - > get_button_index ( ) = = MouseButton : : WHEEL_UP | | mb - > get_button_index ( ) = = MouseButton : : WHEEL_DOWN ) {
2020-05-14 16:41:43 +02:00
if ( grabber - > is_visible ( ) ) {
2023-12-18 15:46:56 +01:00
callable_mp ( ( CanvasItem * ) this , & CanvasItem : : queue_redraw ) . call_deferred ( ) ;
2020-05-14 16:41:43 +02:00
}
2018-05-15 22:12:35 +02:00
}
}
Ref < InputEventMouseMotion > mm = p_event ;
if ( mm . is_valid ( ) ) {
if ( grabbing_spinner_attempt ) {
2018-05-21 15:02:20 +02:00
double diff_x = mm - > get_relative ( ) . x ;
2021-04-24 22:33:50 +02:00
if ( mm - > is_shift_pressed ( ) & & grabbing_spinner ) {
2018-05-21 15:02:20 +02:00
diff_x * = 0.1 ;
}
2021-07-21 00:45:29 +02:00
grabbing_spinner_dist_cache + = diff_x * grabbing_spinner_speed ;
2018-05-21 15:02:20 +02:00
2023-06-16 09:21:22 +02:00
if ( ! grabbing_spinner & & ABS ( grabbing_spinner_dist_cache ) > 4 * grabbing_spinner_speed * EDSCALE ) {
2020-04-28 15:19:37 +02:00
Input : : get_singleton ( ) - > set_mouse_mode ( Input : : MOUSE_MODE_CAPTURED ) ;
2018-05-15 22:12:35 +02:00
grabbing_spinner = true ;
2018-07-18 20:35:01 +02:00
}
if ( grabbing_spinner ) {
2020-05-06 02:38:15 +02:00
// Don't make the user scroll all the way back to 'in range' if they went off the end.
if ( pre_grab_value < get_min ( ) & & ! is_lesser_allowed ( ) ) {
pre_grab_value = get_min ( ) ;
}
if ( pre_grab_value > get_max ( ) & & ! is_greater_allowed ( ) ) {
pre_grab_value = get_max ( ) ;
}
2022-09-02 11:37:48 +02:00
if ( mm - > is_command_or_control_pressed ( ) ) {
2020-05-06 02:38:15 +02:00
// If control was just pressed, don't make the value do a huge jump in magnitude.
if ( grabbing_spinner_dist_cache ! = 0 ) {
pre_grab_value + = grabbing_spinner_dist_cache * get_step ( ) ;
grabbing_spinner_dist_cache = 0 ;
}
2019-09-06 19:38:11 +02:00
set_value ( Math : : round ( pre_grab_value + get_step ( ) * grabbing_spinner_dist_cache * 10 ) ) ;
2018-05-21 15:02:20 +02:00
} else {
2019-11-05 15:53:19 +01:00
set_value ( pre_grab_value + get_step ( ) * grabbing_spinner_dist_cache ) ;
2018-05-21 15:02:20 +02:00
}
2018-05-15 22:12:35 +02:00
}
} else if ( updown_offset ! = - 1 ) {
bool new_hover = ( mm - > get_position ( ) . x > updown_offset ) ;
if ( new_hover ! = hover_updown ) {
hover_updown = new_hover ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
}
}
Ref < InputEventKey > k = p_event ;
2022-09-24 10:01:02 +02:00
if ( k . is_valid ( ) & & k - > is_pressed ( ) & & k - > is_action ( " ui_accept " , true ) ) {
2018-05-21 21:36:43 +02:00
_focus_entered ( ) ;
2018-05-15 22:12:35 +02:00
}
}
void EditorSpinSlider : : _grabber_gui_input ( const Ref < InputEvent > & p_event ) {
2022-05-04 07:31:53 +02:00
if ( read_only ) {
return ;
}
2018-05-15 22:12:35 +02:00
Ref < InputEventMouseButton > mb = p_event ;
2019-08-13 12:11:24 +02:00
2022-08-01 01:20:24 +02:00
if ( is_read_only ( ) ) {
return ;
}
2019-08-13 12:11:24 +02:00
if ( grabbing_grabber ) {
if ( mb . is_valid ( ) ) {
2021-08-13 23:31:57 +02:00
if ( mb - > get_button_index ( ) = = MouseButton : : WHEEL_UP ) {
2019-08-13 12:11:24 +02:00
set_value ( get_value ( ) + get_step ( ) ) ;
mousewheel_over_grabber = true ;
2021-08-13 23:31:57 +02:00
} else if ( mb - > get_button_index ( ) = = MouseButton : : WHEEL_DOWN ) {
2019-08-13 12:11:24 +02:00
set_value ( get_value ( ) - get_step ( ) ) ;
mousewheel_over_grabber = true ;
}
}
}
2021-08-13 23:31:57 +02:00
if ( mb . is_valid ( ) & & mb - > get_button_index ( ) = = MouseButton : : LEFT ) {
2018-05-15 22:12:35 +02:00
if ( mb - > is_pressed ( ) ) {
grabbing_grabber = true ;
2019-08-13 12:11:24 +02:00
if ( ! mousewheel_over_grabber ) {
grabbing_ratio = get_as_ratio ( ) ;
grabbing_from = grabber - > get_transform ( ) . xform ( mb - > get_position ( ) ) . x ;
}
2022-12-08 13:38:01 +01:00
emit_signal ( " grabbed " ) ;
2018-05-15 22:12:35 +02:00
} else {
grabbing_grabber = false ;
2019-08-13 12:11:24 +02:00
mousewheel_over_grabber = false ;
2022-12-08 13:38:01 +01:00
emit_signal ( " ungrabbed " ) ;
2018-05-15 22:12:35 +02:00
}
}
Ref < InputEventMouseMotion > mm = p_event ;
if ( mm . is_valid ( ) & & grabbing_grabber ) {
2020-05-14 16:41:43 +02:00
if ( mousewheel_over_grabber ) {
2019-08-13 12:11:24 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-05-15 22:12:35 +02:00
2021-03-05 15:06:05 +01:00
float scale_x = get_global_transform_with_canvas ( ) . get_scale ( ) . x ;
ERR_FAIL_COND ( Math : : is_zero_approx ( scale_x ) ) ;
float grabbing_ofs = ( grabber - > get_transform ( ) . xform ( mm - > get_position ( ) ) . x - grabbing_from ) / float ( grabber_range ) / scale_x ;
2018-05-15 22:12:35 +02:00
set_as_ratio ( grabbing_ratio + grabbing_ofs ) ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
}
2020-09-07 16:24:35 +02:00
void EditorSpinSlider : : _value_input_gui_input ( const Ref < InputEvent > & p_event ) {
Ref < InputEventKey > k = p_event ;
2022-08-01 01:20:24 +02:00
if ( k . is_valid ( ) & & k - > is_pressed ( ) & & ! is_read_only ( ) ) {
2020-09-07 16:24:35 +02:00
double step = get_step ( ) ;
if ( step < 1 ) {
double divisor = 1.0 / get_step ( ) ;
if ( trunc ( divisor ) = = divisor ) {
step = 1.0 ;
}
}
2023-06-08 23:24:00 +02:00
if ( k - > is_command_or_control_pressed ( ) ) {
2020-09-07 16:24:35 +02:00
step * = 100.0 ;
} else if ( k - > is_shift_pressed ( ) ) {
step * = 10.0 ;
} else if ( k - > is_alt_pressed ( ) ) {
step * = 0.1 ;
}
2021-08-13 23:31:57 +02:00
Key code = k - > get_keycode ( ) ;
2020-09-07 16:24:35 +02:00
2023-09-03 19:23:14 +02:00
switch ( code ) {
case Key : : UP :
2021-08-13 23:31:57 +02:00
case Key : : DOWN : {
2020-09-07 16:24:35 +02:00
_evaluate_input_text ( ) ;
double last_value = get_value ( ) ;
2023-09-03 19:23:14 +02:00
if ( code = = Key : : DOWN ) {
step * = - 1 ;
}
set_value ( last_value + step ) ;
2020-09-07 16:24:35 +02:00
double new_value = get_value ( ) ;
2023-09-03 19:23:14 +02:00
double clamp_value = CLAMP ( new_value , get_min ( ) , get_max ( ) ) ;
if ( new_value ! = clamp_value ) {
set_value ( clamp_value ) ;
2020-09-07 16:24:35 +02:00
}
2021-10-26 22:38:45 +02:00
value_input_dirty = true ;
set_process_internal ( true ) ;
2020-09-07 16:24:35 +02:00
} break ;
2023-01-12 14:13:49 +01:00
case Key : : ESCAPE : {
value_input_closed_frame = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ;
if ( value_input_popup ) {
value_input_popup - > hide ( ) ;
}
} break ;
2021-08-13 23:31:57 +02:00
default :
break ;
2020-09-07 16:24:35 +02:00
}
}
}
2021-07-16 23:36:05 +02:00
void EditorSpinSlider : : _update_value_input_stylebox ( ) {
if ( ! value_input ) {
return ;
}
2021-08-13 23:07:59 +02:00
2021-07-16 23:36:05 +02:00
// Add a left margin to the stylebox to make the number align with the Label
// when it's edited. The LineEdit "focus" stylebox uses the "normal" stylebox's
// default margins.
2021-08-13 23:07:59 +02:00
Ref < StyleBox > stylebox = get_theme_stylebox ( SNAME ( " normal " ) , SNAME ( " LineEdit " ) ) - > duplicate ( ) ;
2021-07-16 23:36:05 +02:00
// EditorSpinSliders with a label have more space on the left, so add an
// higher margin to match the location where the text begins.
// The margin values below were determined by empirical testing.
if ( is_layout_rtl ( ) ) {
2023-01-19 17:14:09 +01:00
stylebox - > set_content_margin ( SIDE_LEFT , 0 ) ;
stylebox - > set_content_margin ( SIDE_RIGHT , ( ! get_label ( ) . is_empty ( ) ? 23 : 16 ) * EDSCALE ) ;
2021-07-16 23:36:05 +02:00
} else {
2023-01-19 17:14:09 +01:00
stylebox - > set_content_margin ( SIDE_LEFT , ( ! get_label ( ) . is_empty ( ) ? 23 : 16 ) * EDSCALE ) ;
stylebox - > set_content_margin ( SIDE_RIGHT , 0 ) ;
2021-07-16 23:36:05 +02:00
}
2021-08-13 23:07:59 +02:00
2022-02-08 10:14:58 +01:00
value_input - > add_theme_style_override ( " normal " , stylebox ) ;
2021-07-16 23:36:05 +02:00
}
2018-05-15 22:12:35 +02:00
2021-08-13 23:07:59 +02:00
void EditorSpinSlider : : _draw_spin_slider ( ) {
updown_offset = - 1 ;
2020-02-15 02:18:24 +01:00
2021-08-13 23:07:59 +02:00
RID ci = get_canvas_item ( ) ;
bool rtl = is_layout_rtl ( ) ;
Vector2 size = get_size ( ) ;
2018-05-15 22:12:35 +02:00
2021-08-16 04:42:24 +02:00
Ref < StyleBox > sb = get_theme_stylebox ( is_read_only ( ) ? SNAME ( " read_only " ) : SNAME ( " normal " ) , SNAME ( " LineEdit " ) ) ;
2021-08-13 23:07:59 +02:00
if ( ! flat ) {
draw_style_box ( sb , Rect2 ( Vector2 ( ) , size ) ) ;
}
Ref < Font > font = get_theme_font ( SNAME ( " font " ) , SNAME ( " LineEdit " ) ) ;
int font_size = get_theme_font_size ( SNAME ( " font_size " ) , SNAME ( " LineEdit " ) ) ;
int sep_base = 4 * EDSCALE ;
int sep = sep_base + sb - > get_offset ( ) . x ; //make it have the same margin on both sides, looks better
2021-06-30 19:11:01 +02:00
2022-05-09 11:47:10 +02:00
int label_width = font - > get_string_size ( label , HORIZONTAL_ALIGNMENT_LEFT , - 1 , font_size ) . width ;
2021-08-13 23:07:59 +02:00
int number_width = size . width - sb - > get_minimum_size ( ) . width - label_width - sep ;
2018-07-19 00:37:17 +02:00
2021-08-16 04:42:24 +02:00
Ref < Texture2D > updown = get_theme_icon ( is_read_only ( ) ? SNAME ( " updown_disabled " ) : SNAME ( " updown " ) , SNAME ( " SpinBox " ) ) ;
2018-05-15 22:12:35 +02:00
2021-08-13 23:07:59 +02:00
String numstr = get_text_value ( ) ;
2018-05-15 22:12:35 +02:00
2021-08-13 23:07:59 +02:00
int vofs = ( size . height - font - > get_height ( font_size ) ) / 2 + font - > get_ascent ( font_size ) ;
2018-05-15 22:12:35 +02:00
2021-08-16 04:42:24 +02:00
Color fc = get_theme_color ( is_read_only ( ) ? SNAME ( " font_uneditable_color " ) : SNAME ( " font_color " ) , SNAME ( " LineEdit " ) ) ;
2022-03-04 01:19:00 +01:00
Color lc = get_theme_color ( is_read_only ( ) ? SNAME ( " read_only_label_color " ) : SNAME ( " label_color " ) ) ;
2018-05-15 22:12:35 +02:00
2021-12-09 10:42:46 +01:00
if ( flat & & ! label . is_empty ( ) ) {
2022-04-01 01:55:51 +02:00
Ref < StyleBox > label_bg = get_theme_stylebox ( SNAME ( " label_bg " ) , SNAME ( " EditorSpinSlider " ) ) ;
2021-08-13 23:07:59 +02:00
if ( rtl ) {
2022-04-01 01:55:51 +02:00
draw_style_box ( label_bg , Rect2 ( Vector2 ( size . width - ( sb - > get_offset ( ) . x * 2 + label_width ) , 0 ) , Vector2 ( sb - > get_offset ( ) . x * 2 + label_width , size . height ) ) ) ;
2018-07-19 03:27:39 +02:00
} else {
2022-04-01 01:55:51 +02:00
draw_style_box ( label_bg , Rect2 ( Vector2 ( ) , Vector2 ( sb - > get_offset ( ) . x * 2 + label_width , size . height ) ) ) ;
2018-07-19 03:27:39 +02:00
}
2021-08-13 23:07:59 +02:00
}
2018-07-19 03:27:39 +02:00
2021-08-13 23:07:59 +02:00
if ( has_focus ( ) ) {
Ref < StyleBox > focus = get_theme_stylebox ( SNAME ( " focus " ) , SNAME ( " LineEdit " ) ) ;
draw_style_box ( focus , Rect2 ( Vector2 ( ) , size ) ) ;
}
if ( rtl ) {
2021-11-25 03:58:47 +01:00
draw_string ( font , Vector2 ( Math : : round ( size . width - sb - > get_offset ( ) . x - label_width ) , vofs ) , label , HORIZONTAL_ALIGNMENT_RIGHT , - 1 , font_size , lc * Color ( 1 , 1 , 1 , 0.5 ) ) ;
2021-08-13 23:07:59 +02:00
} else {
2021-11-25 03:58:47 +01:00
draw_string ( font , Vector2 ( Math : : round ( sb - > get_offset ( ) . x ) , vofs ) , label , HORIZONTAL_ALIGNMENT_LEFT , - 1 , font_size , lc * Color ( 1 , 1 , 1 , 0.5 ) ) ;
2021-08-13 23:07:59 +02:00
}
2018-07-19 03:27:39 +02:00
2021-08-13 23:07:59 +02:00
int suffix_start = numstr . length ( ) ;
RID num_rid = TS - > create_shaped_text ( ) ;
2022-05-09 11:47:10 +02:00
TS - > shaped_text_add_string ( num_rid , numstr + U " \u2009 " + suffix , font - > get_rids ( ) , font_size , font - > get_opentype_features ( ) ) ;
2021-08-13 23:07:59 +02:00
float text_start = rtl ? Math : : round ( sb - > get_offset ( ) . x ) : Math : : round ( sb - > get_offset ( ) . x + label_width + sep ) ;
Vector2 text_ofs = rtl ? Vector2 ( text_start + ( number_width - TS - > shaped_text_get_width ( num_rid ) ) , vofs ) : Vector2 ( text_start , vofs ) ;
2021-08-27 23:19:51 +02:00
int v_size = TS - > shaped_text_get_glyph_count ( num_rid ) ;
const Glyph * glyphs = TS - > shaped_text_get_glyphs ( num_rid ) ;
2021-08-13 23:07:59 +02:00
for ( int i = 0 ; i < v_size ; i + + ) {
for ( int j = 0 ; j < glyphs [ i ] . repeat ; j + + ) {
if ( text_ofs . x > = text_start & & ( text_ofs . x + glyphs [ i ] . advance ) < = ( text_start + number_width ) ) {
Color color = fc ;
if ( glyphs [ i ] . start > = suffix_start ) {
color . a * = 0.4 ;
}
if ( glyphs [ i ] . font_rid ! = RID ( ) ) {
TS - > font_draw_glyph ( glyphs [ i ] . font_rid , ci , glyphs [ i ] . font_size , text_ofs + Vector2 ( glyphs [ i ] . x_off , glyphs [ i ] . y_off ) , glyphs [ i ] . index , color ) ;
} else if ( ( glyphs [ i ] . flags & TextServer : : GRAPHEME_IS_VIRTUAL ) ! = TextServer : : GRAPHEME_IS_VIRTUAL ) {
TS - > draw_hex_code_box ( ci , glyphs [ i ] . font_size , text_ofs + Vector2 ( glyphs [ i ] . x_off , glyphs [ i ] . y_off ) , glyphs [ i ] . index , color ) ;
}
}
text_ofs . x + = glyphs [ i ] . advance ;
2018-07-19 03:27:39 +02:00
}
2021-08-13 23:07:59 +02:00
}
2022-02-13 13:41:29 +01:00
TS - > free_rid ( num_rid ) ;
2018-07-19 03:27:39 +02:00
2022-12-08 13:38:01 +01:00
if ( ! hide_slider ) {
if ( get_step ( ) = = 1 ) {
Ref < Texture2D > updown2 = get_theme_icon ( is_read_only ( ) ? SNAME ( " updown_disabled " ) : SNAME ( " updown " ) , SNAME ( " SpinBox " ) ) ;
int updown_vofs = ( size . height - updown2 - > get_height ( ) ) / 2 ;
if ( rtl ) {
updown_offset = sb - > get_margin ( SIDE_LEFT ) ;
2021-06-30 19:11:01 +02:00
} else {
2022-12-08 13:38:01 +01:00
updown_offset = size . width - sb - > get_margin ( SIDE_RIGHT ) - updown2 - > get_width ( ) ;
}
Color c ( 1 , 1 , 1 ) ;
if ( hover_updown ) {
c * = Color ( 1.2 , 1.2 , 1.2 ) ;
}
draw_texture ( updown2 , Vector2 ( updown_offset , updown_vofs ) , c ) ;
if ( grabber - > is_visible ( ) ) {
2018-05-15 22:12:35 +02:00
grabber - > hide ( ) ;
}
2022-12-08 13:38:01 +01:00
} else {
const int grabber_w = 4 * EDSCALE ;
const int width = size . width - sb - > get_minimum_size ( ) . width - grabber_w ;
const int ofs = sb - > get_offset ( ) . x ;
const int svofs = ( size . height + vofs ) / 2 - 1 ;
Color c = fc ;
// Draw the horizontal slider's background.
c . a = 0.2 ;
draw_rect ( Rect2 ( ofs , svofs + 1 , width , 2 * EDSCALE ) , c ) ;
// Draw the horizontal slider's filled part on the left.
const int gofs = get_as_ratio ( ) * width ;
c . a = 0.45 ;
draw_rect ( Rect2 ( ofs , svofs + 1 , gofs , 2 * EDSCALE ) , c ) ;
// Draw the horizontal slider's grabber.
c . a = 0.9 ;
const Rect2 grabber_rect = Rect2 ( ofs + gofs , svofs , grabber_w , 4 * EDSCALE ) ;
draw_rect ( grabber_rect , c ) ;
grabbing_spinner_mouse_pos = get_global_position ( ) + grabber_rect . get_center ( ) ;
bool display_grabber = ( grabbing_grabber | | mouse_over_spin | | mouse_over_grabber ) & & ! grabbing_spinner & & ! ( value_input_popup & & value_input_popup - > is_visible ( ) ) ;
if ( grabber - > is_visible ( ) ! = display_grabber ) {
if ( display_grabber ) {
grabber - > show ( ) ;
} else {
grabber - > hide ( ) ;
}
2021-08-13 23:07:59 +02:00
}
2018-05-15 22:12:35 +02:00
2022-12-08 13:38:01 +01:00
if ( display_grabber ) {
Ref < Texture2D > grabber_tex ;
if ( mouse_over_grabber ) {
grabber_tex = get_theme_icon ( SNAME ( " grabber_highlight " ) , SNAME ( " HSlider " ) ) ;
} else {
grabber_tex = get_theme_icon ( SNAME ( " grabber " ) , SNAME ( " HSlider " ) ) ;
}
2019-08-13 12:11:24 +02:00
2022-12-08 13:38:01 +01:00
if ( grabber - > get_texture ( ) ! = grabber_tex ) {
grabber - > set_texture ( grabber_tex ) ;
}
2019-08-13 12:11:24 +02:00
2022-12-08 13:38:01 +01:00
Vector2 scale = get_global_transform_with_canvas ( ) . get_scale ( ) ;
grabber - > set_scale ( scale ) ;
grabber - > reset_size ( ) ;
grabber - > set_position ( get_global_position ( ) + ( grabber_rect . get_center ( ) - grabber - > get_size ( ) * 0.5 ) * scale ) ;
if ( mousewheel_over_grabber ) {
Input : : get_singleton ( ) - > warp_mouse ( grabber - > get_position ( ) + grabber_rect . size ) ;
}
2021-08-13 23:07:59 +02:00
2022-12-08 13:38:01 +01:00
grabber_range = width ;
}
2018-05-15 22:12:35 +02:00
}
}
2021-08-13 23:07:59 +02:00
}
2018-05-15 22:12:35 +02:00
2021-08-13 23:07:59 +02:00
void EditorSpinSlider : : _notification ( int p_what ) {
switch ( p_what ) {
2021-07-21 00:45:29 +02:00
case NOTIFICATION_ENTER_TREE : {
grabbing_spinner_speed = EditorSettings : : get_singleton ( ) - > get ( " interface/inspector/float_drag_speed " ) ;
_update_value_input_stylebox ( ) ;
} break ;
2022-02-16 00:52:32 +01:00
case NOTIFICATION_THEME_CHANGED : {
2021-08-13 23:07:59 +02:00
_update_value_input_stylebox ( ) ;
2022-02-16 00:52:32 +01:00
} break ;
2021-08-13 23:07:59 +02:00
2022-02-16 00:52:32 +01:00
case NOTIFICATION_INTERNAL_PROCESS : {
2021-10-26 22:38:45 +02:00
if ( value_input_dirty ) {
value_input_dirty = false ;
value_input - > set_text ( get_text_value ( ) ) ;
}
set_process_internal ( false ) ;
2022-02-16 00:52:32 +01:00
} break ;
2021-10-26 22:38:45 +02:00
2022-02-16 00:52:32 +01:00
case NOTIFICATION_DRAW : {
2021-08-13 23:07:59 +02:00
_draw_spin_slider ( ) ;
2022-02-16 00:52:32 +01:00
} break ;
2021-08-13 23:07:59 +02:00
case NOTIFICATION_WM_WINDOW_FOCUS_IN :
case NOTIFICATION_WM_WINDOW_FOCUS_OUT :
2022-03-16 01:18:01 +01:00
case NOTIFICATION_WM_CLOSE_REQUEST :
2022-02-16 00:52:32 +01:00
case NOTIFICATION_EXIT_TREE : {
2021-08-13 23:07:59 +02:00
if ( grabbing_spinner ) {
grabber - > hide ( ) ;
Input : : get_singleton ( ) - > set_mouse_mode ( Input : : MOUSE_MODE_VISIBLE ) ;
2022-06-07 20:05:52 +02:00
Input : : get_singleton ( ) - > warp_mouse ( grabbing_spinner_mouse_pos ) ;
2021-08-13 23:07:59 +02:00
grabbing_spinner = false ;
grabbing_spinner_attempt = false ;
}
2022-02-16 00:52:32 +01:00
} break ;
2021-08-13 23:07:59 +02:00
2022-02-16 00:52:32 +01:00
case NOTIFICATION_MOUSE_ENTER : {
2021-08-13 23:07:59 +02:00
mouse_over_spin = true ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2022-02-16 00:52:32 +01:00
} break ;
case NOTIFICATION_MOUSE_EXIT : {
2021-08-13 23:07:59 +02:00
mouse_over_spin = false ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2022-02-16 00:52:32 +01:00
} break ;
case NOTIFICATION_FOCUS_ENTER : {
2023-01-12 14:13:49 +01:00
if ( ( Input : : get_singleton ( ) - > is_action_pressed ( " ui_focus_next " ) | | Input : : get_singleton ( ) - > is_action_pressed ( " ui_focus_prev " ) ) & & value_input_closed_frame ! = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ) {
2021-08-13 23:07:59 +02:00
_focus_entered ( ) ;
}
2023-01-12 14:13:49 +01:00
value_input_closed_frame = 0 ;
2022-02-16 00:52:32 +01:00
} break ;
2018-05-21 21:36:43 +02:00
}
2018-05-15 22:12:35 +02:00
}
2021-07-18 01:04:27 +02:00
LineEdit * EditorSpinSlider : : get_line_edit ( ) {
_ensure_input_popup ( ) ;
return value_input ;
}
2018-05-15 22:12:35 +02:00
Size2 EditorSpinSlider : : get_minimum_size ( ) const {
2021-07-17 23:22:52 +02:00
Ref < StyleBox > sb = get_theme_stylebox ( SNAME ( " normal " ) , SNAME ( " LineEdit " ) ) ;
Ref < Font > font = get_theme_font ( SNAME ( " font " ) , SNAME ( " LineEdit " ) ) ;
int font_size = get_theme_font_size ( SNAME ( " font_size " ) , SNAME ( " LineEdit " ) ) ;
2018-05-15 22:12:35 +02:00
Size2 ms = sb - > get_minimum_size ( ) ;
2020-09-03 13:22:16 +02:00
ms . height + = font - > get_height ( font_size ) ;
2018-05-15 22:12:35 +02:00
return ms ;
}
void EditorSpinSlider : : set_hide_slider ( bool p_hide ) {
hide_slider = p_hide ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
bool EditorSpinSlider : : is_hiding_slider ( ) const {
return hide_slider ;
}
void EditorSpinSlider : : set_label ( const String & p_label ) {
label = p_label ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
String EditorSpinSlider : : get_label ( ) const {
return label ;
}
Fix editor suffixes and degrees conversion
* Functions to convert to/from degrees are all gone. Conversion is done by the editor.
* Use PROPERTY_HINT_ANGLE instead of PROPERTY_HINT_RANGE to edit radian angles in degrees.
* Added possibility to add suffixes to range properties, use "min,max[,step][,suffix:<something>]" example "0,100,1,suffix:m"
* In general, can add suffixes for EditorSpinSlider
Not covered by this PR, will have to be addressed by future ones:
* Ability to switch radians/degrees in the inspector for angle properties (if actually wanted).
* Animations previously made will most likely break, need to add a way to make old ones compatible.
* Only added a "px" suffix to 2D position and a "m" one to 3D position, someone needs to go through the rest of the engine and add all remaining suffixes.
* Likely also need to track down usage of EditorSpinSlider outside properties to add suffixes to it too.
2021-06-29 21:42:12 +02:00
void EditorSpinSlider : : set_suffix ( const String & p_suffix ) {
suffix = p_suffix ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
Fix editor suffixes and degrees conversion
* Functions to convert to/from degrees are all gone. Conversion is done by the editor.
* Use PROPERTY_HINT_ANGLE instead of PROPERTY_HINT_RANGE to edit radian angles in degrees.
* Added possibility to add suffixes to range properties, use "min,max[,step][,suffix:<something>]" example "0,100,1,suffix:m"
* In general, can add suffixes for EditorSpinSlider
Not covered by this PR, will have to be addressed by future ones:
* Ability to switch radians/degrees in the inspector for angle properties (if actually wanted).
* Animations previously made will most likely break, need to add a way to make old ones compatible.
* Only added a "px" suffix to 2D position and a "m" one to 3D position, someone needs to go through the rest of the engine and add all remaining suffixes.
* Likely also need to track down usage of EditorSpinSlider outside properties to add suffixes to it too.
2021-06-29 21:42:12 +02:00
}
String EditorSpinSlider : : get_suffix ( ) const {
return suffix ;
}
2018-08-08 22:34:24 +02:00
void EditorSpinSlider : : _evaluate_input_text ( ) {
Ref < Expression > expr ;
2021-06-18 00:03:09 +02:00
expr . instantiate ( ) ;
2023-08-16 20:18:22 +02:00
// Convert commas ',' to dots '.' for French/German etc. keyboard layouts.
String text = value_input - > get_text ( ) . replace ( " , " , " . " ) ;
text = text . replace ( " ; " , " , " ) ;
text = TS - > parse_number ( text ) ;
2018-08-08 22:34:24 +02:00
Error err = expr - > parse ( text ) ;
if ( err ! = OK ) {
2023-08-16 20:18:22 +02:00
// If the expression failed try without converting commas to dots - they might have been for parameter separation.
text = value_input - > get_text ( ) ;
text = TS - > parse_number ( text ) ;
err = expr - > parse ( text ) ;
if ( err ! = OK ) {
return ;
}
2018-08-08 22:34:24 +02:00
}
2022-06-27 22:10:04 +02:00
Variant v = expr - > execute ( Array ( ) , nullptr , false , true ) ;
2020-05-14 16:41:43 +02:00
if ( v . get_type ( ) = = Variant : : NIL ) {
2018-08-08 22:34:24 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-08-08 22:34:24 +02:00
set_value ( v ) ;
}
2021-06-16 18:43:34 +02:00
//text_submitted signal
void EditorSpinSlider : : _value_input_submitted ( const String & p_text ) {
2023-01-12 14:13:49 +01:00
value_input_closed_frame = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ;
2021-07-16 23:36:05 +02:00
if ( value_input_popup ) {
value_input_popup - > hide ( ) ;
}
2018-05-21 21:36:43 +02:00
}
//modal_closed signal
void EditorSpinSlider : : _value_input_closed ( ) {
2018-08-08 22:34:24 +02:00
_evaluate_input_text ( ) ;
2023-01-12 14:13:49 +01:00
value_input_closed_frame = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ;
2018-05-21 21:36:43 +02:00
}
//focus_exited signal
void EditorSpinSlider : : _value_focus_exited ( ) {
2019-08-12 12:39:27 +02:00
// discontinue because the focus_exit was caused by right-click context menu
2021-07-16 23:36:05 +02:00
if ( value_input - > is_menu_visible ( ) ) {
2019-08-12 12:39:27 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2019-08-12 12:39:27 +02:00
2023-04-16 11:08:53 +02:00
if ( is_read_only ( ) ) {
// Spin slider has become read only while it was being edited.
return ;
}
2018-08-08 22:34:24 +02:00
_evaluate_input_text ( ) ;
2023-04-16 11:08:53 +02:00
// focus is not on the same element after the value_input was exited
2018-05-21 21:36:43 +02:00
// -> focus is on next element
// -> TAB was pressed
// -> modal_close was not called
// -> need to close/hide manually
2023-01-12 14:13:49 +01:00
if ( value_input_closed_frame ! = Engine : : get_singleton ( ) - > get_frames_drawn ( ) ) {
2021-07-16 23:36:05 +02:00
if ( value_input_popup ) {
value_input_popup - > hide ( ) ;
}
2018-05-21 21:36:43 +02:00
//tab was pressed
} else {
//enter, click, esc
2022-11-26 22:57:31 +01:00
grab_focus ( ) ;
2018-05-21 21:36:43 +02:00
}
2022-12-08 13:38:01 +01:00
emit_signal ( " value_focus_exited " ) ;
2018-05-21 21:36:43 +02:00
}
2018-05-15 22:12:35 +02:00
void EditorSpinSlider : : _grabber_mouse_entered ( ) {
mouse_over_grabber = true ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
void EditorSpinSlider : : _grabber_mouse_exited ( ) {
mouse_over_grabber = false ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-05-15 22:12:35 +02:00
}
2018-06-07 17:46:14 +02:00
void EditorSpinSlider : : set_read_only ( bool p_enable ) {
read_only = p_enable ;
2023-04-16 11:08:53 +02:00
if ( read_only & & value_input ) {
value_input - > release_focus ( ) ;
}
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-06-07 17:46:14 +02:00
}
bool EditorSpinSlider : : is_read_only ( ) const {
return read_only ;
}
2018-07-19 00:37:17 +02:00
void EditorSpinSlider : : set_flat ( bool p_enable ) {
flat = p_enable ;
2022-08-13 23:21:24 +02:00
queue_redraw ( ) ;
2018-07-19 00:37:17 +02:00
}
bool EditorSpinSlider : : is_flat ( ) const {
return flat ;
}
2022-08-21 09:03:38 +02:00
bool EditorSpinSlider : : is_grabbing ( ) const {
return grabbing_grabber | | grabbing_spinner ;
}
2018-05-21 21:36:43 +02:00
void EditorSpinSlider : : _focus_entered ( ) {
2021-07-16 23:36:05 +02:00
_ensure_input_popup ( ) ;
2018-05-21 21:36:43 +02:00
value_input - > set_text ( get_text_value ( ) ) ;
2022-10-14 17:51:57 +02:00
value_input_popup - > set_size ( get_size ( ) ) ;
2018-05-21 21:36:43 +02:00
value_input - > set_focus_next ( find_next_valid_focus ( ) - > get_path ( ) ) ;
value_input - > set_focus_previous ( find_prev_valid_focus ( ) - > get_path ( ) ) ;
2023-12-18 15:46:56 +01:00
callable_mp ( ( CanvasItem * ) value_input_popup , & CanvasItem : : show ) . call_deferred ( ) ;
callable_mp ( ( Control * ) value_input , & Control : : grab_focus ) . call_deferred ( ) ;
callable_mp ( value_input , & LineEdit : : select_all ) . call_deferred ( ) ;
2022-12-08 13:38:01 +01:00
emit_signal ( " value_focus_entered " ) ;
2018-05-21 21:36:43 +02:00
}
2018-05-15 22:12:35 +02:00
void EditorSpinSlider : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " set_label " , " label " ) , & EditorSpinSlider : : set_label ) ;
ClassDB : : bind_method ( D_METHOD ( " get_label " ) , & EditorSpinSlider : : get_label ) ;
Fix editor suffixes and degrees conversion
* Functions to convert to/from degrees are all gone. Conversion is done by the editor.
* Use PROPERTY_HINT_ANGLE instead of PROPERTY_HINT_RANGE to edit radian angles in degrees.
* Added possibility to add suffixes to range properties, use "min,max[,step][,suffix:<something>]" example "0,100,1,suffix:m"
* In general, can add suffixes for EditorSpinSlider
Not covered by this PR, will have to be addressed by future ones:
* Ability to switch radians/degrees in the inspector for angle properties (if actually wanted).
* Animations previously made will most likely break, need to add a way to make old ones compatible.
* Only added a "px" suffix to 2D position and a "m" one to 3D position, someone needs to go through the rest of the engine and add all remaining suffixes.
* Likely also need to track down usage of EditorSpinSlider outside properties to add suffixes to it too.
2021-06-29 21:42:12 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_suffix " , " suffix " ) , & EditorSpinSlider : : set_suffix ) ;
ClassDB : : bind_method ( D_METHOD ( " get_suffix " ) , & EditorSpinSlider : : get_suffix ) ;
2018-06-07 17:46:14 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_read_only " , " read_only " ) , & EditorSpinSlider : : set_read_only ) ;
ClassDB : : bind_method ( D_METHOD ( " is_read_only " ) , & EditorSpinSlider : : is_read_only ) ;
2018-07-19 00:37:17 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_flat " , " flat " ) , & EditorSpinSlider : : set_flat ) ;
ClassDB : : bind_method ( D_METHOD ( " is_flat " ) , & EditorSpinSlider : : is_flat ) ;
2022-04-09 23:31:22 +02:00
ClassDB : : bind_method ( D_METHOD ( " set_hide_slider " , " hide_slider " ) , & EditorSpinSlider : : set_hide_slider ) ;
ClassDB : : bind_method ( D_METHOD ( " is_hiding_slider " ) , & EditorSpinSlider : : is_hiding_slider ) ;
2018-05-15 22:12:35 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : STRING , " label " ) , " set_label " , " get_label " ) ;
Fix editor suffixes and degrees conversion
* Functions to convert to/from degrees are all gone. Conversion is done by the editor.
* Use PROPERTY_HINT_ANGLE instead of PROPERTY_HINT_RANGE to edit radian angles in degrees.
* Added possibility to add suffixes to range properties, use "min,max[,step][,suffix:<something>]" example "0,100,1,suffix:m"
* In general, can add suffixes for EditorSpinSlider
Not covered by this PR, will have to be addressed by future ones:
* Ability to switch radians/degrees in the inspector for angle properties (if actually wanted).
* Animations previously made will most likely break, need to add a way to make old ones compatible.
* Only added a "px" suffix to 2D position and a "m" one to 3D position, someone needs to go through the rest of the engine and add all remaining suffixes.
* Likely also need to track down usage of EditorSpinSlider outside properties to add suffixes to it too.
2021-06-29 21:42:12 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : STRING , " suffix " ) , " set_suffix " , " get_suffix " ) ;
2018-06-07 17:46:14 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " read_only " ) , " set_read_only " , " is_read_only " ) ;
2018-07-19 00:37:17 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " flat " ) , " set_flat " , " is_flat " ) ;
2022-04-09 23:31:22 +02:00
ADD_PROPERTY ( PropertyInfo ( Variant : : BOOL , " hide_slider " ) , " set_hide_slider " , " is_hiding_slider " ) ;
2022-12-08 13:38:01 +01:00
ADD_SIGNAL ( MethodInfo ( " grabbed " ) ) ;
ADD_SIGNAL ( MethodInfo ( " ungrabbed " ) ) ;
ADD_SIGNAL ( MethodInfo ( " value_focus_entered " ) ) ;
ADD_SIGNAL ( MethodInfo ( " value_focus_exited " ) ) ;
2018-05-15 22:12:35 +02:00
}
2021-07-16 23:36:05 +02:00
void EditorSpinSlider : : _ensure_input_popup ( ) {
if ( value_input_popup ) {
return ;
}
2021-08-13 23:07:59 +02:00
2022-10-14 17:51:57 +02:00
value_input_popup = memnew ( Control ) ;
2021-07-16 23:36:05 +02:00
add_child ( value_input_popup ) ;
2021-08-13 23:07:59 +02:00
2021-07-16 23:36:05 +02:00
value_input = memnew ( LineEdit ) ;
2023-01-12 14:13:49 +01:00
value_input - > set_focus_mode ( FOCUS_CLICK ) ;
2021-07-16 23:36:05 +02:00
value_input_popup - > add_child ( value_input ) ;
2022-03-19 01:02:57 +01:00
value_input - > set_anchors_and_offsets_preset ( PRESET_FULL_RECT ) ;
2022-10-14 17:51:57 +02:00
value_input_popup - > connect ( " hidden " , callable_mp ( this , & EditorSpinSlider : : _value_input_closed ) ) ;
2021-07-16 23:36:05 +02:00
value_input - > connect ( " text_submitted " , callable_mp ( this , & EditorSpinSlider : : _value_input_submitted ) ) ;
value_input - > connect ( " focus_exited " , callable_mp ( this , & EditorSpinSlider : : _value_focus_exited ) ) ;
2020-09-07 16:24:35 +02:00
value_input - > connect ( " gui_input " , callable_mp ( this , & EditorSpinSlider : : _value_input_gui_input ) ) ;
2021-08-13 23:07:59 +02:00
2021-07-16 23:36:05 +02:00
if ( is_inside_tree ( ) ) {
_update_value_input_stylebox ( ) ;
}
}
2020-09-07 16:24:35 +02:00
2018-05-15 22:12:35 +02:00
EditorSpinSlider : : EditorSpinSlider ( ) {
set_focus_mode ( FOCUS_ALL ) ;
grabber = memnew ( TextureRect ) ;
add_child ( grabber ) ;
grabber - > hide ( ) ;
2020-10-01 09:17:33 +02:00
grabber - > set_as_top_level ( true ) ;
2018-05-15 22:12:35 +02:00
grabber - > set_mouse_filter ( MOUSE_FILTER_STOP ) ;
2020-02-21 18:28:45 +01:00
grabber - > connect ( " mouse_entered " , callable_mp ( this , & EditorSpinSlider : : _grabber_mouse_entered ) ) ;
grabber - > connect ( " mouse_exited " , callable_mp ( this , & EditorSpinSlider : : _grabber_mouse_exited ) ) ;
grabber - > connect ( " gui_input " , callable_mp ( this , & EditorSpinSlider : : _grabber_gui_input ) ) ;
2018-05-15 22:12:35 +02:00
}