└── 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 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | ) --------------------------------------------------------------------------------