└── addons
└── tattomoosa.spinner
├── plugin.gd.uid
├── spinner.gd.uid
├── plugin.cfg
├── icons
├── StatusWarning.svg
├── StatusSuccess.svg
├── StatusIndicator.svg
├── StatusError.svg
├── Spinner.svg
├── StatusError.svg.import
├── StatusSuccess.svg.import
├── StatusWarning.svg.import
├── Spinner.svg.import
└── StatusIndicator.svg.import
├── LICENSE.txt
├── plugin.gd
├── README.md
└── spinner.gd
/addons/tattomoosa.spinner/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b7ndv0sff5jhc
2 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/spinner.gd.uid:
--------------------------------------------------------------------------------
1 | uid://daxfjew482siq
2 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="Spinner"
4 | description=""
5 | author="Tattomoosa"
6 | version=""
7 | script="plugin.gd"
8 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusWarning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusSuccess.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusIndicator.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusError.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/Spinner.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusError.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://dte4xq84xh78b"
6 | path="res://.godot/imported/StatusError.svg-25d7218f058f49a6ac2186222f24770e.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/tattomoosa.spinner/icons/StatusError.svg"
14 | dest_files=["res://.godot/imported/StatusError.svg-25d7218f058f49a6ac2186222f24770e.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=2.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusSuccess.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://tfqsc1vul272"
6 | path="res://.godot/imported/StatusSuccess.svg-975e5d7a7f1036671eac1e324d7b014b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/tattomoosa.spinner/icons/StatusSuccess.svg"
14 | dest_files=["res://.godot/imported/StatusSuccess.svg-975e5d7a7f1036671eac1e324d7b014b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=2.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusWarning.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://i6esq6rgrc00"
6 | path="res://.godot/imported/StatusWarning.svg-23fc42d8d419686429fb7f31874bda64.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/tattomoosa.spinner/icons/StatusWarning.svg"
14 | dest_files=["res://.godot/imported/StatusWarning.svg-23fc42d8d419686429fb7f31874bda64.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=2.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/Spinner.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://ox82pp12w4i4"
6 | path="res://.godot/imported/Spinner.svg-05c0090370c41ed3fd816447fc3fc3e0.ctex"
7 | metadata={
8 | "has_editor_variant": true,
9 | "vram_texture": false
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://addons/tattomoosa.spinner/icons/Spinner.svg"
15 | dest_files=["res://.godot/imported/Spinner.svg-05c0090370c41ed3fd816447fc3fc3e0.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=0
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=false
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=1
36 | svg/scale=1.0
37 | editor/scale_with_editor_scale=true
38 | editor/convert_colors_with_editor_theme=true
39 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/icons/StatusIndicator.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://rf86x1ivenf3"
6 | path="res://.godot/imported/StatusIndicator.svg-f77d47787ac5dc0cb7a001a7e59f5c6b.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/tattomoosa.spinner/icons/StatusIndicator.svg"
14 | dest_files=["res://.godot/imported/StatusIndicator.svg-f77d47787ac5dc0cb7a001a7e59f5c6b.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md).
2 | Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorPlugin
3 |
4 | var spinner_inspector := SpinnerEditorInspector.new()
5 |
6 | func _enter_tree():
7 | # Initialization of the plugin goes here.
8 | add_inspector_plugin(spinner_inspector)
9 | pass
10 |
11 |
12 | func _exit_tree():
13 | # Clean-up of the plugin goes here.
14 | remove_inspector_plugin(spinner_inspector)
15 |
16 |
17 |
18 |
19 |
20 | class SpinnerEditorInspector extends EditorInspectorPlugin:
21 | func _can_handle(object):
22 | return object is Spinner
23 |
24 | func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide):
25 | var spinner := object as Spinner
26 | if name == "nine_patch_stretch":
27 | return true
28 | if name == "color_use_editor_theme":
29 | var use_editor_theme_control := SpinnerColorsProperty.new()
30 | # add_property_editor(name, use_editor_theme_control)
31 | add_property_editor_for_multiple_properties(
32 | "Use Editor Theme",
33 | [
34 | "color_use_editor_theme",
35 | "color_ok",
36 | "color_warn",
37 | "color_error",
38 | "color_progress"
39 | ],
40 | use_editor_theme_control
41 | )
42 | return true
43 | if spinner.color_use_editor_theme:
44 | if name.begins_with("color_"):
45 | return true
46 |
47 | func _parse_group(object, group):
48 | var spinner := object as Spinner
49 |
50 | class SpinnerColorsProperty extends EditorProperty:
51 | var property_control := CheckBox.new()
52 |
53 | func _init():
54 | add_child(property_control)
55 | add_focusable(property_control)
56 |
57 | func _ready():
58 | property_control.button_pressed = get_edited_object().color_use_editor_theme
59 | property_control.toggled.connect(toggled)
60 |
61 | func toggled(value: bool):
62 | var spinner : Spinner = get_edited_object()
63 | spinner.color_use_editor_theme = value
64 | if value:
65 | spinner.color_success = property_control.get_theme_color("success_color", "Editor")
66 | spinner.color_warning = property_control.get_theme_color("warning_color", "Editor")
67 | spinner.color_error = property_control.get_theme_color("error_color", "Editor")
68 | spinner.color_progress = property_control.get_theme_color("accent_color", "Editor")
69 | emit_changed(get_edited_property(), spinner)
70 | emit_changed("color_success", spinner)
71 | emit_changed("color_warning", spinner)
72 | emit_changed("color_error", spinner)
73 | emit_changed("color_progress", spinner)
74 |
75 |
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 | Spinner
8 |
9 |
10 |
11 |
12 | Simple but configurable process status indicator node, for Godot
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |

23 |

24 |
25 |
26 |
27 |
28 |
29 | > Compatible with Godot 4.4 - use 4.3 branch for Godot 4.3 compatibility
30 |
31 | Adds new Range control Spinner, an all-purpose process indicator.
32 | Use to provide feedback on ongoing processes - threads, network requests, timers, etc.
33 |
34 | ## Features
35 |
36 | * 5 statuses
37 | * `EMPTY` for a process that hasn't started
38 | * `SPINNING` for indeterminate progress, such as a network request
39 | * `PROGRESSING` for determinate progress, such as a timer or download with known message body size
40 | * `SUCCESS` for a process that has completed successfully
41 | * `WARNING` for a process that has completed successfully with warnings
42 | * `ERROR` for a process that has errored out
43 | * Can use custom icons, with defaults taken from Godot's own icons
44 | * Option to use no icon and fills with status color instead
45 | * Configurable spinner/border width and speed
46 | * Configurable colors and option to use editor theme
47 |
48 | ## Installation
49 |
50 | Install via the AssetLib tab within Godot by searching for Spinner
51 |
52 | ## Usage
53 |
54 | Add the Spinner node to your scene. All options update a live preview in the editor.
55 | All options are documented in the Inspector panel. If anything isn't clear enough there,
56 | open an issue.
57 |
58 | ### Basic Usage
59 |
60 | Set `status` to the desired status indication setting. Spinner is a range node, and
61 | can have its `min_value`, `max_value` and `value` updated accordingly, but those values
62 | only affect its border fill when `status` is `Status.Progressing`.
63 |
64 | To set `value` to a new value and `status` to `Status.Progressing` at the same time,
65 | use `set_progressing(value)`. This function can be connected to a signal for easy updates,
66 | but be aware that when `value == max_value` Spinner will not automatically update to `Status.SUCCESS`,
67 | that has to be set separately.
68 |
69 | ### Borderless Icons
70 |
71 | If you set `icon_borderless`, you probably also want to set `icon_scale` to `1`.
72 |
73 | ### Example: Watching an HTTPRequest
74 |
75 | A very basic implementation.
76 |
77 | ``` go
78 | @onready var spinner : Spinner = $Spinner
79 | @onready var http_request : HTTPRequest = $HTTPRequest
80 |
81 | func _ready() -> void:
82 | var error := http_request.request("https://github.com/godotengine/godot/releases/download/4.3-stable/godot-4.3-stable.tar.xz")
83 | if error != OK:
84 | spinner.status = Spinner.Status.ERROR
85 | http_request.request_completed.connect(_on_request_completed)
86 |
87 | func _process(_delta: float) -> void:
88 | if http_request.get_body_size() > 0 and http_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
89 | spinner.max_value = http_request.get_body_size()
90 | spinner.set_progressing(http_request.get_downloaded_bytes())
91 |
92 | func _on_request_completed(
93 | result: int,
94 | response_code: int,
95 | headers: PackedStringArray,
96 | body: PackedByteArray
97 | ) -> void:
98 | spinner.status = Spinner.Status.SUCCESS
99 | ```
100 |
101 | ## The Future
102 |
103 | I primarily intend to use this to watch network requests, thread execution, etc.
104 | I am considering bundling extended classes which do this as-expected and have
105 | optional labels, but in its current state it is fairly simple to hook up to these
106 | manually. Let me know if there's interest in the following (or other) pre-built
107 | solutions:
108 |
109 | * HTTPRequestSpinner
110 | * ThreadSpinner
111 | * ValueSpinner
112 | * Connect to any `value_changed`-type Signal, auto-SUCCESS upon completion)
113 |
114 | The main thing holding me back from including this is it feels a bit like clutter
115 | and any plugin-side implementation should be really robust.
116 |
117 | Let me know if there's interest!
118 |
119 | ## My Other Godot Plugins
120 |
121 | * [VisionCone3D](https://github.com/Tattomoosa/VisionCone3D)
122 | * [NetworkTextureRect](https://github.com/Tattomoosa/NetworkTextureRect)
--------------------------------------------------------------------------------
/addons/tattomoosa.spinner/spinner.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("./icons/Spinner.svg")
3 | class_name Spinner
4 | extends Range
5 | ## Spinner which spins during loading and can also show operation results
6 |
7 | ## Status displayed by the Spinner
8 | enum Status {
9 | ## Only shows the background, useful if something hasn't started yet
10 | EMPTY,
11 | ## Spinning, for indeterminate progress
12 | SPINNING,
13 | ## Progressing, for determinate progress, set value elsewhere
14 | PROGRESSING,
15 | ## Success, for operations that completed successfully
16 | SUCCESS,
17 | ## Success, for operations that completed successfully but had warnings
18 | WARNING,
19 | ## Success, for operations that errored
20 | ERROR,
21 | }
22 |
23 | ## Current status of the spinner
24 | @export var status : Status = Status.SPINNING:
25 | set(new_value):
26 | status = new_value
27 | _update_status()
28 |
29 | @export_group("Display Options")
30 | ## Whether or not to use icons during SUCCESS, WARNING, ERROR status
31 |
32 | @export var use_icons := true:
33 | set(value):
34 | use_icons = value
35 | _update_status()
36 | ## Width of the spinning/static border
37 | @export_range(0.0, 0.3, 0.001) var border_width : float = 0.1:
38 | set(value):
39 | border_width = value
40 | _update_children_size()
41 |
42 | @export_subgroup("Spin", "spin_")
43 | ## Percent filled by the border when the spinner is spinning
44 | @export_range(0.0, 1.0) var spin_fill_percent := 0.2
45 | ## Speed of the spinning border during Status.SPINNING
46 | @export_range(0, 2) var spin_revolution_per_second := 0.5
47 | ## Sets whether to animate border spin on Status.SPINNING or not
48 | @export var spin_preview_in_editor := true
49 |
50 | @export_subgroup("Colors", "color_")
51 | ## Uses editor themes "color_success", "accent_color", "progress_color", "warning_color"
52 | @export var color_use_editor_theme := false
53 | ## Color when status is Status.SUCCESS
54 | @export var color_success := Color(0.45, 0.95, 0.5):
55 | set(value): color_success = value; _update_status()
56 | ## Color when status is Status.PROGRESSING or Status.SPINNING
57 | @export var color_progress := Color(0.44, 0.73, 0.98):
58 | set(value): color_progress = value; _update_status()
59 | ## Color when status is Status.ERROR
60 | @export var color_error := Color(1, 0.47, 0.42):
61 | set(value): color_error = value; _update_status()
62 | ## Color when status is Status.WARNING
63 | @export var color_warning := Color(1, 0.87, 0.4):
64 | set(value): color_warning = value; _update_status()
65 | ## Background color
66 | @export var color_background := Color(0.0, 0.0, 0.0, 0.4):
67 | set(value): color_background = value; _update_status()
68 |
69 | @export_subgroup("Icons", "icon_")
70 | ## Whether or not to draw the spinning indicator in non-PROGRESSING/SPINNING statuses
71 | ##
72 | ## Best paired with a circular icon at icon_scale = 1.0, so icons fill the whole control
73 | @export var icon_borderless := false:
74 | set(value):
75 | icon_borderless = value
76 | _update_status()
77 | ## Scale (relative to diameter) to render icon
78 | @export_range(0.0, 1.0, 0.01) var icon_scale := 0.7:
79 | set(value):
80 | icon_scale = value
81 | _icon.icon_scale = value
82 | # _icon.radius = (diameter / 2) * value
83 | _update_status()
84 | ## Icon to display when status is Status.SUCCESS
85 | @export var icon_success : Texture2D = preload("./icons/StatusSuccess.svg"):
86 | set(value):
87 | icon_success = value
88 | _update_status()
89 | ## Icon to display when status is Status.ERROR
90 | @export var icon_error : Texture2D = preload("./icons/StatusError.svg"):
91 | set(value):
92 | icon_error = value
93 | _update_status()
94 | ## Icon to display when status is Status.WARNING
95 | @export var icon_warning : Texture2D = preload("./icons/StatusWarning.svg"):
96 | set(value):
97 | icon_warning = value
98 | _update_status()
99 |
100 | var diameter : float:
101 | get():
102 | return min(size.x, size.y)
103 |
104 | var _radial_initial_angle : float = 0.0
105 |
106 | var _background := _SpinnerSolidCircle.new()
107 | var _icon := _SpinnerIcon.new()
108 | var _progress_border := _SpinnerProgressBorder.new()
109 |
110 | ## Sets value and status to Status.PROGRESSING at the same time.
111 | func set_progressing(to_value: float):
112 | if status != Status.PROGRESSING:
113 | status = Status.PROGRESSING
114 | value = to_value
115 |
116 | func _ready():
117 | clip_contents = true
118 | var external_children := get_children()
119 | for child in get_children(true):
120 | if child in external_children:
121 | remove_child(child)
122 | child.queue_free()
123 |
124 | # TODO not sure how to handle container sizing logic
125 | if custom_minimum_size == Vector2.ZERO:
126 | custom_minimum_size = Vector2(16, 16)
127 | if size.x < custom_minimum_size.x:
128 | size.x = custom_minimum_size.x
129 | if size.y < custom_minimum_size.y:
130 | size.y = custom_minimum_size.y
131 |
132 | add_child(_background, false, INTERNAL_MODE_FRONT)
133 | add_child(_icon, false, INTERNAL_MODE_FRONT)
134 | add_child(_progress_border, false, INTERNAL_MODE_FRONT)
135 | _update_children_size()
136 |
137 | value_changed.connect(queue_redraw.unbind(1))
138 | # resized.connect(_update_children_size)
139 | item_rect_changed.connect(_update_children_size)
140 | _update_status()
141 |
142 | func _update_status():
143 | _background.color = color_background
144 | match status:
145 | Status.SUCCESS:
146 | var color := _get_color("success_color", color_success)
147 | _icon.show()
148 | _icon.color = color
149 | _icon.icon = icon_success if use_icons else null
150 | if !icon_borderless:
151 | _progress_border.color = color
152 | _progress_border.show()
153 | else:
154 | _progress_border.hide()
155 | Status.ERROR:
156 | var color := _get_color("error_color", color_error)
157 | _icon.show()
158 | _icon.color = color_error
159 | _icon.icon = icon_error if use_icons else null
160 | _progress_border.color = color_error
161 | if !icon_borderless:
162 | _progress_border.color = color
163 | _progress_border.show()
164 | else:
165 | _progress_border.hide()
166 | Status.WARNING:
167 | _icon.show()
168 | var color := _get_color("warning_color", color_warning)
169 | _icon.color = color_warning
170 | _icon.icon = icon_warning if use_icons else null
171 | if !icon_borderless:
172 | _progress_border.color = color
173 | _progress_border.show()
174 | else:
175 | _progress_border.hide()
176 | Status.PROGRESSING:
177 | _progress_border.show()
178 | _radial_initial_angle = 0.0
179 | _icon.hide()
180 | _progress_border.color = _get_color("accent_color", color_progress)
181 | Status.SPINNING:
182 | _progress_border.show()
183 | _radial_initial_angle = 0.0
184 | _icon.hide()
185 | _progress_border.color = _get_color("accent_color", color_progress)
186 | Status.EMPTY:
187 | _icon.hide()
188 | _progress_border.hide()
189 | queue_redraw()
190 |
191 | func _update_children_size():
192 | var radius := diameter / 2
193 | _background.radius = radius
194 | _icon.icon_scale = icon_scale
195 | _icon.radius = radius
196 | _progress_border.radius = radius
197 | _progress_border.stroke_width = border_width * diameter
198 |
199 | func _process(delta: float):
200 | if status == Status.SPINNING and (!Engine.is_editor_hint() or spin_preview_in_editor):
201 | _radial_initial_angle += 360 * delta * spin_revolution_per_second
202 | if _radial_initial_angle >= 360:
203 | _radial_initial_angle -= 360
204 | else:
205 | _radial_initial_angle = 0
206 | _update_progress_border()
207 |
208 | func _update_progress_border():
209 | var v : float
210 | match status:
211 | Status.EMPTY:
212 | v = min_value
213 | Status.PROGRESSING:
214 | v = value
215 | Status.SPINNING:
216 | v = min_value + (max_value * spin_fill_percent)
217 | _:
218 | v = max_value
219 | _progress_border.start_angle = _radial_initial_angle
220 | _progress_border.end_angle = _radial_initial_angle + lerp(0, 360, float(v - min_value) / float(max_value - min_value))
221 |
222 | func _should_use_editor_theme():
223 | return Engine.is_editor_hint() and color_use_editor_theme and get_tree() and get_tree().edited_scene_root not in [self, owner]
224 |
225 | func _get_color(theme_color_name: String, fallback: Color) -> Color:
226 | var c := get_theme_color(theme_color_name)
227 | if _should_use_editor_theme():
228 | return get_theme_color(theme_color_name, "Editor")
229 | else:
230 | return fallback
231 |
232 | func _validate_property(property):
233 | if property.name in [
234 | "_radial_initial_angle",
235 | "page"
236 | ]:
237 | property.usage = PROPERTY_USAGE_NONE
238 |
239 | class _SpinnerElement extends Control:
240 | var color := Color.BLACK:
241 | set(value):
242 | color = value
243 | queue_redraw()
244 | var radius := 32.0:
245 | set(value):
246 | radius = value
247 | pivot_offset = Vector2.ZERO
248 | position = Vector2.ZERO
249 | size = Vector2(radius * 2, radius * 2)
250 | # pivot_offset = Vector2(radius, radius)
251 | queue_redraw()
252 | func _init():
253 | set_anchors_preset(PRESET_CENTER)
254 | mouse_filter = MOUSE_FILTER_IGNORE
255 |
256 | class _SpinnerSolidCircle extends _SpinnerElement:
257 | func _draw():
258 | if color == Color.TRANSPARENT:
259 | return
260 | draw_circle(Vector2(radius, radius), radius, color, true)
261 |
262 | # Draws solid circle if no icon
263 | class _SpinnerIcon extends _SpinnerSolidCircle:
264 | var icon : Texture2D
265 | var icon_scale : float
266 |
267 | func _draw():
268 | if color == Color.TRANSPARENT:
269 | return
270 | if !icon:
271 | super()
272 | return
273 | var diameter = radius * 2
274 | var scale := Vector2(diameter / icon.get_size().x, diameter / icon.get_size().y)
275 | draw_set_transform(Vector2(radius, radius), 0, scale * icon_scale)
276 | var pos := -icon.get_size() / 2.0
277 | draw_texture(icon, pos, color)
278 | draw_set_transform(Vector2.ZERO, 0, Vector2.ONE)
279 |
280 | class _SpinnerProgressBorder extends _SpinnerElement:
281 | var stroke_width : float:
282 | set(value):
283 | stroke_width = value
284 | queue_redraw()
285 | var start_angle : float:
286 | set(value):
287 | start_angle = value
288 | queue_redraw()
289 | var end_angle : float:
290 | set(value):
291 | end_angle = value
292 | queue_redraw()
293 |
294 | func _draw():
295 | draw_arc(
296 | Vector2(radius, radius),
297 | radius - stroke_width,
298 | deg_to_rad(start_angle - 90),
299 | deg_to_rad(end_angle - 90),
300 | radius * 2,
301 | color,
302 | stroke_width,
303 | true
304 | )
--------------------------------------------------------------------------------