diff --git a/assets/font/SourceCodePro-VariableFont_wght.ttf b/assets/font/SourceCodePro-VariableFont_wght.ttf deleted file mode 100644 index cf592055..00000000 Binary files a/assets/font/SourceCodePro-VariableFont_wght.ttf and /dev/null differ diff --git a/assets/font/SourceCodePro-VariableFont_wght.ttf.import b/assets/font/SourceCodePro-VariableFont_wght.ttf.import deleted file mode 100644 index ee568288..00000000 --- a/assets/font/SourceCodePro-VariableFont_wght.ttf.import +++ /dev/null @@ -1,36 +0,0 @@ -[remap] - -importer="font_data_dynamic" -type="FontFile" -uid="uid://bp0rn6rs2chy1" -path="res://.godot/imported/SourceCodePro-VariableFont_wght.ttf-660a8b39808daa6fa4e7d4d38c4324e2.fontdata" - -[deps] - -source_file="res://assets/font/SourceCodePro-VariableFont_wght.ttf" -dest_files=["res://.godot/imported/SourceCodePro-VariableFont_wght.ttf-660a8b39808daa6fa4e7d4d38c4324e2.fontdata"] - -[params] - -Rendering=null -antialiasing=1 -generate_mipmaps=false -disable_embedded_bitmaps=true -multichannel_signed_distance_field=false -msdf_pixel_range=8 -msdf_size=48 -allow_system_fallback=true -force_autohinter=false -modulate_color_glyphs=false -hinting=1 -subpixel_positioning=4 -keep_rounding_remainders=true -oversampling=0.0 -Fallbacks=null -fallbacks=[] -Compress=null -compress=true -preload=[] -language_support={} -script_support={} -opentype_features={} diff --git a/assets/icons/PlayAlt.svg b/assets/icons/PlayAlt.svg index c6634138..3986dba9 100644 --- a/assets/icons/PlayAlt.svg +++ b/assets/icons/PlayAlt.svg @@ -7,7 +7,7 @@ version="1.1" id="svg1" sodipodi:docname="PlayAlt.svg" - inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + inkscape:version="1.4.3 (0d15f75042, 2025-12-25)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -24,15 +24,15 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:zoom="48.833333" - inkscape:cx="12" + inkscape:cx="11.989761" inkscape:cy="12" inkscape:window-width="2560" - inkscape:window-height="1372" - inkscape:window-x="1920" - inkscape:window-y="29" + inkscape:window-height="1360" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg1" /> diff --git a/assets/icons/PlayPause.svg b/assets/icons/PlayPause.svg index c2e20c76..9a0695d6 100644 --- a/assets/icons/PlayPause.svg +++ b/assets/icons/PlayPause.svg @@ -1 +1,39 @@ - \ No newline at end of file + + + + + + diff --git a/assets/icons/RandomMode.svg b/assets/icons/RandomMode.svg new file mode 100644 index 00000000..c7b5857c --- /dev/null +++ b/assets/icons/RandomMode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/RandomMode.svg.import b/assets/icons/RandomMode.svg.import new file mode 100644 index 00000000..63a786e4 --- /dev/null +++ b/assets/icons/RandomMode.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfdxexemm7766" +path="res://.godot/imported/RandomMode.svg-0e6de5f36ee5ae23b97ec7abca32de16.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/RandomMode.svg" +dest_files=["res://.godot/imported/RandomMode.svg-0e6de5f36ee5ae23b97ec7abca32de16.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/icons/Zone.svg b/assets/icons/Zone.svg new file mode 100644 index 00000000..419aacc6 --- /dev/null +++ b/assets/icons/Zone.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/Zone.svg.import b/assets/icons/Zone.svg.import new file mode 100644 index 00000000..6f765465 --- /dev/null +++ b/assets/icons/Zone.svg.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc0acgx0we044" +path="res://.godot/imported/Zone.svg-4ee6a3e7c2a26955050d813c1a1ed98d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/Zone.svg" +dest_files=["res://.godot/imported/Zone.svg-4ee6a3e7c2a26955050d813c1a1ed98d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/components/DataInputs/DataInputColor/DataInputColor.gd b/components/DataInputs/DataInputColor/DataInputColor.gd new file mode 100644 index 00000000..39b71ebb --- /dev/null +++ b/components/DataInputs/DataInputColor/DataInputColor.gd @@ -0,0 +1,44 @@ + # Copyright (c) 2026 Liam Sherwin. All rights reserved. +# This file is part of the Spectrum Lighting Controller, licensed under the GPL v3.0 or later. +# See the LICENSE file for details. + +class_name DataInputColor extends DataInput +## DataInput for Data.Type.COLOR + + +## The LineEdit +var _button: ColorPickerButton + + +## Ready +func _ready() -> void: + _data_type = Data.Type.COLOR + _button = $HBox/Button + _label = $HBox/Label + _outline = $HBox/Button/Outline + _focus_node = _button + + +## Resets this DataInputString +func _reset() -> void: + _button.set_pick_color(Color.WHITE) + + +## Override this function to provide a SettingsModule to display +func _settings_module_changed(p_module: SettingsModule) -> void: + _button.set_pick_color(p_module.get_getter().call()) + + +## Called when the editable state is changed +func _set_editable(p_editable: bool) -> void: + _button.set_disabled(not p_editable) + + +## Called when the button is pressed down +func _on_button_button_down() -> void: + _update_outline_feedback(_module.get_setter().call()) + + +## Called when the color is changed on the color picker +func _on_button_color_changed(p_color: Color) -> void: + _update_outline_feedback(_module.get_setter().call(p_color)) diff --git a/components/DataInputs/DataInputColor/DataInputColor.gd.uid b/components/DataInputs/DataInputColor/DataInputColor.gd.uid new file mode 100644 index 00000000..1870cb11 --- /dev/null +++ b/components/DataInputs/DataInputColor/DataInputColor.gd.uid @@ -0,0 +1 @@ +uid://cgohncgxj4a8f diff --git a/components/DataInputs/DataInputColor/DataInputColor.tscn b/components/DataInputs/DataInputColor/DataInputColor.tscn new file mode 100644 index 00000000..4e5464b5 --- /dev/null +++ b/components/DataInputs/DataInputColor/DataInputColor.tscn @@ -0,0 +1,35 @@ +[gd_scene load_steps=3 format=3 uid="uid://rac2xe1lsdhe"] + +[ext_resource type="Script" uid="uid://cgohncgxj4a8f" path="res://components/DataInputs/DataInputColor/DataInputColor.gd" id="1_0hx11"] +[ext_resource type="StyleBox" uid="uid://cftl4b5dnt57b" path="res://assets/styles/ResolveBoxBGLess.tres" id="2_v7583"] + +[node name="DataInputColor" type="PanelContainer"] +custom_minimum_size = Vector2(0, 50) +offset_right = 86.5625 +offset_bottom = 49.0 +script = ExtResource("1_0hx11") + +[node name="HBox" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="Label" type="Label" parent="HBox"] +visible = false +layout_mode = 2 + +[node name="Button" type="ColorPickerButton" parent="HBox"] +custom_minimum_size = Vector2(150, 0) +layout_mode = 2 +size_flags_horizontal = 10 + +[node name="Outline" type="Panel" parent="HBox/Button"] +modulate = Color(1, 1, 1, 0) +layout_mode = 0 +offset_top = 721.0 +offset_right = 298.0 +offset_bottom = 721.0 +grow_horizontal = 2 +grow_vertical = 2 +mouse_filter = 1 +theme_override_styles/panel = ExtResource("2_v7583") + +[connection signal="color_changed" from="HBox/Button" to="." method="_on_button_color_changed"] diff --git a/components/TO_BE_SORTED/PlaybackRow/PlaybackRow.gd b/components/TO_BE_SORTED/PlaybackRow/PlaybackRow.gd index 51dd75fd..74b292a4 100644 --- a/components/TO_BE_SORTED/PlaybackRow/PlaybackRow.gd +++ b/components/TO_BE_SORTED/PlaybackRow/PlaybackRow.gd @@ -136,7 +136,7 @@ func load_auto_config(component: EngineComponent) -> void: slider.deserialize(config) - set_label(component.name) + set_label(component.name()) ## Saves this PlayBack row into a dict diff --git a/core/components/FixtureManifest.gd b/core/components/FixtureManifest.gd index b475870e..45928d5f 100644 --- a/core/components/FixtureManifest.gd +++ b/core/components/FixtureManifest.gd @@ -10,6 +10,7 @@ class_name FixtureManifest extends EngineComponent ## Info: This FixtureManifest contains basic info for a given manifest file. enum Type {Manifest, Info} + ## Current type of this FixtureManifest var type: Type = Type.Manifest @@ -37,7 +38,7 @@ func _init(p_uuid: String = UUID_Util.v4(), p_name: String = _name) -> void: super._init(p_uuid, p_name) _set_self_class("FixtureManifest") - _set_name("FixtureManifest") + set_name("FixtureManifest") ## Creates a new mode @@ -65,13 +66,14 @@ func create_zone(p_mode: String, p_zone: String) -> bool: ## Creates a parameter in the given mode and zone -func add_parameter(p_mode: String, p_zone: String, p_parameter: String, p_channels: Array[int], p_category: String = "") -> bool: +func add_parameter(p_mode: String, p_zone: String, p_parameter: String, p_channels: Array[int], p_category: String = "", p_default_function: String = "") -> bool: if not _modes.has(p_mode) or not _modes[p_mode].zones.has(p_zone): return false _modes[p_mode].zones[p_zone][p_parameter] = { "attribute": p_parameter, "offsets": p_channels.duplicate(), + "default_function": p_default_function, "functions": {} } @@ -103,7 +105,7 @@ func remove_parameter(p_mode: String, p_zone: String, p_parameter: String) -> bo ## Adds a funtion to the given parameter -func add_parameter_function(p_mode: String, p_zone: String, p_parameter: String, p_function: String, p_name: String, p_default: int, p_range: Array[int], p_can_fade: bool, p_vdim_effected: bool) -> bool: +func add_parameter_function(p_mode: String, p_zone: String, p_parameter: String, p_function: String, p_name: String, p_default: int, p_range: Array[int], p_can_fade: bool, p_vdim_effected: bool, p_control_type: Fixture.ControlType) -> bool: if not _modes.has(p_mode) or not _modes[p_mode].zones.has(p_zone) or not _modes[p_mode].zones[p_zone].has(p_parameter): return false @@ -113,6 +115,7 @@ func add_parameter_function(p_mode: String, p_zone: String, p_parameter: String, "default": p_default, "can_fade": p_can_fade, "vdim_effected": p_vdim_effected, + "control_type": p_control_type, "dmx_range": p_range.duplicate(), "sets": [] } @@ -168,9 +171,14 @@ func function_can_vdim(p_mode: String, p_zone: String, p_parameter: String, p_fu return _modes.get(p_mode, {}).get("zones", {}).get(p_zone, {}).get(p_parameter, {}).get("functions", {}).get(p_function, {}).get("vdim_effected", false) +## Checks if this FixtureManifest has a function that can vdim +func function_control_type(p_mode: String, p_zone: String, p_parameter: String, p_function: String) -> Fixture.ControlType: + return _modes.get(p_mode, {}).get("zones", {}).get(p_zone, {}).get(p_parameter, {}).get("functions", {}).get(p_function, {}).get("control_type", Fixture.ControlType.VALUE) + + ## Returns the given mode func get_mode(p_mode: String) -> Dictionary: - return _modes.get(p_mode, {}).duplicate(true) + return _modes.get(p_mode, {}) ## Returns all the modes in this manifest @@ -245,7 +253,7 @@ func deserialize(p_serialized_data: Dictionary) -> void: manufacturer = type_convert(p_serialized_data.get("manufacturer"), TYPE_STRING) importer = type_convert(p_serialized_data.get("importer"), TYPE_STRING) file_path = type_convert(p_serialized_data.get("file_path"), TYPE_STRING) - + _modes = type_convert(p_serialized_data.get("modes"), TYPE_DICTIONARY) _categorys = type_convert(p_serialized_data.get("categorys"), TYPE_DICTIONARY) _force_defaults = Array(type_convert(p_serialized_data.get("force_defaults"), TYPE_ARRAY), TYPE_STRING, "", null) diff --git a/core/components/Universe.gd b/core/components/Universe.gd index 32fcbae9..2b072bc7 100644 --- a/core/components/Universe.gd +++ b/core/components/Universe.gd @@ -167,7 +167,7 @@ func _remove_output(p_output: DMXOutput, p_no_signal: bool = false, p_delete: bo outputs_removed.emit([p_output]) if p_delete: - p_output.local_delete() + p_output.delete() return true diff --git a/core/components/fixtures/DMXFixture.gd b/core/components/fixtures/DMXFixture.gd index f495bd81..b2ea8627 100644 --- a/core/components/fixtures/DMXFixture.gd +++ b/core/components/fixtures/DMXFixture.gd @@ -39,6 +39,9 @@ var _raw_override_layers: Dictionary[String, Dictionary] = {} ## The FixtureManifest for this fixture var _manifest: FixtureManifest = null +## Stores all parameter subscriptions { "zone": { "parameter": Array[Callable] } } +var _parameter_subscriptions: Dictionary[String, Dictionary] + ## init func _init(p_uuid: String = UUID_Util.v4(), p_name: String = _name) -> void: @@ -119,6 +122,11 @@ func get_parameter_functions(p_zone: String, p_parameter: String) -> Array: return _manifest.get_parameter_functions(_mode, p_zone, p_parameter) +## Gets all the parameter functions +func get_function_control_type(p_zone: String, p_parameter: String, p_function: String) -> ControlType: + return _manifest.function_control_type(_mode, p_zone, p_parameter, p_function) + + ## Gets the default value of a parameter func get_default(p_zone: String, p_parameter: String, p_function: String = "", p_raw_dmx: bool = false) -> float: if p_function == "": @@ -126,7 +134,7 @@ func get_default(p_zone: String, p_parameter: String, p_function: String = "", p var dmx_value: int = _manifest.get_mode(_mode).zones[p_zone][p_parameter].functions[p_function].default var range: Array = _manifest.get_mode(_mode).zones[p_zone][p_parameter].functions[p_function].dmx_range - + if p_raw_dmx: return dmx_value else: @@ -145,8 +153,32 @@ func get_default_function(p_zone: String, p_parameter: String) -> String: ## Gets the current value, or the default -func get_current_value(p_zone: String, p_parameter: String, p_allow_default: bool = true) -> float: - return _active_values.get(p_zone, {}).get(p_parameter, {}).get("value", get_default(p_zone, p_parameter) if p_allow_default else 0.0) +func get_current_value(p_zone: String, p_parameter: String, p_allow_default: bool = true, p_allow_override: bool = true) -> float: + if has_override(p_zone, p_parameter): + return _raw_override_layers[p_zone][p_parameter].value + else: + return _active_values.get(p_zone, {}).get(p_parameter, {}).get("value", get_default(p_zone, p_parameter) if p_allow_default else 0.0) + + +## Gets the current function, or the default +func get_current_function(p_zone: String, p_parameter: String, p_allow_default: bool = true, p_allow_override: bool = true) -> String: + if has_override(p_zone, p_parameter): + return _raw_override_layers[p_zone][p_parameter].function + else: + return _active_values.get(p_zone, {}).get(p_parameter, {}).get("function", get_default_function(p_zone, p_parameter) if p_allow_default else "") + + +## Gets the current value, or the default +func get_current_value_or_force_default(p_zone: String, p_parameter: String) -> float: + var value: float = _active_values.get(p_zone, {}).get(p_parameter, {}).get("value", -1) + + if value == -1: + if has_force_default(p_parameter): + return get_default(p_zone, p_parameter) + else: + return 0.0 + else: + return value ## Gets all the zones @@ -162,6 +194,11 @@ func has_overrides() -> bool: return _raw_override_layers != {} +## Returns true if this Fixture has an override value +func has_override(p_zone: String, p_parameter: String) -> bool: + return _raw_override_layers.has(p_zone) and _raw_override_layers[p_zone].has(p_parameter) + + ## Checks if this fixture has a parameter func has_parameter(p_zone: String, p_parameter: String, p_function: String = "") -> bool: if not _manifest: @@ -178,11 +215,34 @@ func has_force_default(p_parameter: String) -> bool: return _manifest.has_force_default(p_parameter) +## Returns True if the given callable is subscribed to the given parameter +func has_parameter_subscription(p_zone: String, p_parameter: String, p_callable: Callable) -> bool: + return _parameter_subscriptions.get(p_zone, {}).get(p_parameter, {}).has(p_callable) + + ## Checks if this DMXFixture has a function that can fade func function_can_fade(p_zone: String, p_parameter: String, p_function: String) -> bool: return _manifest.function_can_fade(_mode, p_zone, p_parameter, p_function) +## Subscribes to a parameter on the given zone +func subscribe_to_parameter(p_zone: String, p_parameter: String, p_callable: Callable) -> bool: + if has_parameter_subscription(p_zone, p_parameter, p_callable): + return false + + _parameter_subscriptions.get_or_add(p_zone, {}).get_or_add(p_parameter, []).append(p_callable) + return true + + +## Removes a subscription from the parameter in the given zone +func remove_subscription(p_zone: String, p_parameter: String, p_callable: Callable) -> bool: + if not has_parameter_subscription(p_zone, p_parameter, p_callable): + return false + + _parameter_subscriptions[p_zone][p_parameter].erase(p_callable) + return true + + ## Internl: Sets the channel func _set_channel(p_channel: int, p_no_signal: bool = false) -> void: _channel = p_channel @@ -220,18 +280,30 @@ func _set_manifest(p_manifest: Variant, p_mode: String, p_no_signal: bool = fals func _set_parameter(p_zone: String, p_parameter: String, p_function: String, p_value: Variant) -> void: _active_values.get_or_add(p_zone, {})[p_parameter] = {"value": p_value, "function": p_function} parameter_changed.emit(p_zone, p_parameter, p_function, p_value) + + if has_override(p_zone, p_parameter): + return + + _emit_parameter_subs(p_zone, p_parameter, p_function, p_value, false) ## Internal: Erases the parameter on the given layer func _erase_parameter(p_zone: String, p_parameter: String) -> void: _active_values.get_or_add(p_zone, {}).erase(p_parameter) parameter_erased.emit(p_parameter, p_zone) + + if has_override(p_zone, p_parameter): + return + + _emit_parameter_subs(p_zone, p_parameter, get_default_function(p_zone, p_parameter), get_default(p_zone, p_parameter), false) ## Internal: Sets a parameter override to a float value func _set_override(p_zone: String, p_parameter: String, p_function: String, p_value: float) -> void: _raw_override_layers.get_or_add(p_zone, {})[p_parameter] = {"value": p_value, "function": p_function} override_changed.emit(p_zone, p_parameter, p_function, p_value) + + _emit_parameter_subs(p_zone, p_parameter, p_function, p_value, true) ## Internal: Erases the parameter override @@ -240,6 +312,17 @@ func _erase_override(p_zone: String, p_parameter: String) -> void: _raw_override_layers.erase(p_zone) override_erased.emit(p_zone, p_parameter) + + _emit_parameter_subs(p_zone, p_parameter, get_current_function(p_zone, p_parameter), get_current_value(p_zone, p_parameter), false) + + +## Calls back all parameter subscripptions +func _emit_parameter_subs(p_zone: String, p_parameter: String, p_function: String, p_value: float, p_override: bool) -> void: + for callable: Callable in _parameter_subscriptions.get(p_zone, {}).get(p_parameter, []).duplicate(): + if not callable.is_valid(): + _parameter_subscriptions[p_zone][p_parameter].erase(callable) + else: + callable.call(p_value, p_function, p_override) ## Internal: Erases all overrides diff --git a/core/components/fixtures/Fixture.gd b/core/components/fixtures/Fixture.gd index 5f9ef011..49e6e8fc 100644 --- a/core/components/fixtures/Fixture.gd +++ b/core/components/fixtures/Fixture.gd @@ -23,6 +23,17 @@ signal override_erased(zone: String, parameter: String) signal all_override_removed() +## Enum for ControlType +enum ControlType { + INTENSITY, # % based, blackout/full + POSITION, # homeable, spatial + SELECT, # discrete choices + VALUE, # continuous, non-% + SPEED, # rate-based + SYSTEM # non-show controls +} + + ## Root Zone static var RootZone: String = "root" @@ -98,6 +109,11 @@ func get_parameter_functions(p_zone: String, p_parameter: String) -> Array: return [] +## Gets all the parameter functions +func get_function_control_type(p_zone: String, p_parameter: String, p_function: String) -> ControlType: + return ControlType.VALUE + + ## Gets the default value of a parameter func get_default(p_zone: String, p_parameter: String, p_function: String = "", p_raw_dmx: bool = false) -> float: return 0.0 @@ -109,17 +125,17 @@ func get_default_function(p_zone: String, p_parameter: String) -> String: ## Gets the current value, or the default -func get_current_value(p_zone: String, p_parameter: String, p_allow_default: bool = true) -> float: +func get_current_value(p_zone: String, p_parameter: String, p_allow_default: bool = true, p_allow_override: bool = true) -> float: return 0.0 -## Gets a value from the given layer id, parameter, and zone -func get_current_value_layered(p_zone: String, p_parameter: String, p_layer_id: String, p_function: String = "", p_allow_default: bool = true) -> float: - return 0.0 +## Gets the current value, or the default +func get_current_function(p_zone: String, p_parameter: String, p_allow_default: bool = true, p_allow_override: bool = true) -> String: + return "" -## Gets the current value from a given layer ID, the default is none is present, or 0 if p_parameter is not a force default -func get_current_value_layered_or_force_default(p_zone: String, p_parameter: String, p_layer_id: String, p_function: String = "") -> float: +## Gets the current value, or the default +func get_current_value_or_force_default(p_zone: String, p_parameter: String) -> float: return 0.0 @@ -133,6 +149,11 @@ func has_overrides() -> bool: return false +## Returns true if this Fixture has an override value +func has_override(p_zone: String, p_parameter: String) -> bool: + return false + + ## Checks if this fixture has a parameter func has_parameter(p_zone: String, p_parameter: String, p_function: String = "") -> bool: return false @@ -148,7 +169,17 @@ func function_can_fade(p_zone: String, p_parameter: String, p_function: String) return false -## Internal: Sets a parameter to a float value +## Subscribes to a parameter on the given zone +func subscribe_to_parameter(p_zone: String, p_parameter: String, p_callable: Callable) -> bool: + return false + + +## Removes a subscription from the parameter in the given zone +func remove_subscription(p_zone: String, p_parameter: String, p_callable: Callable) -> bool: + return false + + +## Internal: Sets a parameter to a float values func _set_parameter(p_zone: String, p_parameter: String, p_function: String, p_value: Variant) -> void: return diff --git a/core/global/Programmer.gd b/core/global/Programmer.gd index 9921246f..d8aea57a 100644 --- a/core/global/Programmer.gd +++ b/core/global/Programmer.gd @@ -9,9 +9,6 @@ class_name CoreProgrammer extends Node ## Emitted when the programmer is cleared signal cleared() -## Emitted when the store mode state is changed -signal store_mode_changed(store_mode_state: bool, class_hint: String) - ## Save Modes enum SaveMode { @@ -32,6 +29,41 @@ enum MixMode { Subtractive ## Uses Subtractive Mixing } +## Enum for Category +enum Category { + DIMMER, + COLOR, + GOBO, + POSITION, + BEAM, + FOCUS, + SHAPERS, + CONTROL, + OTHER, +} + +## Enum for Layer +enum Layer { + VALUE, + START, + STOP, + CANFADE +} + + +## Stores the order of all parameters +var _parameter_order: Dictionary[Category, Variant] = { + Category.DIMMER: ["Dimmer"], + Category.COLOR: [ + "ColorAdd_R", "ColorAdd_G", "ColorAdd_B", + "ColorAdd_W", "ColorSub_R", "ColorSub_G", "ColorSub_B", "ColorSub_C", "ColorSub_M", "ColorSub_Y", + "ColorRGB_Red", "ColorRGB_Green", "ColorRGB_Blue", + "CTC", "Color" + ], + Category.POSITION: ["Pan", "Tilt"] +} + + ## Current store mode state var _store_mode_state: bool = false @@ -50,6 +82,31 @@ func _init() -> void: settings_manager.register_networked_callbacks({ "on_cleared": _clear, }) + + _convert_order_array_to_dict() + + +## Gets a Category enum as a string +func get_category_as_string(p_category: Category) -> String: + return Category.keys()[p_category].capitalize() + + +## Gets a Category from a string +func get_category_from_string(p_category: String) -> Category: + var index: int = Category.keys().find(p_category.to_upper()) + + return (index as Category) if index != -1 else Category.OTHER + + +## Gets the order index of a given parameter, or -1 +func get_parameter_index(p_category: Category, p_parameter: String) -> int: + var number_result: Dictionary = Utils.remove_numbers(p_parameter) + var index: int = _parameter_order.get(p_category, {}).get(number_result.string, -1) + + if number_result.numbers: + index += number_result.numbers[0] + + return index ## Clears the programmer @@ -57,11 +114,6 @@ func clear() -> Promise: return Network.send_command("Programmer", "clear") -## Called when the programmer is cleared on the server -func _clear() -> void: - cleared.emit() - - ## Function to set the fixture data at the given chanel key func set_parameter(p_fixtures: Array, p_parameter: String, p_function: String, p_value: float, p_zone: String) -> Promise: return Network.send_command("Programmer", "set_parameter", [p_fixtures, p_parameter, p_function, p_value, p_zone]) @@ -77,57 +129,23 @@ func erase_parameter(p_fixtures: Array, p_parameter: String, p_zone: String) -> return Network.send_command("Programmer", "erase_parameter", [p_fixtures, p_parameter, p_zone]) -## Saves the current state of this programmer to a scene -func save_to_new_scene(fixtures: Array, mode: SaveMode = SaveMode.MODIFIED) -> Promise: - return Network.send_command("Programmer", "save_to_new_scene", [fixtures, mode]) - - ## Shortcut to set the color of fixtures func shortcut_set_color(p_fixtures: Array, p_color: Color, p_mode: MixMode) -> Promise: return Network.send_command("Programmer", "shortcut_set_color", [p_fixtures, p_color, p_mode]) -## Enters store mode -func enter_store_mode(callback: Callable = _store_callback, class_hint: String = "EngineComponent") -> void: - _store_mode_state = true - _store_mode_callback = callback - store_mode_changed.emit(_store_mode_state, class_hint) - - -## Exits store mode -func exit_store_mode() -> void: - if not _store_mode_state: - return - - _store_mode_state = false - _store_mode_callback = Callable() - store_mode_changed.emit(_store_mode_state, "") - - -## Gets the store mode state -func get_store_mode() -> bool: - return _store_mode_state +## Converts the parameter order array to a dictonary for faster lookup +func _convert_order_array_to_dict() -> void: + for category: Category in _parameter_order: + var array_order: Array = _parameter_order[category] + var dict_order: Dictionary[String, int] + + for index: int in range(0, array_order.size()): + dict_order[array_order[index]] = index + + _parameter_order[category] = dict_order -## Resolves the store mode by handing back a component to store to -func resolve_store_mode(with: EngineComponent) -> void: - exit_store_mode() - - var fixtures: Array = Values.get_selection_value("selected_fixtures") - Network.send_command("Programmer", "store_into", [with, fixtures]) - - -## Resolves the store mode by handing back a classname for a new component -func resolve_store_mode_with_new(classname: String) -> Promise: - exit_store_mode() - - var fixtures: Array = Values.get_selection_value("selected_fixtures") - var promise: Promise = Network.send_command("Programmer", "store_into_new", [classname, fixtures]) - - promise.then(_store_callback) - return promise - - -## Store callback -func _store_callback(component: EngineComponent) -> void: - Interface.show_name_prompt(component) +## Called when the programmer is cleared on the server +func _clear() -> void: + cleared.emit() diff --git a/core/static/Utils.gd b/core/static/Utils.gd index ca290a71..fdceea5a 100644 --- a/core/static/Utils.gd +++ b/core/static/Utils.gd @@ -16,6 +16,47 @@ class_name Utils extends Object ## } static var _signal_connections: Dictionary +## Removes numbers regex +static var number_regex := RegEx.new() + + +## init +static func _static_init() -> void: + number_regex.compile("\\d+") + + +## Formats a 12 hour time from an 24 hour interger +static func format_12_hour(p_hour_24: int) -> String: + var period: String = "AM" + var hour_12: int = p_hour_24 + + if p_hour_24 == 0: + hour_12 = 12 + period = "AM" + elif p_hour_24 == 12: + hour_12 = 12 + period = "PM" + elif p_hour_24 > 12: + hour_12 = p_hour_24 - 12 + period = "PM" + + return str("%02d" % hour_12) + period + + +## Removes numbers from a string +static func remove_numbers(p_string: String) -> Dictionary: + var matches: Array[RegExMatch] = number_regex.search_all(p_string) + var numbers: Array[int] = [] + + for m: RegExMatch in matches: + numbers.append(int(m.get_string())) + + var cleaned: String = number_regex.sub(p_string, "", true) + + return { + "string": cleaned, + "numbers": numbers + } ## Saves a JSON valid dictonary to a file, creates the file and folder if it does not exist diff --git a/panels/TO_BE_SORTED/Clock/Clock.gd b/panels/TO_BE_SORTED/Clock/Clock.gd deleted file mode 100644 index ec984827..00000000 --- a/panels/TO_BE_SORTED/Clock/Clock.gd +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (c) 2025 Liam Sherwin. All rights reserved. -# This file is part of the Spectrum Lighting Engine, licensed under the GPL v3.0 or later. -# See the LICENSE file for details. - -class_name UIClock extends UIPanel -## A clock - - -func _ready() -> void: - $Control/Label.label_settings = $Control/Label.label_settings.duplicate() - _set_font_size() - - -func _process(delta: float) -> void: - $Control/Label.text = Time.get_time_string_from_system() - - -func _set_font_size() -> void: - $Control/Label.label_settings.font_size = (size.x * 0.13) diff --git a/panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd b/panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd deleted file mode 100644 index 8a2dd7d1..00000000 --- a/panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2025 Liam Sherwin. All rights reserved. -# This file is part of the Spectrum Lighting Engine, licensed under the GPL v3.0 or later. -# See the LICENSE file for details. - -class_name UIColorPicker extends UIPanel -## UI Panel for showing a color wheel - - -## VSlider for the value input on the pad -@export var _pad_value_slider: VSlider - -## The TextureRect for the Pad -@export var _pad: TextureRect - -## Crosshair for the Pad -@export var _crosshair: TextureRect - -## Mix Mode selection -@export var _mix_mode: OptionButton - - -## Hue -var _hue: float = 1 - -## Saturation -var _sat: float = 1 - -## Value -var _value: float = 1 - - -## Time since last call -var _last_call_time: int = 0 - -## 45 times per second -var _call_interval: int = 1.0 / 45.0 - - -## Updates the color -func _update_color() -> void: - var color: Color = Color.from_hsv(_hue, _sat, _value) - - _update_programmer(color) - - -## Called when the color is changed, will only output CoreEngine.call_interval times per second to avoid overloading the server -func _update_programmer(color: Color) -> void: - var current_time = Time.get_ticks_msec() / 1000.0 # Convert milliseconds to seconds - - if current_time - _last_call_time >= _call_interval: - Programmer.shortcut_set_color(Values.get_selection_value("selected_fixtures", []), color, _mix_mode.selected) - - _last_call_time = current_time - - -## Called for all GUI imputs on the pad -func _on_texture_rect_gui_input(event: InputEvent) -> void: - if event is InputEventMouse and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): - var mouse_pos: Vector2 = _pad.get_local_mouse_position().clamp(Vector2.ZERO, _pad.size) - - _hue = remap(mouse_pos.x, 0, _pad.size.x, 0, 1) - _sat = remap(mouse_pos.y, _pad.size.y, 0, 0, 1) - - _crosshair.position = mouse_pos - _crosshair.size / 2 - _update_color() - - -## Called when the pad value slider is moved -func _on_pad_value_slider_value_changed(value: float) -> void: - _value = value - _pad.self_modulate = Color(value, value, value) - _crosshair.modulate = Color(1-value , 1-value , 1-value) - _update_color() - - -## Called when the pad is resized -func _on_pad_resized() -> void: - _crosshair.position = Vector2( - remap(_hue, 0, 1, 0, _pad.size.x), - remap(_sat, 0, 1, _pad.size.y, 0) - ) - _crosshair.size / 2 diff --git a/panels/TO_BE_SORTED/ColorPicker/ColorPicker.tscn b/panels/TO_BE_SORTED/ColorPicker/ColorPicker.tscn deleted file mode 100644 index 5dd9c7d6..00000000 --- a/panels/TO_BE_SORTED/ColorPicker/ColorPicker.tscn +++ /dev/null @@ -1,284 +0,0 @@ -[gd_scene load_steps=10 format=3 uid="uid://dhp8aa07tyxbf"] - -[ext_resource type="StyleBox" uid="uid://daxhx5qr5qdeu" path="res://assets/styles/UIPanelBase.tres" id="1_dxfro"] -[ext_resource type="Script" uid="uid://bltvgq1jnaqgt" path="res://panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd" id="1_pckpl"] -[ext_resource type="StyleBox" uid="uid://csle20exwqtti" path="res://assets/styles/PanelMenuBar.tres" id="2_dxq6u"] -[ext_resource type="Texture2D" uid="uid://clt2djtbul2kf" path="res://assets/icons/ColorPalett.svg" id="3_pxh41"] -[ext_resource type="Texture2D" uid="uid://2p7ffb5yq0hx" path="res://assets/asset_scripts/color_picker.png" id="5_dxq6u"] -[ext_resource type="PackedScene" uid="uid://dfu5s8fjy0ex4" path="res://components/EditControls/EditControls.tscn" id="5_j1krd"] -[ext_resource type="Texture2D" uid="uid://b7q628ocmror7" path="res://assets/icons/Crosshair.svg" id="7_7y88r"] - -[sub_resource type="ButtonGroup" id="ButtonGroup_7y88r"] -resource_name = "dasda" - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7y88r"] -corner_radius_top_left = 5 -corner_radius_top_right = 5 -corner_radius_bottom_right = 5 -corner_radius_bottom_left = 5 - -[node name="PanelContainer" type="PanelContainer" node_paths=PackedStringArray("_pad_value_slider", "_pad", "_crosshair", "_mix_mode", "edit_controls")] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -theme_override_styles/panel = ExtResource("1_dxfro") -script = ExtResource("1_pckpl") -_pad_value_slider = NodePath("VBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer/PadValueSlider") -_pad = NodePath("VBoxContainer/Picker/HBoxContainer/PanelContainer/Pad") -_crosshair = NodePath("VBoxContainer/Picker/HBoxContainer/PanelContainer/Pad/Crosshair") -_mix_mode = NodePath("VBoxContainer/PanelContainer/HBoxContainer/PanelContainer/MixMode") -edit_controls = NodePath("VBoxContainer/PanelContainer/HBoxContainer/EditControls") - -[node name="VBoxContainer" type="VBoxContainer" parent="."] -layout_mode = 2 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer"] -layout_mode = 2 -theme_override_styles/panel = ExtResource("2_dxq6u") - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer"] -layout_mode = 2 - -[node name="Button" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -text = "ColorPicker" -icon = ExtResource("3_pxh41") -flat = true - -[node name="TabBar" type="PanelContainer" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 6 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer/HBoxContainer/TabBar"] -layout_mode = 2 - -[node name="ShowPickers" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer/TabBar/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) -layout_mode = 2 -toggle_mode = true -button_pressed = true -button_group = SubResource("ButtonGroup_7y88r") -text = "Picker" - -[node name="ShowSliders" type="Button" parent="VBoxContainer/PanelContainer/HBoxContainer/TabBar/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) -layout_mode = 2 -toggle_mode = true -button_group = SubResource("ButtonGroup_7y88r") -text = "Sliders" - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 - -[node name="MixMode" type="OptionButton" parent="VBoxContainer/PanelContainer/HBoxContainer/PanelContainer"] -layout_mode = 2 -selected = 0 -item_count = 2 -popup/item_0/text = "Additive" -popup/item_0/id = 0 -popup/item_1/text = "Subtractive" -popup/item_1/id = 1 - -[node name="EditControls" parent="VBoxContainer/PanelContainer/HBoxContainer" instance=ExtResource("5_j1krd")] -layout_mode = 2 -show_edit = false -show_settings = false - -[node name="Picker" type="PanelContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Picker"] -layout_mode = 2 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/Picker/HBoxContainer"] -clip_children = 1 -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_7y88r") - -[node name="Pad" type="TextureRect" parent="VBoxContainer/Picker/HBoxContainer/PanelContainer"] -layout_mode = 2 -texture = ExtResource("5_dxq6u") -expand_mode = 1 - -[node name="Crosshair" type="TextureRect" parent="VBoxContainer/Picker/HBoxContainer/PanelContainer/Pad"] -modulate = Color(0.0475577, 0.0475576, 0.0475576, 1) -layout_mode = 1 -offset_right = 24.0 -offset_bottom = 24.0 -texture = ExtResource("7_7y88r") -stretch_mode = 2 - -[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/Picker/HBoxContainer"] -custom_minimum_size = Vector2(100, 0) -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Picker/HBoxContainer/PanelContainer2"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer"] -layout_mode = 2 -text = "Value" -horizontal_alignment = 1 - -[node name="PadValueSlider" type="VSlider" parent="VBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 -max_value = 1.0 -step = 0.001 -value = 1.0 -tick_count = 10 -ticks_on_borders = true - -[node name="Sliders" type="PanelContainer" parent="VBoxContainer"] -visible = false -layout_mode = 2 -size_flags_vertical = 3 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Sliders"] -layout_mode = 2 - -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/Sliders/HBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer"] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Red" - -[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider2" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Green" - -[node name="VBoxContainer3" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider3" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Blue" - -[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/Sliders/HBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2"] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Hue" - -[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider2" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Sat" - -[node name="VBoxContainer3" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider3" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer2/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Value" - -[node name="PanelContainer3" type="PanelContainer" parent="VBoxContainer/Sliders/HBoxContainer"] -layout_mode = 2 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3"] -layout_mode = 2 - -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Cyan" - -[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider2" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer2"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Magenta" - -[node name="VBoxContainer3" type="VBoxContainer" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer"] -layout_mode = 2 - -[node name="VSlider3" type="VSlider" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -size_flags_vertical = 3 - -[node name="Label" type="Label" parent="VBoxContainer/Sliders/HBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer3"] -layout_mode = 2 -size_flags_horizontal = 4 -text = "Yellow" - -[connection signal="toggled" from="VBoxContainer/PanelContainer/HBoxContainer/TabBar/HBoxContainer/ShowPickers" to="VBoxContainer/Picker" method="set_visible"] -[connection signal="toggled" from="VBoxContainer/PanelContainer/HBoxContainer/TabBar/HBoxContainer/ShowSliders" to="VBoxContainer/Sliders" method="set_visible"] -[connection signal="gui_input" from="VBoxContainer/Picker/HBoxContainer/PanelContainer/Pad" to="." method="_on_texture_rect_gui_input"] -[connection signal="resized" from="VBoxContainer/Picker/HBoxContainer/PanelContainer/Pad" to="." method="_on_pad_resized"] -[connection signal="value_changed" from="VBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer/PadValueSlider" to="." method="_on_pad_value_slider_value_changed"] diff --git a/panels/UIClock/UIClock.gd b/panels/UIClock/UIClock.gd new file mode 100644 index 00000000..332dd90d --- /dev/null +++ b/panels/UIClock/UIClock.gd @@ -0,0 +1,319 @@ +# Copyright (c) 2026 Liam Sherwin. All rights reserved. +# This file is part of the Spectrum Lighting Engine, licensed under the GPL v3.0 or later. +# See the LICENSE file for details. + +class_name UIClock extends UIPanel +## A clock + + +## Emitted when the show year state is changed +signal show_year_changed(show_year: bool) + +## Emitted when the show month state is changed +signal show_month_changed(show_month: bool) + +## Emitted when the show day state is changed +signal show_day_changed(show_day: bool) + +## Emitted when the show hour state is changed +signal show_hour_changed(show_hour: bool) + +## Emitted when the show minute state is changed +signal show_minute_changed(show_minute: bool) + +## Emitted when the show second state is changed +signal show_second_changed(show_second: bool) + +## Emitted when the show millisecond state is changed +signal show_millisecond_changed(show_millisecond: bool) + +## Emitted when the use twelve hour state is changed +signal use_twelve_hour_changed(use_twelve_hour: bool) + +## Emitted when the font scale multiplier is changed +signal font_scale_multiplier_changed(font_scale_multiplier: float) + +## Emitted when the font color is changed +signal font_color_changed(font_color: Color) + + +## Contains english names for all months +const Months: Array[String] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] + +## Contains english names for all week days +const WeekDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] + + +## The Label to show the time +@export var label: Label + + +## Shows the year +var _show_year: bool = false + +## Shows the month +var _show_month: bool = false + +## Shows the day +var _show_day: bool = false + +## Shows the hour +var _show_hour: bool = true + +## Shows the minute +var _show_minute: bool = true + +## Shows the second +var _show_second: bool = true + +## Shows the millisecond +var _show_millisecond: bool = false + +## Displays the hour in AM/PM time +var _use_twelve_hour: bool = false + +## The font scale multiplier +var _font_scale_multiplier: float = 0.9 + +## Font color +var _font_color: Color = Color.WHITE + + +## init +func _init() -> void: + super._init() + + _set_class_name("UIClock") + + settings_manager.register_setting("ShowYear", Data.Type.BOOL, set_show_year, get_show_year, [show_year_changed]) + settings_manager.register_setting("ShowMonth", Data.Type.BOOL, set_show_month, get_show_month, [show_month_changed]) + settings_manager.register_setting("ShowDay", Data.Type.BOOL, set_show_day, get_show_day, [show_day_changed]) + settings_manager.register_setting("ShowHour", Data.Type.BOOL, set_show_hour, get_show_hour, [show_hour_changed]) + settings_manager.register_setting("ShowMinute", Data.Type.BOOL, set_show_minute, get_show_minute, [show_minute_changed]) + settings_manager.register_setting("ShowSecond", Data.Type.BOOL, set_show_second, get_show_second, [show_second_changed]) + settings_manager.register_setting("ShowMillisecond", Data.Type.BOOL, set_show_millisecond, get_show_millisecond, [show_millisecond_changed]) + + settings_manager.register_setting("UseTwelveHour", Data.Type.BOOL, set_use_twelve_hour, get_use_twelve_hour, [use_twelve_hour_changed]) + settings_manager.register_setting("FontScaleMultiplier", Data.Type.FLOAT, set_font_scale_multiplier, get_font_scale_multiplier, [font_scale_multiplier_changed]).set_min_max(0.1, 5.0) + settings_manager.register_setting("FontColor", Data.Type.COLOR, set_font_color, get_font_color, [font_color_changed]) + +## ready +func _ready() -> void: + label.label_settings = label.label_settings.duplicate() + + await get_tree().process_frame + await get_tree().process_frame + + _update_font_size() + + +## process +func _process(delta: float) -> void: + var date_time: Dictionary = Time.get_datetime_dict_from_system() + var result: PackedStringArray = [] + + if _show_year: + result.append(str(date_time.year)) + + if _show_month: + result.append(Months[date_time.month]) + + if _show_day: + result.append(WeekDays[date_time.weekday]) + + if _show_hour: + result.append(Utils.format_12_hour(date_time.hour) if _use_twelve_hour else ("%02d" % date_time.hour)) + + if _show_minute: + result.append("%02d" % date_time.minute) + + if _show_second: + result.append("%02d" % date_time.second) + + if _show_millisecond: + var now_sec: float = Time.get_unix_time_from_system() + var ms_in_second: int = int((now_sec - floor(now_sec)) * 1000) + result.append("%03d" % ms_in_second) + + label.set_text(":".join(result)) + + +## Sets the show year state +func set_show_year(p_show_year: bool, p_no_signal: bool = false) -> void: + _show_year = p_show_year + queue(_update_font_size) + + if not p_no_signal: + show_year_changed.emit(_show_year) + + +## Sets the show month state +func set_show_month(p_show_month: bool, p_no_signal: bool = false) -> void: + _show_month = p_show_month + queue(_update_font_size) + + if not p_no_signal: + show_month_changed.emit(_show_month) + + +## Sets the show day state +func set_show_day(p_show_day: bool, p_no_signal: bool = false) -> void: + _show_day = p_show_day + queue(_update_font_size) + + if not p_no_signal: + show_day_changed.emit(_show_day) + + +## Sets the show hour state +func set_show_hour(p_show_hour: bool, p_no_signal: bool = false) -> void: + _show_hour = p_show_hour + queue(_update_font_size) + + if not p_no_signal: + show_hour_changed.emit(_show_hour) + + +## Sets the show minute state +func set_show_minute(p_show_minute: bool, p_no_signal: bool = false) -> void: + _show_minute = p_show_minute + queue(_update_font_size) + + if not p_no_signal: + show_minute_changed.emit(_show_minute) + + +## Sets the show second state +func set_show_second(p_show_second: bool, p_no_signal: bool = false) -> void: + _show_second = p_show_second + queue(_update_font_size) + + if not p_no_signal: + show_second_changed.emit(_show_second) + + +## Sets the show millisecond state +func set_show_millisecond(p_show_millisecond: bool, p_no_signal: bool = false) -> void: + _show_millisecond = p_show_millisecond + queue(_update_font_size) + + if not p_no_signal: + show_millisecond_changed.emit(_show_millisecond) + + +## Sets the use twelve hour state +func set_use_twelve_hour(p_use_twelve_hour: bool, p_no_signal: bool = false) -> void: + _use_twelve_hour = p_use_twelve_hour + queue(_update_font_size) + + if not p_no_signal: + use_twelve_hour_changed.emit(_use_twelve_hour) + + +## Sets the font scale multiplier +func set_font_scale_multiplier(p_font_scale_multiplier: float, p_no_signal: bool = false) -> void: + _font_scale_multiplier = p_font_scale_multiplier + queue(_update_font_size) + + if not p_no_signal: + font_scale_multiplier_changed.emit(_font_scale_multiplier) + + +## Sets the font color +func set_font_color(p_font_color: Color, p_no_signal: bool = false) -> void: + _font_color = p_font_color + label.get_label_settings().set_font_color(_font_color) + + if not p_no_signal: + font_color_changed.emit(_font_color) + + +## Gets the show year state +func get_show_year() -> bool: + return _show_year + + +## Gets the show month state +func get_show_month() -> bool: + return _show_month + + +## Gets the show day state +func get_show_day() -> bool: + return _show_day + + +## Gets the show hour state +func get_show_hour() -> bool: + return _show_hour + + +## Gets the show minute state +func get_show_minute() -> bool: + return _show_minute + + +## Gets the show second state +func get_show_second() -> bool: + return _show_second + + +## Gets the show millisecond state +func get_show_millisecond() -> bool: + return _show_millisecond + + +## Gets the use twelve hour state +func get_use_twelve_hour() -> bool: + return _use_twelve_hour + + +## Gets the font scale multiplier +func get_font_scale_multiplier() -> float: + return _font_scale_multiplier + + +## Gets the font color +func get_font_color() -> Color: + return _font_color + + +## Serializes this UIClock +func serialize() -> Dictionary: + return super.serialize().merged({ + "show_year": _show_year, + "show_month": _show_month, + "show_day": _show_day, + "show_hour": _show_hour, + "show_minute": _show_minute, + "show_second": _show_second, + "show_millisecond": _show_millisecond, + "font_scale_multiplier": _font_scale_multiplier, + "font_color": var_to_str(_font_color), + }) + + +## Deserialize this UIClock +func deserialize(p_serialized_data: Dictionary) -> void: + super.deserialize(p_serialized_data) + print("deser") + set_show_year(type_convert(p_serialized_data.get("show_year", _show_year), TYPE_BOOL), true) + set_show_month(type_convert(p_serialized_data.get("show_month", _show_month), TYPE_BOOL), true) + set_show_day(type_convert(p_serialized_data.get("show_day", _show_day), TYPE_BOOL), true) + set_show_hour(type_convert(p_serialized_data.get("show_hour", _show_hour), TYPE_BOOL), true) + set_show_minute(type_convert(p_serialized_data.get("show_minute", _show_minute), TYPE_BOOL), true) + set_show_second(type_convert(p_serialized_data.get("show_second", _show_second), TYPE_BOOL), true) + set_show_millisecond(type_convert(p_serialized_data.get("show_millisecond", _show_millisecond), TYPE_BOOL), true) + + set_font_scale_multiplier(type_convert(p_serialized_data.get("font_scale_multiplier", _font_scale_multiplier), TYPE_FLOAT), true) + set_font_color(type_convert(str_to_var(type_convert(p_serialized_data.get("font_color", _font_color), TYPE_STRING)), TYPE_COLOR), true) + + +## Sets font size +func _update_font_size() -> void: + label.label_settings.font_size += 32 + var text_width: int = label.label_settings.font.get_string_size(label.text, label.horizontal_alignment, -1, label.label_settings.font_size).x + + if text_width > size.x and text_width > 0: + var scale_factor: float = size.x / text_width * _font_scale_multiplier + label.label_settings.set_font_size(int(label.label_settings.font_size * scale_factor)) diff --git a/panels/TO_BE_SORTED/Clock/Clock.gd.uid b/panels/UIClock/UIClock.gd.uid similarity index 100% rename from panels/TO_BE_SORTED/Clock/Clock.gd.uid rename to panels/UIClock/UIClock.gd.uid diff --git a/panels/TO_BE_SORTED/Clock/Clock.tscn b/panels/UIClock/UIClock.tscn similarity index 64% rename from panels/TO_BE_SORTED/Clock/Clock.tscn rename to panels/UIClock/UIClock.tscn index 065af0a0..00a95342 100644 --- a/panels/TO_BE_SORTED/Clock/Clock.tscn +++ b/panels/UIClock/UIClock.tscn @@ -1,13 +1,13 @@ [gd_scene load_steps=5 format=3 uid="uid://8mfy3g85o3g5"] -[ext_resource type="Script" uid="uid://dt5d0ib35v1w4" path="res://panels/TO_BE_SORTED/Clock/Clock.gd" id="1_8tr61"] -[ext_resource type="StyleBox" uid="uid://daxhx5qr5qdeu" path="res://assets/styles/UIPanelBase.tres" id="1_r82tc"] -[ext_resource type="FontFile" uid="uid://crlak6jhg5jy2" path="res://assets/font/RubikMonoOne-Regular.ttf" id="2_546b1"] +[ext_resource type="StyleBox" uid="uid://daxhx5qr5qdeu" path="res://assets/styles/UIPanelBase.tres" id="1_r3wuq"] +[ext_resource type="Script" uid="uid://dt5d0ib35v1w4" path="res://panels/UIClock/UIClock.gd" id="2_xjxwu"] +[ext_resource type="FontFile" uid="uid://crlak6jhg5jy2" path="res://assets/font/RubikMonoOne-Regular.ttf" id="3_bm64l"] [sub_resource type="LabelSettings" id="LabelSettings_2atlb"] -font = ExtResource("2_546b1") +font = ExtResource("3_bm64l") -[node name="Clock" type="PanelContainer"] +[node name="UIClock" type="PanelContainer" node_paths=PackedStringArray("label")] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 @@ -15,8 +15,9 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_styles/panel = ExtResource("1_r82tc") -script = ExtResource("1_8tr61") +theme_override_styles/panel = ExtResource("1_r3wuq") +script = ExtResource("2_xjxwu") +label = NodePath("Control/Label") [node name="Control" type="Control" parent="."] layout_mode = 2 @@ -28,9 +29,9 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -75.0 +offset_left = -80.0 offset_top = -10.0 -offset_right = 75.0 +offset_right = 80.0 offset_bottom = 10.0 grow_horizontal = 2 grow_vertical = 2 @@ -40,4 +41,4 @@ label_settings = SubResource("LabelSettings_2atlb") horizontal_alignment = 1 vertical_alignment = 1 -[connection signal="resized" from="." to="." method="_set_font_size"] +[connection signal="resized" from="." to="." method="_update_font_size"] diff --git a/panels/UIColorPicker/UIColorPicker.gd b/panels/UIColorPicker/UIColorPicker.gd new file mode 100644 index 00000000..4f1794c9 --- /dev/null +++ b/panels/UIColorPicker/UIColorPicker.gd @@ -0,0 +1,121 @@ +# Copyright (c) 2025 Liam Sherwin. All rights reserved. +# This file is part of the Spectrum Lighting Engine, licensed under the GPL v3.0 or later. +# See the LICENSE file for details. + +class_name UIColorPicker extends UIPanel +## UI Panel for showing a color picker + + +## The TextureRect for the Pad +@export var _pad: TextureRect + +## Crosshair for the Pad +@export var _crosshair: TextureRect + +## The VSlider for the Value +@export var _value_slider: VSlider + +## Mix Mode selection +@export var _mix_mode: OptionButton + + +## The current color +var _color: Color = Color.WHITE + +## Time since last call +var _last_call_time: int = 0 + +## 45 times per second +var _call_interval: float = 1.0 / 45.0 + +## All current selected fixtures +var _fixtures: Array + +## The refernce fixture +var _refernce_fixture: Fixture + + +## init +func _init() -> void: + super._init() + + _set_class_name("UIColorPicker") + Values.connect_to_selection_value("selected_fixtures", _on_selected_fixture_changed) + + +## Called when the color is changed, will only output CoreEngine.call_interval times per second to avoid overloading the server +func _update_programmer() -> void: + var current_time: float = Time.get_ticks_msec() / 1000.0 + + if current_time - _last_call_time >= _call_interval: + _last_call_time = current_time + Programmer.shortcut_set_color(_fixtures, _color, _mix_mode.selected) + + +## Called when the pad is resized +func _update_pad() -> void: + _crosshair.position = Vector2( + remap(_color.h, 0, 1, 0, _pad.size.x), + remap(_color.s, 0, 1, _pad.size.y, 0) + ) - _crosshair.size / 2 + _update_slider() + + +## Updates the slider and backgroud color +func _update_slider() -> void: + _pad.self_modulate = Color(_color.v, _color.v, _color.v) + _crosshair.modulate = Color(1-_color.v , 1-_color.v , 1-_color.v) + _value_slider.set_value_no_signal(_color.v) + + +## Called when the selected fixtures change +func _on_selected_fixture_changed(p_fixtures: Array) -> void: + if _refernce_fixture: + _refernce_fixture.remove_subscription(Fixture.RootZone, "ColorAdd_R", _on_fixture_red_changed) + _refernce_fixture.remove_subscription(Fixture.RootZone, "ColorAdd_G", _on_fixture_green_changed) + _refernce_fixture.remove_subscription(Fixture.RootZone, "ColorAdd_B", _on_fixture_blue_changed) + + _fixtures = p_fixtures + _refernce_fixture = p_fixtures[0] if p_fixtures else null + + if _refernce_fixture: + _refernce_fixture.subscribe_to_parameter(Fixture.RootZone, "ColorAdd_R", _on_fixture_red_changed) + _refernce_fixture.subscribe_to_parameter(Fixture.RootZone, "ColorAdd_G", _on_fixture_green_changed) + _refernce_fixture.subscribe_to_parameter(Fixture.RootZone, "ColorAdd_B", _on_fixture_blue_changed) + + +## Called when the red parameter is changed on the fixture +func _on_fixture_red_changed(p_red: float, _p_function: String, _p_override: bool) -> void: + _color.r = p_red + _update_pad() + + +## Called when the green parameter is changed on the fixture +func _on_fixture_green_changed(p_green: float, _p_function: String, _p_override: bool) -> void: + _color.g = p_green + _update_pad() + + +## Called when the blue parameter is changed on the fixture +func _on_fixture_blue_changed(p_blue: float, _p_function: String, _p_override: bool) -> void: + _color.b = p_blue + _update_pad() + + +## Called for all GUI imputs on the pad +func _on_texture_rect_gui_input(p_event: InputEvent) -> void: + if p_event is InputEventMouse and Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): + var mouse_pos: Vector2 = _pad.get_local_mouse_position().clamp(Vector2.ZERO, _pad.size) + + _color.h = remap(mouse_pos.x, 0, _pad.size.x, 0, 1) + _color.s = remap(mouse_pos.y, _pad.size.y, 0, 0, 1) + + _crosshair.position = mouse_pos - _crosshair.size / 2 + _update_programmer() + + +## Called when the pad value slider is moved +func _on_pad_value_slider_value_changed(p_value: float) -> void: + _color.v = p_value + _update_slider() + _update_programmer() diff --git a/panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd.uid b/panels/UIColorPicker/UIColorPicker.gd.uid similarity index 100% rename from panels/TO_BE_SORTED/ColorPicker/ColorPicker.gd.uid rename to panels/UIColorPicker/UIColorPicker.gd.uid diff --git a/panels/UIColorPicker/UIColorPicker.tscn b/panels/UIColorPicker/UIColorPicker.tscn new file mode 100644 index 00000000..880bbc65 --- /dev/null +++ b/panels/UIColorPicker/UIColorPicker.tscn @@ -0,0 +1,110 @@ +[gd_scene load_steps=8 format=3 uid="uid://dhp8aa07tyxbf"] + +[ext_resource type="StyleBox" uid="uid://daxhx5qr5qdeu" path="res://assets/styles/UIPanelBase.tres" id="1_vgwxr"] +[ext_resource type="Script" uid="uid://bltvgq1jnaqgt" path="res://panels/UIColorPicker/UIColorPicker.gd" id="2_ogrry"] +[ext_resource type="PackedScene" uid="uid://dr0kfolsg46yu" path="res://panels/UIPanel/PanelMenuBar.tscn" id="3_g582g"] +[ext_resource type="Texture2D" uid="uid://clt2djtbul2kf" path="res://assets/icons/ColorPalett.svg" id="4_ara1c"] +[ext_resource type="Texture2D" uid="uid://2p7ffb5yq0hx" path="res://assets/asset_scripts/color_picker.png" id="6_3j0ua"] +[ext_resource type="Texture2D" uid="uid://b7q628ocmror7" path="res://assets/icons/Crosshair.svg" id="7_b53g6"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7y88r"] +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[node name="UIColorPicker" type="PanelContainer" node_paths=PackedStringArray("_pad", "_crosshair", "_value_slider", "_mix_mode", "edit_controls", "menu_bar")] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme_override_styles/panel = ExtResource("1_vgwxr") +script = ExtResource("2_ogrry") +_pad = NodePath("VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer/Pad") +_crosshair = NodePath("VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer/Pad/Crosshair") +_value_slider = NodePath("VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer/PadValueSlider") +_mix_mode = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/PanelContainer/MixMode") +edit_controls = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/EditControls") +menu_bar = NodePath("VBoxContainer/PanelMenuBar") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="PanelMenuBar" parent="VBoxContainer" instance=ExtResource("3_g582g")] +layout_mode = 2 + +[node name="ComponentButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer" index="0"] +text = "ColorPicker" +icon = ExtResource("4_ara1c") + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer" index="2"] +layout_mode = 2 + +[node name="MixMode" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/PanelContainer"] +layout_mode = 2 +selected = 0 +item_count = 2 +popup/item_0/text = "Additive" +popup/item_0/id = 0 +popup/item_1/text = "Subtractive" +popup/item_1/id = 1 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Picker" type="PanelContainer" parent="VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/Picker"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer"] +clip_children = 1 +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_7y88r") + +[node name="Pad" type="TextureRect" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer"] +layout_mode = 2 +texture = ExtResource("6_3j0ua") +expand_mode = 1 + +[node name="Crosshair" type="TextureRect" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer/Pad"] +modulate = Color(0.0475577, 0.0475576, 0.0475576, 1) +layout_mode = 1 +offset_right = 24.0 +offset_bottom = 24.0 +texture = ExtResource("7_b53g6") +stretch_mode = 2 + +[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer2"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer"] +layout_mode = 2 +text = "Value" +horizontal_alignment = 1 + +[node name="PadValueSlider" type="VSlider" parent="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 3 +max_value = 1.0 +step = 0.001 +value = 1.0 +tick_count = 10 +ticks_on_borders = true + +[connection signal="gui_input" from="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer/Pad" to="." method="_on_texture_rect_gui_input"] +[connection signal="resized" from="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer/Pad" to="." method="_update_pad"] +[connection signal="value_changed" from="VBoxContainer/HBoxContainer/Picker/HBoxContainer/PanelContainer2/VBoxContainer/PadValueSlider" to="." method="_on_pad_value_slider_value_changed"] + +[editable path="VBoxContainer/PanelMenuBar"] diff --git a/panels/UIDesk/UIDesk.gd b/panels/UIDesk/UIDesk.gd index deaf4fd2..621b70de 100644 --- a/panels/UIDesk/UIDesk.gd +++ b/panels/UIDesk/UIDesk.gd @@ -318,12 +318,15 @@ func _on_edit_pressed() -> void: ## Called when the copy button is pressed func _on_copy_pressed() -> void: - var nodes_to_copy: Array = _selected_items.duplicate() + var nodes_to_copy: Array[UIDeskItemContainer] = _selected_items.duplicate() select_none() - for panel_container: Control in nodes_to_copy: + for panel_container: UIDeskItemContainer in nodes_to_copy: + var new_panel: UIPanel = UIDB.instance_panel(panel_container.get_panel().get_class_name()) + + new_panel.deserialize(panel_container.get_panel().serialize()) select_item(add_panel( - panel_container.get_panel().duplicate(), + new_panel, panel_container.position, panel_container.size )) diff --git a/panels/UIPanel/PanelMenuBar.tscn b/panels/UIPanel/PanelMenuBar.tscn index e193675e..2a1a6bfd 100644 --- a/panels/UIPanel/PanelMenuBar.tscn +++ b/panels/UIPanel/PanelMenuBar.tscn @@ -23,7 +23,7 @@ flat = true layout_mode = 2 size_flags_horizontal = 3 horizontal_scroll_mode = 3 -vertical_scroll_mode = 3 +vertical_scroll_mode = 0 [node name="EditControls" parent="HBoxContainer" instance=ExtResource("3_fnf3r")] layout_mode = 2 diff --git a/panels/UIPanel/UIPanel.gd b/panels/UIPanel/UIPanel.gd index f8ef6ef0..e76a80c1 100644 --- a/panels/UIPanel/UIPanel.gd +++ b/panels/UIPanel/UIPanel.gd @@ -91,15 +91,19 @@ func _init() -> void: ## Disables all the buttons in the given array -static func disable_button_array(buttons: Array[Button]) -> void: - for button: Button in buttons: - button.disabled = true +static func disable_button_array(p_buttons: Array[Button]) -> void: + set_button_array_enabled(p_buttons, true) ## Enables all the buttons in the given array -static func enable_button_array(buttons: Array[Button]) -> void: - for button: Button in buttons: - button.disabled = false +static func enable_button_array(p_buttons: Array[Button]) -> void: + set_button_array_enabled(p_buttons, false) + + +## Sets an array of buttons enabled or disabled +static func set_button_array_enabled(p_buttons: Array, p_disabled: bool) -> void: + for button: Button in p_buttons: + button.disabled = p_disabled ## Sets the move and resize handle @@ -308,7 +312,8 @@ func serialize() -> Dictionary: button_actions[button.name] = actions return super.serialize().merged({ - "button_actions": button_actions + "button_actions": button_actions, + "show_menu_bar": get_menu_bar_visible() }) @@ -316,6 +321,8 @@ func serialize() -> Dictionary: func deserialize(p_serialized_data: Dictionary) -> void: super.deserialize(p_serialized_data) + set_menu_bar_visible(type_convert(p_serialized_data.get("show_menu_bar", get_menu_bar_visible()), TYPE_BOOL)) + var button_actions: Dictionary = type_convert(p_serialized_data.get("button_actions"), TYPE_DICTIONARY) for button_name: Variant in button_actions.keys(): diff --git a/panels/UIProgrammer/ParameterController/ParameterController.gd b/panels/UIProgrammer/ParameterController/ParameterController.gd index f6e16fa9..4a8bdf11 100644 --- a/panels/UIProgrammer/ParameterController/ParameterController.gd +++ b/panels/UIProgrammer/ParameterController/ParameterController.gd @@ -7,13 +7,16 @@ class_name ParameterController extends PanelContainer ## Emitted when the value is changed -signal value_changed(parameter: String, function: String, value: float) +signal value_changed(value: float) -## Emitted when the erase is pressed -signal erase_pressed(parameter: String) +## Emitted when a function is selected +signal function_selected(function: String) + +## Emitted when an override is added to the refernce fixture +signal fixture_override_added() -## Emitted when the random button is pressed -signal random_pressed(parameter: String, function: String) +## Emitted when the erase is pressed +signal erase_pressed() ## FunctionList node for displaying all parameter functions @@ -23,17 +26,30 @@ signal random_pressed(parameter: String, function: String) @export var _slider: VSlider ## The NameLabel for the parameter -@export var _name_label: Label - -## The zone label -@export var _zone_label: Label +@export var _title_button: Button ## TitleBar PanelContainer @export var _title_bar: PanelContainer +## The Label to show the value +@export var _value_label: Label + +## The GridContainer to contain all preset buttons +@export var _preset_container: GridContainer + + +## Font size in PX for the preset buttons +const PRESET_BUTTON_FONT_SIZE: int = 12 + +## Number of Intensity percentage presets to show +const NUM_INTENSITY_PRESETS: int = 5 + + +## The Fixture this ParameterControler is basing its value updates off +var _refernce_fixture: Fixture -## The category of this parameter -var category: String = "" +## Current override state of the refernce fixture +var _override_state: bool = false ## The parameter this ParameterController controls var _parameter: String = "" @@ -50,48 +66,61 @@ var _title_stylebox: StyleBoxFlat = null ## Default color of this ParameterController var _default_color: Color = Color(0.129, 0.129, 0.129) -## Override color of this ParameterController -var _override_color: Color = Color(1, 0.518, 0) - +## Ready func _ready() -> void: _title_stylebox = _title_bar.get_theme_stylebox("panel").duplicate() _title_bar.add_theme_stylebox_override("panel", _title_stylebox) + _default_color = _title_stylebox.get_bg_color() ## Sets the parameter -func set_parameter(parameter: String) -> void: - _parameter = parameter - _name_label.text = parameter +func set_parameter(p_parameter: String) -> void: + _parameter = p_parameter + _title_button.set_text(_parameter) ## Sets the zone -func set_zone(zone: String) -> void: - _zone = zone - _zone_label.text = zone - - -## Gets the zone -func get_zone() -> String: - return _zone - - -## Sets the state of the override background -func set_override_bg(state: bool) -> void: - if state: - _title_stylebox.bg_color = _override_color - else: - _title_stylebox.bg_color = _default_color +func set_zone(p_zone: String) -> void: + _zone = p_zone ## Sets the value of this parameter, no signal func set_value(value: float) -> void: _slider.set_value_no_signal(value) + _set_label_value(value) + + +## Setst the value of this ParameterControler to the current fixture value +func set_value_fixture() -> void: + if not _refernce_fixture: + return + + set_value(_refernce_fixture.get_current_value(_zone, _parameter)) + set_function(_refernce_fixture.get_current_function(_zone, _parameter)) ## Sets the current selected function func set_function(function: String) -> void: _function_list.select(_functions.find(function)) + + if is_instance_valid(_refernce_fixture): + load_presets() + else: + load_presets.call_deferred() + + +## Sets the state of the override background +func set_override_bg(p_state: bool) -> void: + var target: Color + + if p_state: + target = ThemeManager.Colors.Statuses.ProgrammerOverride + else: + target = _default_color + + get_tree().create_tween().tween_property(_title_stylebox, "bg_color", target, ThemeManager.Constants.Times.InterfaceFadeFast) + _override_state = p_state ## Adds an item to the function list @@ -106,31 +135,146 @@ func has_function(function: String) -> bool: return _functions.has(function) +## Gets the parameter +func get_parameter() -> String: + return _parameter + + +## Gets the zone +func get_zone() -> String: + return _zone + + +## Gets the current value +func get_value() -> float: + return _slider.value + + ## Gets the current selected function func get_function() -> String: - return _function_list.get_item_text(_function_list.get_selected_id()) + var selected: int = _function_list.get_selected_id() == -1 + + if selected: + return "" + else: + return _function_list.get_item_text(selected) + + +## Gets the refernce fixture +func get_refernce_fixture() -> Fixture: + return _refernce_fixture + + +## Subscribes to the given fixture +func subscribe(p_fixture: Fixture) -> void: + if is_instance_valid(_refernce_fixture): + remove_subscription() + + _refernce_fixture = p_fixture + _refernce_fixture.subscribe_to_parameter(_zone, _parameter, _on_fixture_value_changed) + + _override_state = _refernce_fixture.has_override(_zone, _parameter) + + +## Removes the subscription to the fixture +func remove_subscription() -> void: + if not is_instance_valid(_refernce_fixture): + return + + _refernce_fixture.remove_subscription(_zone, _parameter, _on_fixture_value_changed) + + +## Loads all the presets based on the current selected function +func load_presets() -> void: + for old_preset: Control in _preset_container.get_children(): + old_preset.queue_free() + + if not _refernce_fixture: + return + + match _refernce_fixture.get_function_control_type(_zone, _parameter, get_function()): + Fixture.ControlType.INTENSITY, Fixture.ControlType.VALUE: + var presets: Array[float] = _generate_preset_values(NUM_INTENSITY_PRESETS) + + for value: float in presets: + _add_preset_button(value, str(int(value * 100)) + "%") ## Clears this ParameterController func clear() -> void: _functions.clear() _function_list.clear() - _slider.set_value_no_signal(0) + + remove_subscription() set_override_bg(false) + load_presets() -## Called when the slider value changes -func _on_v_slider_value_changed(value: float) -> void: - if _parameter: - value_changed.emit(_zone, _parameter, get_function(), value) +## Returns an array with preset values +func _generate_preset_values(p_num_presets: int) -> Array[float]: + if p_num_presets <= 0: + return [] + if p_num_presets == 1: + return [1.0] + + var presets: Array[float] = [] + var step: float = 1.0 / float(p_num_presets - 1) + + for i in range(p_num_presets): + presets.append(i * step) + + return presets + + +## Adds a preset value button +func _add_preset_button(p_value: float, p_text: String) -> void: + var new_button: Button = Button.new() + + new_button.set_text(p_text) + new_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL + new_button.pressed.connect(_set_value.bind(p_value)) + + _preset_container.add_child(new_button) + + +## Sets and emits the value +func _set_value(p_value: float) -> void: + value_changed.emit(p_value) + _set_label_value(p_value) + + +## Sets the value on the label +func _set_label_value(p_value: float) -> void: + _value_label.set_text(str(snappedf(p_value * 100, 0.1)) + "%") + + +## Called when the value is changed on the selected fixture +func _on_fixture_value_changed(p_value: float, p_function: String, p_override: bool) -> void: + set_value(p_value) + set_function(p_function) + + if p_override != _override_state and p_override: + fixture_override_added.emit() + _override_state = p_override ## Called when the Erace button is pressed func _on_erase_pressed() -> void: - erase_pressed.emit(_zone, _parameter) + erase_pressed.emit() + + +## Called when the default button is pressed +func _on_default_pressed() -> void: + _set_value(_refernce_fixture.get_default(_zone, _parameter, get_function())) + + +## Called when a function is selected +func _on_function_list_item_selected(index: int) -> void: + function_selected.emit(get_function()) -## Called when the Random button is pressed -func _on_random_pressed() -> void: - random_pressed.emit(_zone, _parameter, get_function()) +## Called for each GUI input on the value label +func _on_value_gui_input(p_event: InputEvent) -> void: + if p_event is InputEventMouseButton and p_event.button_index == MOUSE_BUTTON_LEFT and p_event.is_pressed(): + _set_value(get_value()) diff --git a/panels/UIProgrammer/ParameterController/ParameterController.tscn b/panels/UIProgrammer/ParameterController/ParameterController.tscn index 12b35605..fadd2c12 100644 --- a/panels/UIProgrammer/ParameterController/ParameterController.tscn +++ b/panels/UIProgrammer/ParameterController/ParameterController.tscn @@ -3,9 +3,8 @@ [ext_resource type="Theme" uid="uid://cyua45ur0ijqo" path="res://assets/Main.theme" id="1_7me6c"] [ext_resource type="StyleBox" uid="uid://bxwylw4uk8cyk" path="res://assets/styles/ClassSettingsBG.tres" id="2_56yd5"] [ext_resource type="Script" uid="uid://01ronclpv10b" path="res://panels/UIProgrammer/ParameterController/ParameterController.gd" id="3_437gf"] -[ext_resource type="Texture2D" uid="uid://dyd2xybu6duv4" path="res://assets/icons/NewWindow.svg" id="4_sjuwc"] +[ext_resource type="FontFile" uid="uid://crlak6jhg5jy2" path="res://assets/font/RubikMonoOne-Regular.ttf" id="4_33gbp"] [ext_resource type="Texture2D" uid="uid://dlikb8or3xepr" path="res://assets/icons/Reset.svg" id="4_ukk8e"] -[ext_resource type="Texture2D" uid="uid://c0lqh3h58ht3c" path="res://assets/icons/Random.svg" id="5_sjuwc"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sjuwc"] content_margin_left = 5.0 @@ -18,21 +17,36 @@ corner_radius_top_right = 5 corner_radius_bottom_right = 5 corner_radius_bottom_left = 5 -[sub_resource type="LabelSettings" id="LabelSettings_sjuwc"] -font_size = 10 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3ced7"] +content_margin_left = 5.0 +content_margin_top = 5.0 +content_margin_right = 5.0 +content_margin_bottom = 5.0 +bg_color = Color(0.12156863, 0.12156863, 0.12156863, 1) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="LabelSettings" id="LabelSettings_qnt7p"] +font = ExtResource("4_33gbp") +font_size = 26 -[node name="ParameterController" type="PanelContainer" node_paths=PackedStringArray("_function_list", "_slider", "_name_label", "_zone_label", "_title_bar")] +[node name="ParameterController" type="PanelContainer" node_paths=PackedStringArray("_function_list", "_slider", "_title_button", "_title_bar", "_value_label", "_preset_container")] custom_minimum_size = Vector2(219, 0) -offset_right = 304.0 -offset_bottom = 425.0 +offset_right = 402.0 +offset_bottom = 319.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 theme = ExtResource("1_7me6c") theme_override_styles/panel = ExtResource("2_56yd5") script = ExtResource("3_437gf") -_function_list = NodePath("VBoxContainer/PanelContainer2/HBoxContainer/OptionButton") +_function_list = NodePath("VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer/FunctionList") _slider = NodePath("VBoxContainer/PanelContainer3/HBoxContainer/VSlider") -_name_label = NodePath("VBoxContainer/TitleBar/HBoxContainer/VBoxContainer/NameLabel") -_zone_label = NodePath("VBoxContainer/TitleBar/HBoxContainer/VBoxContainer/ZoneLabel") +_title_button = NodePath("VBoxContainer/TitleBar/HBoxContainer/PanelContainer2/TitleButton") _title_bar = NodePath("VBoxContainer/TitleBar") +_value_label = NodePath("VBoxContainer/TitleBar/HBoxContainer/PanelContainer/Value") +_preset_container = NodePath("VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer/ScrollContainer/PresetContainer") [node name="VBoxContainer" type="VBoxContainer" parent="."] layout_mode = 2 @@ -44,26 +58,29 @@ theme_override_styles/panel = SubResource("StyleBoxFlat_sjuwc") [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TitleBar"] layout_mode = 2 -[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TitleBar/HBoxContainer"] +[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/TitleBar/HBoxContainer"] layout_mode = 2 -size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_3ced7") -[node name="NameLabel" type="Label" parent="VBoxContainer/TitleBar/HBoxContainer/VBoxContainer"] +[node name="TitleButton" type="Button" parent="VBoxContainer/TitleBar/HBoxContainer/PanelContainer2"] layout_mode = 2 -size_flags_horizontal = 3 +theme_override_colors/font_color = Color(1, 1, 1, 1) text = "Dimmer" -text_overrun_behavior = 3 +flat = true +alignment = 0 +icon_alignment = 2 -[node name="ZoneLabel" type="Label" parent="VBoxContainer/TitleBar/HBoxContainer/VBoxContainer"] +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/TitleBar/HBoxContainer"] layout_mode = 2 -size_flags_horizontal = 3 -text = "Dimmer" -label_settings = SubResource("LabelSettings_sjuwc") -text_overrun_behavior = 3 +size_flags_horizontal = 10 +theme_override_styles/panel = SubResource("StyleBoxFlat_3ced7") -[node name="Erase" type="Button" parent="VBoxContainer/TitleBar/HBoxContainer"] +[node name="Value" type="Label" parent="VBoxContainer/TitleBar/HBoxContainer/PanelContainer"] layout_mode = 2 -icon = ExtResource("4_sjuwc") +size_flags_horizontal = 10 +mouse_filter = 0 +text = "100%" +label_settings = SubResource("LabelSettings_qnt7p") [node name="PanelContainer3" type="PanelContainer" parent="VBoxContainer"] layout_mode = 2 @@ -84,54 +101,48 @@ value = 1.0 tick_count = 20 ticks_on_borders = true -[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer"] +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 -[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/PanelContainer"] +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 +size_flags_vertical = 3 -[node name="GridContainer" type="GridContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/PanelContainer/ScrollContainer"] -visible = false -layout_mode = 2 -size_flags_horizontal = 3 -columns = 2 - -[node name="Button" type="Button" parent="VBoxContainer/PanelContainer3/HBoxContainer/PanelContainer/ScrollContainer/GridContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer"] layout_mode = 2 size_flags_horizontal = 3 -toggle_mode = true -button_pressed = true -text = "Open" -[node name="Button5" type="Button" parent="VBoxContainer/PanelContainer3/HBoxContainer/PanelContainer/ScrollContainer/GridContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="PresetContainer" type="GridContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer/ScrollContainer"] layout_mode = 2 size_flags_horizontal = 3 -toggle_mode = true -text = "Close" +columns = 5 -[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer"] +[node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer"] layout_mode = 2 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer2"] +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2"] layout_mode = 2 -[node name="Erace" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +[node name="Erace" type="Button" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer"] layout_mode = 2 +size_flags_horizontal = 0 icon = ExtResource("4_ukk8e") -[node name="Random" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +[node name="Default" type="Button" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer"] +custom_minimum_size = Vector2(40, 0) layout_mode = 2 -icon = ExtResource("5_sjuwc") +theme_override_fonts/font = ExtResource("4_33gbp") +text = "d" -[node name="OptionButton" type="OptionButton" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +[node name="FunctionList" type="OptionButton" parent="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 text_overrun_behavior = 3 -[connection signal="value_changed" from="VBoxContainer/PanelContainer3/HBoxContainer/VSlider" to="." method="_on_v_slider_value_changed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer2/HBoxContainer/Erace" to="." method="_on_erase_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer2/HBoxContainer/Random" to="." method="_on_random_pressed"] +[connection signal="gui_input" from="VBoxContainer/TitleBar/HBoxContainer/PanelContainer/Value" to="." method="_on_value_gui_input"] +[connection signal="value_changed" from="VBoxContainer/PanelContainer3/HBoxContainer/VSlider" to="." method="_set_value"] +[connection signal="pressed" from="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer/Erace" to="." method="_on_erase_pressed"] +[connection signal="pressed" from="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer/Default" to="." method="_on_default_pressed"] +[connection signal="item_selected" from="VBoxContainer/PanelContainer3/HBoxContainer/VBoxContainer/PanelContainer2/HBoxContainer/FunctionList" to="." method="_on_function_list_item_selected"] diff --git a/panels/UIProgrammer/UIProgrammer.gd b/panels/UIProgrammer/UIProgrammer.gd index afae0bd8..f42fdc12 100644 --- a/panels/UIProgrammer/UIProgrammer.gd +++ b/panels/UIProgrammer/UIProgrammer.gd @@ -6,335 +6,791 @@ class_name UIProgrammer extends UIPanel ## Programmer to adust the settings and paramiters of Fixtures -## DimmerTabButton Button -@export var _dimmer_tab_button: Button +## Emitted when the number of controlers per page is changed +signal controlers_per_page_changed(controlers: int) -## ColorTabButton Button -@export var _color_tab_button: Button -## GoboTabButton Button -@export var _gobo_tab_button: Button +## The default number of ParameterControlers per page +@export var default_controlers_per_page: int = 4 -## PositionTabButton Button -@export var _position_tab_button: Button +## Min size X for tab buttons +@export var tab_button_min_size: int = 80 -## BeamTabButton Button -@export var _beam_tab_button: Button -## FocusTabButton Button -@export var _focus_tab_button: Button +@export_group("Nodes") -## ShapersTabButton Button -@export var _shapers_tab_button: Button +## The HBoxContainer for all tab buttons +@export var tab_button_container: HBoxContainer -## ControlTabButton Button -@export var _control_tab_button: Button +## The HBoxContainer for all layer buttons +@export var layer_button_container: HBoxContainer ## The HBoxContainer for ParameterControllers -@export var _parameter_container: HBoxContainer - -## The ButtonGroup for all the tab buttons -@export var button_group: ButtonGroup - -## The SaveToScene Button -@export var save_to_scene: Button +@export var controler_container: HBoxContainer ## Zone select button @export var zone_select: OptionButton +## Random Mode button +@export var random_mode: OptionButton -## All sorted tab buttons -@onready var _tab_buttons: Dictionary[String, Button] = { - "Dimmer": _dimmer_tab_button, - "Color": _color_tab_button, - "Gobo": _gobo_tab_button, - "Position": _position_tab_button, - "Beam": _beam_tab_button, - "Focus": _focus_tab_button, - "Shapers": _shapers_tab_button, - "Control": _control_tab_button -} +## The Template placeholder item +@export var template_placeholder: PanelContainer -## The RefMap for tab buttons -@onready var _button_map: RefMap = RefMap.from(_tab_buttons) +## The OptionButton for selecting the current page +@export var page_select_option: OptionButton -## The current tab -var _current_tab: String = "Dimmer" +## The button to go to the previous page +@export var prev_page_button: Button -## All the currently selected fixtures -var _fixtures: Array[Fixture] +## The button to go to the next page +@export var next_page_button: Button + + +## RefMap for Programmer.Category: Button +var _tab_buttons: RefMap = RefMap.new() + +## RefMap for Programmer.Layer: Button +var _layer_buttons: RefMap = RefMap.new() + +## The ButtonGroup for all tab buttons +var _tab_button_group: ButtonGroup = ButtonGroup.new() + +## The ButtonGroup for all layer buttons +var _layer_button_group: ButtonGroup = ButtonGroup.new() + +## Contains all placeholder items +var _placeholder_items: Array[Control] + +## Contains all ParameterControllers +var _controlers: Array[ParameterController] -## The current selected fixture zone -var _current_zone: String = "" +## Stores all pages by category { Category: { "zone": { index: { entry... } } } +var _pages: Dictionary[Programmer.Category, Dictionary] -## All the zones in the current selected fixtures -var _selected_fixture_zones: Array[String] = [] +## Stores all parameters that are currenty being shown by page by zone +var _shown_parameters: Dictionary[Programmer.Category, Dictionary] -## RandomMode for random values -var _random_mode: Programmer.RandomMode = Programmer.RandomMode.All +## State for all category overrides colors +var _category_overrides: Dictionary[Programmer.Category, bool] -## All the current ParameterControllers -var _parameter_controllers: Dictionary[String, Dictionary] = {} +## Number of controlers per page +var _controlers_per_page: int = default_controlers_per_page -## All the current ParameterControllers sorted by categorie -var _controller_categories: Dictionary[String, Array] = {} +## The current category +var _current_category: Programmer.Category = Programmer.Category.DIMMER -## All current override values sorted by catigory -var _current_values: Dictionary[String, Dictionary] = {} +## The current zone +var _current_zone: String = Fixture.RootZone -## All the visible ParameterControllers -var _visible_parameter_controllers: Array[ParameterController] = [] +## Current page number +var _current_page: int = 0 -## Override color of this ParameterController -var _tab_button_override_color: Color = Color(1, 0.518, 0) +## The current programmer layer +var _current_layer: Programmer.Layer = Programmer.Layer.VALUE + +## The OptionButton index of each zone +var _zone_indexes: Dictionary[String, int] = {} + +## All zones that have override values in them +var _zones_with_overrides: Dictionary[String, Variant] + +## Stores all fixture overrides: { "zone": { Programmer.Category: { "parameter": null } } } +var _overrides_by_parameter: Dictionary[String, Dictionary] + +## All the currently selected fixtures +var _fixtures: Array ## init func _init() -> void: super._init() - _set_class_name("UIProgrammer") + + settings_manager.register_setting("ControlersPerPage", Data.Type.INT, set_controlers_per_page, get_controlers_per_page, [controlers_per_page_changed]).set_min_max(1, 10) ## ready func _ready() -> void: Values.connect_to_selection_value("selected_fixtures", _on_selected_fixtures_changed) - button_group.pressed.connect(_on_tab_button_pressed) + Programmer.cleared.connect(_clear_overrides) + + template_placeholder.get_parent().remove_child(template_placeholder) - Programmer.cleared.connect(_clear_override_bg) + _create_controlers_and_placeholders() + _create_tab_buttons() + _create_layer_buttons() -## Updates the tabs of categorys displayed to the user -func _update_categorys(new_fixtures: Array) -> void: - var controlers_to_hide: Array[ParameterController] = _visible_parameter_controllers.duplicate() - var tabs_to_disable: Array[String] = _tab_buttons.keys() - var new_visible_controllers: Array[ParameterController] = [] - var zone_select_index: int = 1 - var zone_to_select: int = 0 - - _current_values.clear() - zone_select.clear() - _selected_fixture_zones = [] +## Sets the category to display +func set_category(p_category: Programmer.Category) -> void: + _current_category = p_category + set_page(_current_page) + + +## Sets the page number +func set_page(p_page: int) -> void: + _current_page = abs(clamp(p_page, 0, get_page_count() - 1)) + page_select_option.select(_current_page) + _update_page() + + +## Sets the selected zone +func set_zone(p_zone: String) -> void: + _current_zone = p_zone + _update_page() + _update_zone_override() + + +## Sets the layer +func set_layer(p_layer: Programmer.Layer) -> void: + _current_layer = p_layer + + +## Sets the number of controlers per page +func set_controlers_per_page(p_per_page: int) -> void: + if p_per_page == _controlers_per_page or p_per_page < 1: + return + + _controlers_per_page = p_per_page + _create_controlers_and_placeholders() + _update_page() + + +## Gets the number of controlers per page +func get_controlers_per_page() -> int: + return _controlers_per_page + + +## Gets the total number of pages +func get_page_count() -> int: + var items: int = _pages.get(_current_category, {}).get(_current_zone, []).size() + return int(ceil(float(items) / float(_controlers_per_page))) + + +## Seralizes this UIProgrammer +func serialize() -> Dictionary: + return super.serialize().merged({ + "controlers_per_page": get_controlers_per_page() + }) + + +## Deserializes this UIProgrammer +func deserialize(p_serialized_data: Dictionary) -> void: + super.deserialize(p_serialized_data) - zone_select.add_item("All", 0) - zone_select.add_separator() - zone_select.select(0) + set_controlers_per_page(type_convert(p_serialized_data.get("controlers_per_page", _controlers_per_page), TYPE_INT)) + + +## Creates the default set of placeholders and controlers +func _create_controlers_and_placeholders() -> void: + for placeholder: PanelContainer in _placeholder_items: + placeholder.queue_free() - for fixture: Fixture in new_fixtures: - var zones: Array[String] = fixture.get_zones() + for controler: ParameterController in _controlers: + controler.queue_free() + + _placeholder_items.clear() + _controlers.clear() + + for index: int in range(0, _controlers_per_page): + var placeholder: PanelContainer = template_placeholder.duplicate() + placeholder.get_child(0).set_text(str(index + 1)) - if _current_zone not in zones: - _current_zone = "" + var controler: ParameterController = preload("res://panels/UIProgrammer/ParameterController/ParameterController.tscn").instantiate() + controler.hide() - Utils.sort_text_and_numbers(zones) - Utils.array_move_to_start(zones, "root") + controler_container.add_child(placeholder) + controler_container.add_child(controler) - for zone: String in zones: - if zone not in _selected_fixture_zones: - zone_select.add_item(zone, zone_select_index) - _selected_fixture_zones.append(zone) - - if zone == _current_zone: - zone_to_select = zone_select_index - - zone_select_index += 1 + controler.value_changed.connect(_on_controler_value_changed.bind(controler)) + controler.function_selected.connect(_on_controler_function_selected.bind(controler)) + controler.fixture_override_added.connect(_on_controler_fixture_override_added.bind(controler)) + controler.erase_pressed.connect(_on_controler_erase_pressed.bind(controler)) + + _placeholder_items.append(placeholder) + _controlers.append(controler) + + +## Creates the tab buttons +func _create_tab_buttons() -> void: + for category: Programmer.Category in Programmer.Category.values(): + var tab_button: Button = Button.new() + + tab_button.set_toggle_mode(true) + tab_button.set_disabled(true) + tab_button.set_button_group(_tab_button_group) + + tab_button.set_text(Programmer.get_category_as_string(category).capitalize()) + tab_button.set_custom_minimum_size(Vector2(tab_button_min_size, 0)) + + tab_button.pressed.connect(set_category.bind(category)) + _tab_buttons.map(category, tab_button) + _category_overrides[category] = false + + tab_button_container.add_child(tab_button) + + _tab_buttons.left(Programmer.Category.DIMMER).set_pressed_no_signal(true) + + +## Creates the layer buttons +func _create_layer_buttons() -> void: + for layer: Programmer.Layer in Programmer.Layer.values(): + var layer_button: Button = Button.new() + + layer_button.set_toggle_mode(true) + layer_button.set_disabled(true) + layer_button.set_button_group(_layer_button_group) + + layer_button.set_text(Programmer.Layer.keys()[layer].capitalize()) + layer_button.set_custom_minimum_size(Vector2(tab_button_min_size, 0)) + + layer_button.pressed.connect(set_layer.bind(layer)) + _layer_buttons.map(layer, layer_button) + + layer_button_container.add_child(layer_button) + + _layer_buttons.left(Programmer.Layer.VALUE).set_pressed_no_signal(true) + + +## Updates the tabs of categorys displayed to the user +func _update_categorys(p_fixtures: Array) -> void: + var zones: Dictionary = {Fixture.RootZone: null} + var used_categories: Dictionary[int, Variant] + + _pages.clear() + zone_select.clear() + + _zone_indexes.clear() + _zones_with_overrides.clear() + _shown_parameters.clear() + + _fixtures = p_fixtures + _current_zone = Fixture.RootZone + + for fixture: Fixture in p_fixtures: + for zone: String in fixture.get_zones(): var categories: Dictionary = fixture.get_parameter_categories(zone) - var current_values: Dictionary = fixture.get_all_values() - current_values.merge(fixture.get_all_override_values(), true) + if not zones.has(zone): + zones[zone] = null - for parameter: String in categories: - var controller: ParameterController - if _parameter_controllers.get_or_add(zone, {}).has(parameter): - controller = _parameter_controllers[zone][parameter] - - if fixture not in _fixtures: - for function: String in fixture.get_parameter_functions(zone, parameter): - if not controller.has_function(function): - controller.add_function(function) - - tabs_to_disable.erase(controller.category) - _button_map.left(controller.category).disabled = false - - if controller.category == _current_tab: - controller.show() - - if controller not in new_visible_controllers: - new_visible_controllers.append(controller) - - controlers_to_hide.erase(controller) + for parameter: String in categories.keys(): + var category: int = Programmer.get_category_from_string(categories[parameter]) - else: - controller = load("uid://bfk7wccmsndfr").instantiate() - var category = categories[parameter] - - if category not in _tab_buttons.keys(): - category = "Control" - - controller.set_parameter(parameter) - controller.set_zone(zone) - controller.category = category - - for function: String in fixture.get_parameter_functions(zone, parameter): - controller.add_function(function) - - _parameter_controllers.get_or_add(zone, {})[parameter] = controller - _controller_categories.get_or_add(category, []).append(controller) - new_visible_controllers.append(controller) - - tabs_to_disable.erase(category) - _button_map.left(category).disabled = false - - if category != _current_tab: - controller.hide() - - _parameter_container.add_child(controller) - controller.value_changed.connect(_on_controller_value_changed.bind(controller)) - controller.random_pressed.connect(_on_controller_random_pressed.bind(controller)) - controller.erase_pressed.connect(_on_controller_erase_pressed.bind(controller)) + _display_parameter(fixture, zone, parameter, category) - var current: Dictionary = current_values.get(zone, {}).get(parameter, {}) - if current: - controller.set_value(current.value) - controller.set_function(current.function) - controller.set_override_bg(true) - _set_tab_button_override(_tab_buttons[controller.category], true) + if fixture.has_override(zone, parameter): + used_categories[category] = true + _zones_with_overrides[zone] = true - _current_values.get_or_add(controller.category, {})[parameter] = { - "value": current.value, - "function": current.function - } + _overrides_by_parameter.get_or_add(zone, {}).get_or_add(category, {})[parameter] = null - for controller: ParameterController in controlers_to_hide: - controller.clear() - controller.hide() + for category: Programmer.Category in _pages.keys(): + _set_category_override(category, used_categories.has(category)) - for tab_name: String in tabs_to_disable: - _button_map.left(tab_name).disabled = true - _set_tab_button_override(_button_map.left(tab_name), false) + zone_select.add_item(Fixture.RootZone, 0) + zone_select.set_item_icon(0, preload("res://assets/icons/Zone.svg")) + _zone_indexes[Fixture.RootZone] = 0 - _visible_parameter_controllers = new_visible_controllers - _fixtures.assign(new_fixtures) + var sorted_zones: Array = Utils.sort_text_and_numbers(zones.keys()) + for index: int in range(0, sorted_zones.size()): + var zone: String = sorted_zones[index] + var idx: int = index + 1 + + if _zone_indexes.has(zone): + continue + + zone_select.add_item(zone, idx) + zone_select.set_item_icon(idx, preload("res://assets/icons/Zone.svg")) + + zone_select.get_popup().set_item_icon_modulate(idx, ThemeManager.Colors.Statuses.ProgrammerOverride if _zones_with_overrides.has(zone) else Color.WHITE) + _zone_indexes[zone] = idx + + _update_page() + _update_buttons() + _update_zone_override() + + +## Displays a parameter controler for the given fixture +func _display_parameter(p_fixture: Fixture, p_zone: String, p_parameter: String, p_category: Programmer.Category) -> void: + if _shown_parameters.get(p_category, {}).get(p_zone, {}).has(p_parameter): + return + + var page: Dictionary = _pages.get_or_add(p_category, {}).get_or_add(p_zone, {}) + var index: int = Programmer.get_parameter_index(p_category, p_parameter) + + if index == -1: + index = _get_next_index(page) - if zone_to_select == 0: - _current_zone = "" + elif page.has(index): + page[_get_next_index(page)] = page[index] + page.erase(index) - zone_select.select(zone_to_select) - _update_zone_filter() + page[index] = { + "fixture": p_fixture, + "parameter": p_parameter, + } + + _shown_parameters.get_or_add(p_category, {}).get_or_add(p_zone, {})[p_parameter] = null -## Shows or hides ParameterControllers based on the current zone filter -func _update_zone_filter() -> void: - for controller: ParameterController in _visible_parameter_controllers: - if controller.category != _current_tab: - continue +## Gets the next empty index starting from 0 +func _get_next_index(p_dict: Dictionary) -> int: + var end_index: int = 0 + + while p_dict.has(end_index): + end_index += 1 + + return end_index + + +## Updates what ParameterControlers are shown on the current page +func _update_page() -> void: + _update_page_buttons() + + for index: int in range(0, _controlers_per_page): + var entrys_to_show: Dictionary = _pages.get(_current_category, {}).get(_current_zone, {}) + var sorted_order: Array = entrys_to_show.keys() + var page_offset: int = _controlers_per_page * _current_page - if controller.get_zone() == _current_zone or _current_zone == "": - controller.show() + sorted_order.sort() - elif controller.get_zone() != _current_zone: - controller.hide() + if (sorted_order.size() - page_offset) > index: + _placeholder_items[index].hide() + _controlers[index].show() + + var entry: Dictionary = entrys_to_show[sorted_order[index + page_offset]] + _update_controler(_controlers[index], entry.fixture, _current_zone, entry.parameter) + + else: + _controlers[index].hide() + _placeholder_items[index].show() + + _controlers[index].remove_subscription() -## Clears the override BG on all of the ParameterControllers and tab buttons -func _clear_override_bg() -> void: - for zone: String in _parameter_controllers.keys(): - for controller: ParameterController in _parameter_controllers[zone].values(): - controller.set_override_bg(false) +## Updated a ParameterControler with the given fixture, zone, and parameter +func _update_controler(p_controler: ParameterController, p_fixture: Fixture, p_zone: String, p_parameter: String) -> void: + var old_value: float = p_controler.get_value() + p_controler.clear() - for tab_button: Button in _tab_buttons.values(): - _set_tab_button_override(tab_button, false) + for function: String in p_fixture.get_parameter_functions(p_zone, p_parameter): + p_controler.add_function(function) + + p_controler.set_zone(p_zone) + p_controler.set_parameter(p_parameter) + p_controler.set_function(p_fixture.get_current_function(p_zone, p_parameter)) + + var tween: Tween = get_tree().create_tween() + tween.tween_method(p_controler.set_value, old_value, p_fixture.get_current_value(p_zone, p_parameter), 0.2).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_QUINT) + + p_controler.set_override_bg(p_fixture.has_override(p_zone, p_parameter)) + p_controler.subscribe(p_fixture) -## Sets the override state on the given tab button -func _set_tab_button_override(tab_button: Button, state: bool) -> void: - tab_button.begin_bulk_theme_override() - if state: - tab_button.add_theme_color_override("font_outline_color", _tab_button_override_color) - tab_button.add_theme_color_override("font_color", _tab_button_override_color) - tab_button.add_theme_color_override("font_focus_color", _tab_button_override_color) - tab_button.add_theme_color_override("font_pressed_color", _tab_button_override_color) - else: - tab_button.remove_theme_color_override("font_outline_color") - tab_button.remove_theme_color_override("font_color") - tab_button.remove_theme_color_override("font_focus_color") - tab_button.remove_theme_color_override("font_pressed_color") - tab_button.end_bulk_theme_override() +## Updates tab buttons to enable ones with parameters +func _update_buttons() -> void: + for category: Programmer.Category in _tab_buttons.get_left(): + _tab_buttons.left(category).set_disabled(not _pages.has(category)) + + var has_fixtures: bool = bool(_fixtures.size()) + + zone_select.set_disabled(not has_fixtures) + page_select_option.set_disabled(not has_fixtures) + random_mode.set_disabled(not has_fixtures) + + set_button_array_enabled(_layer_buttons.get_right(), not has_fixtures) + + if not has_fixtures: + next_page_button.set_disabled(true) + prev_page_button.set_disabled(true) -## Called when the fixture selection changes -func _on_selected_fixtures_changed(fixtures: Array) -> void: - if visible and fixtures != _fixtures: - _update_categorys(fixtures) - - save_to_scene.disabled = fixtures == [] +## Updates the page buttons +func _update_page_buttons() -> void: + prev_page_button.set_disabled(_current_page == 0) + next_page_button.set_disabled(_current_page + 1 == get_page_count()) + + if get_page_count() == page_select_option.get_item_count(): + return + + page_select_option.clear() + + for index in range(0, get_page_count()): + page_select_option.add_item(str(index)) + + page_select_option.select(_current_page) + + +## Updates the override hilight on the zone select button +func _update_zone_override() -> void: + _set_button_override(zone_select, _zones_with_overrides.has(_current_zone)) -## Called when any one of the tab buttons is pressed -func _on_tab_button_pressed(button: Button) -> void: - if _button_map.right(button) == _current_tab: +## Enabled or disabled override on a Category +func _set_category_override(p_category: Programmer.Category, p_override: bool) -> void: + if _category_overrides[p_category] == p_override: return - for controller: ParameterController in _controller_categories.get(_current_tab, []): - controller.hide() - #_visible_parameter_controllers.erase(controller) + _set_button_override(_tab_buttons.left(p_category), p_override) + _category_overrides[p_category] = p_override + + +## Enables or disables override on a zone +func _set_zone_override(p_zone: String, p_override: bool) -> void: + if not _zone_indexes.has(p_zone) or _zones_with_overrides.get(p_zone) == p_override: + return - _current_tab = _button_map.right(button) + zone_select.get_popup().set_item_icon_modulate(_zone_indexes[p_zone], ThemeManager.Colors.Statuses.ProgrammerOverride if p_override else Color.WHITE) + _zones_with_overrides[p_zone] = p_override + + if p_zone == _current_zone: + _set_button_override(zone_select, p_override) + + +## Sets the override color on a BaseButton +func _set_button_override(p_button: BaseButton, p_override: bool) -> void: + p_button.begin_bulk_theme_override() - for controller: ParameterController in _controller_categories.get(_current_tab, []): - if controller in _visible_parameter_controllers: - controller.show() + if p_override: + p_button.add_theme_color_override("font_color", ThemeManager.Colors.Statuses.ProgrammerOverride) + p_button.add_theme_color_override("font_focus_color", ThemeManager.Colors.Statuses.ProgrammerOverride) + p_button.add_theme_color_override("font_pressed_color", ThemeManager.Colors.Statuses.ProgrammerOverride) + p_button.add_theme_color_override("font_hover_color", ThemeManager.Colors.Statuses.ProgrammerOverride) + p_button.add_theme_color_override("font_hover_pressed_color", ThemeManager.Colors.Statuses.ProgrammerOverride) + else: + p_button.remove_theme_color_override("font_color") + p_button.remove_theme_color_override("font_focus_color") + p_button.remove_theme_color_override("font_pressed_color") + p_button.remove_theme_color_override("font_hover_color") + p_button.remove_theme_color_override("font_hover_pressed_color") - _update_zone_filter() + p_button.end_bulk_theme_override() -## Called when a value is changed in a controller -func _on_controller_value_changed(zone: String, parameter: String, function: String, value: float, controller: ParameterController) -> void: - Programmer.set_parameter(_fixtures, parameter, function, value, zone) - _current_values.get_or_add(controller.category, {})[parameter] = {"value": value, "function": function} - controller.set_override_bg(true) - _set_tab_button_override(_button_map.left(controller.category), true) +## Handles setting overrides when a controler value is changed +func _handle_controler_override(p_controler: ParameterController) -> void: + var parameter: String = p_controler.get_parameter() + + if _overrides_by_parameter.get(_current_zone, {}).get(_current_category, {}).has(parameter): + return + + _overrides_by_parameter.get_or_add(_current_zone, {}).get_or_add(_current_category, {})[parameter] = null + + _set_category_override(_current_category, true) + _set_zone_override(_current_zone, true) + + p_controler.set_override_bg(true) -## Called when the random button is pressed on any of the controllers -func _on_controller_random_pressed(zone: String, parameter: String, function: String, controller: ParameterController) -> void: - Programmer.set_parameter_random(_fixtures, parameter, function, zone, _random_mode) - _current_values.get_or_add(controller.category, {})[parameter] = {"value": 0, "function": function} - controller.set_override_bg(true) - _set_tab_button_override(_button_map.left(controller.category), true) +## Handles removing overrides when a controler's erase button is pressed +func _handle_controler_erase(p_controler: ParameterController) -> void: + var parameter: String = p_controler.get_parameter() + + if not _overrides_by_parameter.get(_current_zone, {}).get(_current_category, {}).has(parameter): + return + + _overrides_by_parameter[_current_zone][_current_category].erase(parameter) + + if not _overrides_by_parameter[_current_zone][_current_category]: + _set_category_override(_current_category, false) + _overrides_by_parameter[_current_zone].erase(_current_category) + + if not _overrides_by_parameter[_current_zone]: + _set_zone_override(_current_zone, false) + _overrides_by_parameter.erase(_current_zone) + + p_controler.set_override_bg(false) -## Called when the erase button is pressed on any of the controllers -func _on_controller_erase_pressed(zone: String, parameter: String, controller: ParameterController) -> void: - Programmer.erase_parameter(_fixtures, parameter, zone) - controller.set_override_bg(false) +## Clears all override colors +func _clear_overrides() -> void: + for controler: ParameterController in _controlers: + controler.set_override_bg(false) + controler.set_value_fixture() + + for category: Programmer.Category in _category_overrides: + _set_category_override(category, false) + + for zone: String in _zones_with_overrides: + _set_zone_override(zone, false) - if _current_values.get_or_add(controller.category, {}).erase(parameter) and not _current_values[controller.category]: - _set_tab_button_override(_button_map.left(controller.category), false) + _zones_with_overrides.clear() + _overrides_by_parameter.clear() -## Called when the Clear button is pressed -func _on_erace_pressed() -> void: - Programmer.clear() +## Called when the fixture selection changes +func _on_selected_fixtures_changed(fixtures: Array) -> void: + if visible and fixtures != _fixtures: + _update_categorys(fixtures) -## Called when the RandomMode option is changed -func _on_random_mode_item_selected(index: int) -> void: - _random_mode = index +## Called when the page back button is pressed +func _on_page_back_pressed() -> void: + set_page(_current_page - 1) -## Called when the ZoneSelect option is changed +## Called when the page fowards button is pressed +func _on_page_fowards_pressed() -> void: + set_page(_current_page + 1) + + +## Called when a zone is selected func _on_zone_select_item_selected(index: int) -> void: - _current_zone = zone_select.get_item_text(index) - if _current_zone == "All": - _current_zone = "" + set_zone(zone_select.get_item_text(index)) + + +## Called when the clear button is pressed +func _on_clear_pressed() -> void: + Programmer.clear() + _clear_overrides() + + +## Called when the value on a ParameterControler is changed +func _on_controler_value_changed(p_value: float, p_controler: ParameterController) -> void: + _handle_controler_override(p_controler) - _update_zone_filter() + match _current_layer: + Programmer.Layer.VALUE: + Programmer.set_parameter(_fixtures, p_controler.get_parameter(), p_controler.get_function(), p_value, p_controler.get_zone()) + + +## Emitted when the function on the ParameterControler is changed +func _on_controler_function_selected(p_function: String, p_controler: ParameterController) -> void: + _handle_controler_override(p_controler) + var value: float = p_controler.get_refernce_fixture().get_default(_current_zone, p_controler.get_parameter(), p_function) + Programmer.set_parameter(_fixtures, p_controler.get_parameter(), p_function, value, _current_zone) + +## Called when an override is added to a ParameterControlers refernce fixture +func _on_controler_fixture_override_added(p_controler: ParameterController) -> void: + _handle_controler_override(p_controler) -## Called when the SaveToScene button is pressed -func _on_save_to_scene_pressed() -> void: - Programmer.save_to_new_scene(_fixtures).then(func (scene: Scene): - Interface.show_name_prompt(scene) - ) + +## Called when the erase button is pressed on a ParameterController +func _on_controler_erase_pressed(p_controler: ParameterController) -> void: + Programmer.erase_parameter(_fixtures, p_controler.get_parameter(), p_controler.get_zone()) + _handle_controler_erase(p_controler) + + #if fixture is DMXFixture: + #_manifests[fixture.get_manifest()] = [fixture] + + + #var controlers_to_hide: Array[ParameterController] = _visible_parameter_controllers.duplicate() + #var tabs_to_disable: Array[String] = _tab_buttons.keys() + #var new_visible_controllers: Array[ParameterController] = [] + #var zone_select_index: int = 1 + #var zone_to_select: int = 0 + # + #_current_values.clear() + #zone_select.clear() + #_selected_fixture_zones = [] + # + #zone_select.add_item("All", 0) + #zone_select.set_item_icon(0, preload("res://assets/icons/Zone.svg")) + #zone_select.add_separator() + #zone_select.select(0) + # + #for fixture: Fixture in new_fixtures: + #var zones: Array[String] = fixture.get_zones() + # + #if _current_zone not in zones: + #_current_zone = "" + # + #Utils.sort_text_and_numbers(zones) + #Utils.array_move_to_start(zones, "root") + # + #for zone: String in zones: + #if zone not in _selected_fixture_zones: + #zone_select.add_item(zone, zone_select_index) + #zone_select.set_item_icon(zone_select_index + 1, preload("res://assets/icons/Zone.svg")) + #_selected_fixture_zones.append(zone) + # + #if zone == _current_zone: + #zone_to_select = zone_select_index + # + #zone_select_index += 1 + # + #var categories: Dictionary = fixture.get_parameter_categories(zone) + #var current_values: Dictionary = fixture.get_all_values() + # + #current_values.merge(fixture.get_all_override_values(), true) + # + #for parameter: String in categories: + #var controller: ParameterController + #if _parameter_controllers.get_or_add(zone, {}).has(parameter): + #controller = _parameter_controllers[zone][parameter] + # + #if fixture not in _fixtures: + #for function: String in fixture.get_parameter_functions(zone, parameter): + #if not controller.has_function(function): + #controller.add_function(function) + # + #tabs_to_disable.erase(controller.category) + #_button_map.left(controller.category).disabled = false + # + #if controller.category == _current_tab: + #controller.show() + # + #if controller not in new_visible_controllers: + #new_visible_controllers.append(controller) + # + #controlers_to_hide.erase(controller) + # + #else: + #controller = load("uid://bfk7wccmsndfr").instantiate() + #var category = categories[parameter] + # + #if category not in _tab_buttons.keys(): + #category = "Control" + # + #controller.set_parameter(parameter) + #controller.set_zone(zone) + #controller.category = category + # + #for function: String in fixture.get_parameter_functions(zone, parameter): + #controller.add_function(function) + # + #_parameter_controllers.get_or_add(zone, {})[parameter] = controller + #_controller_categories.get_or_add(category, []).append(controller) + #new_visible_controllers.append(controller) + # + #tabs_to_disable.erase(category) + #_button_map.left(category).disabled = false + # + #if category != _current_tab: + #controller.hide() + # + #_parameter_container.add_child(controller) + #controller.value_changed.connect(_on_controller_value_changed.bind(controller)) + #controller.random_pressed.connect(_on_controller_random_pressed.bind(controller)) + #controller.erase_pressed.connect(_on_controller_erase_pressed.bind(controller)) + # + #var current: Dictionary = current_values.get(zone, {}).get(parameter, {}) + #if current: + #controller.set_value(current.value) + #controller.set_function(current.function) + #controller.set_override_bg(true) + #_set_tab_button_override(_tab_buttons[controller.category], true) + # + #_current_values.get_or_add(controller.category, {})[parameter] = { + #"value": current.value, + #"function": current.function + #} + # + #for controller: ParameterController in controlers_to_hide: + #controller.clear() + #controller.hide() + # + #for tab_name: String in tabs_to_disable: + #_button_map.left(tab_name).disabled = true + #_set_tab_button_override(_button_map.left(tab_name), false) + # + #_visible_parameter_controllers = new_visible_controllers + #_fixtures.assign(new_fixtures) + # + #if zone_to_select == 0: + #_current_zone = "" + # + #zone_select.select(zone_to_select) + #_update_zone_filter() +# +# +### Shows or hides ParameterControllers based on the current zone filter +#func _update_zone_filter() -> void: + #for controller: ParameterController in _visible_parameter_controllers: + #if controller.category != _current_tab: + #continue + # + #if controller.get_zone() == _current_zone or _current_zone == "": + #controller.show() + # + #elif controller.get_zone() != _current_zone: + #controller.hide() +# +# +### Clears the override BG on all of the ParameterControllers and tab buttons +#func _clear_override_bg() -> void: + #for zone: String in _parameter_controllers.keys(): + #for controller: ParameterController in _parameter_controllers[zone].values(): + #controller.set_override_bg(false) + # + #for tab_button: Button in _tab_buttons.values(): + #_set_tab_button_override(tab_button, false) +# +# + +# +# + +# +# +### Called when any one of the tab buttons is pressed +#func _on_tab_button_pressed(button: Button) -> void: + #if _button_map.right(button) == _current_tab: + #return + # + #for controller: ParameterController in _controller_categories.get(_current_tab, []): + #controller.hide() + ##_visible_parameter_controllers.erase(controller) + # + #_current_tab = _button_map.right(button) + # + #for controller: ParameterController in _controller_categories.get(_current_tab, []): + #if controller in _visible_parameter_controllers: + #controller.show() + # + #_update_zone_filter() +# +# +### Called when a value is changed in a controller +#func _on_controller_value_changed(zone: String, parameter: String, function: String, value: float, controller: ParameterController) -> void: + #Programmer.set_parameter(_fixtures, parameter, function, value, zone) + #_current_values.get_or_add(controller.category, {})[parameter] = {"value": value, "function": function} + #controller.set_override_bg(true) + #_set_tab_button_override(_button_map.left(controller.category), true) +# +# +### Called when the random button is pressed on any of the controllers +#func _on_controller_random_pressed(zone: String, parameter: String, function: String, controller: ParameterController) -> void: + #Programmer.set_parameter_random(_fixtures, parameter, function, zone, _random_mode) + #_current_values.get_or_add(controller.category, {})[parameter] = {"value": 0, "function": function} + #controller.set_override_bg(true) + #_set_tab_button_override(_button_map.left(controller.category), true) +# +# +### Called when the erase button is pressed on any of the controllers +#func _on_controller_erase_pressed(zone: String, parameter: String, controller: ParameterController) -> void: + #Programmer.erase_parameter(_fixtures, parameter, zone) + #controller.set_override_bg(false) + # + #if _current_values.get_or_add(controller.category, {}).erase(parameter) and not _current_values[controller.category]: + #_set_tab_button_override(_button_map.left(controller.category), false) +# +# +### Called when the Clear button is pressed +#func _on_erace_pressed() -> void: + #Programmer.clear() +# +# +### Called when the RandomMode option is changed +#func _on_random_mode_item_selected(index: int) -> void: + #_random_mode = index +# +# +### Called when the ZoneSelect option is changed +#func _on_zone_select_item_selected(index: int) -> void: + #_current_zone = zone_select.get_item_text(index) + #if _current_zone == "All": + #_current_zone = "" + # + #_update_zone_filter() +# +# +### Called when the SaveToScene button is pressed +#func _on_save_to_scene_pressed() -> void: + #Programmer.save_to_new_scene(_fixtures).then(func (scene: Scene): + #Interface.show_name_prompt(scene) + #) diff --git a/panels/UIProgrammer/UIProgrammer.tscn b/panels/UIProgrammer/UIProgrammer.tscn index 7aa4c3f1..4789f106 100644 --- a/panels/UIProgrammer/UIProgrammer.tscn +++ b/panels/UIProgrammer/UIProgrammer.tscn @@ -1,17 +1,43 @@ -[gd_scene load_steps=10 format=3 uid="uid://dnyj8vnwwbi2v"] +[gd_scene load_steps=20 format=3 uid="uid://dnyj8vnwwbi2v"] [ext_resource type="Theme" uid="uid://cyua45ur0ijqo" path="res://assets/Main.theme" id="1_8omv0"] [ext_resource type="StyleBox" uid="uid://daxhx5qr5qdeu" path="res://assets/styles/UIPanelBase.tres" id="2_y0ly3"] [ext_resource type="Script" uid="uid://ckvl7eb5awy56" path="res://panels/UIProgrammer/UIProgrammer.gd" id="3_sp6ey"] [ext_resource type="PackedScene" uid="uid://dr0kfolsg46yu" path="res://panels/UIPanel/PanelMenuBar.tscn" id="4_rdv1j"] [ext_resource type="Texture2D" uid="uid://dt0khkr0ltw70" path="res://assets/icons/Programmer.svg" id="5_fc45e"] +[ext_resource type="Texture2D" uid="uid://c1cl6qetwg8st" path="res://assets/icons/Arrow_back.svg" id="6_sp6ey"] [ext_resource type="Texture2D" uid="uid://dlikb8or3xepr" path="res://assets/icons/Reset.svg" id="6_xrr4a"] [ext_resource type="StyleBox" uid="uid://csle20exwqtti" path="res://assets/styles/PanelMenuBar.tres" id="7_7jywt"] -[ext_resource type="Texture2D" uid="uid://cjx13jidaqjyf" path="res://assets/icons/Scene.svg" id="8_3huj7"] - -[sub_resource type="ButtonGroup" id="ButtonGroup_alch7"] - -[node name="UIProgrammer" type="PanelContainer" node_paths=PackedStringArray("_dimmer_tab_button", "_color_tab_button", "_gobo_tab_button", "_position_tab_button", "_beam_tab_button", "_focus_tab_button", "_shapers_tab_button", "_control_tab_button", "_parameter_container", "save_to_scene", "zone_select", "edit_controls", "menu_bar")] +[ext_resource type="Texture2D" uid="uid://kkblmodueva1" path="res://assets/icons/Arrow_foward.svg" id="7_rdv1j"] +[ext_resource type="Texture2D" uid="uid://bc0acgx0we044" path="res://assets/icons/Zone.svg" id="8_fc45e"] +[ext_resource type="Texture2D" uid="uid://cfdxexemm7766" path="res://assets/icons/RandomMode.svg" id="9_xrr4a"] +[ext_resource type="FontFile" uid="uid://crlak6jhg5jy2" path="res://assets/font/RubikMonoOne-Regular.ttf" id="10_ikse1"] +[ext_resource type="Texture2D" uid="uid://crxgau8v7k3yh" path="res://assets/icons/PlayAlt.svg" id="12_ikse1"] +[ext_resource type="Texture2D" uid="uid://bf4227ei83gh1" path="res://assets/icons/play.svg" id="13_qpens"] +[ext_resource type="Texture2D" uid="uid://huxujb0ev1gk" path="res://assets/icons/Loop.svg" id="14_qpens"] +[ext_resource type="Texture2D" uid="uid://3r1jb0q1v7ri" path="res://assets/icons/AdvancedTime.svg" id="15_qwc1o"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fc45e"] +bg_color = Color(0.12941177, 0.12941177, 0.12941177, 1) +border_width_left = 2 +border_width_top = 2 +border_width_right = 2 +border_width_bottom = 2 +border_color = Color(0.16206557, 0.1620656, 0.16206554, 1) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 + +[sub_resource type="LabelSettings" id="LabelSettings_3huj7"] +font = ExtResource("10_ikse1") +font_size = 100 +font_color = Color(0.15, 0.15, 0.15, 1) + +[sub_resource type="ButtonGroup" id="ButtonGroup_qwc1o"] +allow_unpress = true + +[node name="UIProgrammer" type="PanelContainer" node_paths=PackedStringArray("tab_button_container", "layer_button_container", "controler_container", "zone_select", "random_mode", "template_placeholder", "page_select_option", "prev_page_button", "next_page_button", "edit_controls", "menu_bar")] anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 @@ -20,18 +46,15 @@ grow_vertical = 2 theme = ExtResource("1_8omv0") theme_override_styles/panel = ExtResource("2_y0ly3") script = ExtResource("3_sp6ey") -_dimmer_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/DimmerTabButton") -_color_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/ColorTabButton") -_gobo_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/GoboTabButton") -_position_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/PositionTabButton") -_beam_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/BeamTabButton") -_focus_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/FocusTabButton") -_shapers_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/ShapersTabButton") -_control_tab_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer/ControlTabButton") -_parameter_container = NodePath("VBoxContainer/ScrollContainer/HBoxContainer") -button_group = SubResource("ButtonGroup_alch7") -save_to_scene = NodePath("VBoxContainer/PanelContainer2/HBoxContainer/SaveToScene") -zone_select = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer/ZoneSelect") +tab_button_container = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/TabButtons/TabButtonContainer") +layer_button_container = NodePath("VBoxContainer/PanelContainer2/HBoxContainer/LayerControls/LayerButtonContainer") +controler_container = NodePath("VBoxContainer/ScrollContainer/HBoxContainer") +zone_select = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer/ZoneSelect") +random_mode = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer/ValueMode") +template_placeholder = NodePath("VBoxContainer/ScrollContainer/HBoxContainer/TemplatePlaceholder") +page_select_option = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageSelect") +prev_page_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageBack") +next_page_button = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageFowards") edit_controls = NodePath("VBoxContainer/PanelMenuBar/HBoxContainer/EditControls") menu_bar = NodePath("VBoxContainer/PanelMenuBar") @@ -45,140 +68,152 @@ layout_mode = 2 text = "Programmer" icon = ExtResource("5_fc45e") -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer" index="0"] -layout_mode = 2 -size_flags_horizontal = 3 - -[node name="TabButtons" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer"] -layout_mode = 2 -size_flags_horizontal = 6 - -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons"] -layout_mode = 2 - -[node name="DimmerTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="PageButtons" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer" index="1"] layout_mode = 2 -toggle_mode = true -button_pressed = true -button_group = SubResource("ButtonGroup_alch7") -text = "Dimmer" -[node name="ColorTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons"] layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Color" -[node name="GoboTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="PageBack" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer"] layout_mode = 2 disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Gobo" - -[node name="PositionTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) -layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Position" +icon = ExtResource("6_sp6ey") -[node name="BeamTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="PageSelect" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer"] +custom_minimum_size = Vector2(51, 0) layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Beam" +selected = 0 +item_count = 1 +popup/item_0/text = "0" +popup/item_0/id = 0 -[node name="FocusTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="PageFowards" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer"] layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Focus -" +icon = ExtResource("7_rdv1j") -[node name="ShapersTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="TabButtons" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer" index="0"] layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Shapers" +size_flags_horizontal = 6 -[node name="ControlTabButton" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/TabButtons/HBoxContainer"] -custom_minimum_size = Vector2(80, 0) +[node name="TabButtonContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/TabButtons"] layout_mode = 2 -disabled = true -toggle_mode = true -button_group = SubResource("ButtonGroup_alch7") -text = "Control" -[node name="ViewButtons" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer"] +[node name="ViewButtons" type="PanelContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer" index="3"] layout_mode = 2 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons"] +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons"] layout_mode = 2 -[node name="ZoneSelect" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer"] -custom_minimum_size = Vector2(150, 0) +[node name="ZoneSelect" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer"] +custom_minimum_size = Vector2(93, 0) layout_mode = 2 size_flags_horizontal = 10 tooltip_text = "Zone Select" +clip_text = true selected = 0 +fit_to_longest_item = false item_count = 1 -popup/item_0/text = "Zone" +popup/item_0/text = "root" +popup/item_0/icon = ExtResource("8_fc45e") popup/item_0/id = 0 popup/item_0/separator = true -[node name="RandomMode" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer"] +[node name="ValueMode" type="OptionButton" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer"] +visible = false +custom_minimum_size = Vector2(137, 0) layout_mode = 2 size_flags_horizontal = 8 tooltip_text = "Random Mode" +clip_text = true selected = 0 +fit_to_longest_item = false item_count = 2 popup/item_0/text = "All" +popup/item_0/icon = ExtResource("9_xrr4a") popup/item_0/id = 0 -popup/item_1/text = "Individual" +popup/item_1/text = "Relative" +popup/item_1/icon = ExtResource("9_xrr4a") popup/item_1/id = 1 -[node name="Erace" type="Button" parent="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer"] -layout_mode = 2 -icon = ExtResource("6_xrr4a") - [node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] layout_mode = 2 size_flags_vertical = 3 [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/ScrollContainer"] layout_mode = 2 +size_flags_horizontal = 3 size_flags_vertical = 3 +[node name="TemplatePlaceholder" type="PanelContainer" parent="VBoxContainer/ScrollContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_fc45e") + +[node name="Label" type="Label" parent="VBoxContainer/ScrollContainer/HBoxContainer/TemplatePlaceholder"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "1" +label_settings = SubResource("LabelSettings_3huj7") + [node name="PanelContainer2" type="PanelContainer" parent="VBoxContainer"] -visible = false layout_mode = 2 theme_override_styles/panel = ExtResource("7_7jywt") [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer2"] layout_mode = 2 -[node name="SaveToScene" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +[node name="LayerControls" type="PanelContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +visible = false layout_mode = 2 -tooltip_text = "Save To Scene -" -disabled = true -icon = ExtResource("8_3huj7") -[connection signal="item_selected" from="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer/ZoneSelect" to="." method="_on_zone_select_item_selected"] -[connection signal="item_selected" from="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer/RandomMode" to="." method="_on_random_mode_item_selected"] -[connection signal="pressed" from="VBoxContainer/PanelMenuBar/HBoxContainer/ScrollContainer/HBoxContainer/ViewButtons/HBoxContainer/Erace" to="." method="_on_erace_pressed"] -[connection signal="pressed" from="VBoxContainer/PanelContainer2/HBoxContainer/SaveToScene" to="." method="_on_save_to_scene_pressed"] +[node name="LayerButtonContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer/LayerControls"] +layout_mode = 2 + +[node name="TransportControls" type="PanelContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer/TransportControls"] +layout_mode = 2 + +[node name="PlayBackwards" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer/TransportControls/HBoxContainer"] +layout_mode = 2 +toggle_mode = true +button_group = SubResource("ButtonGroup_qwc1o") +icon = ExtResource("12_ikse1") + +[node name="PlayFowards" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer/TransportControls/HBoxContainer"] +layout_mode = 2 +toggle_mode = true +button_group = SubResource("ButtonGroup_qwc1o") +icon = ExtResource("13_qpens") + +[node name="Loop" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer/TransportControls/HBoxContainer"] +layout_mode = 2 +toggle_mode = true +icon = ExtResource("14_qpens") + +[node name="SetSpeed" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer/TransportControls/HBoxContainer"] +layout_mode = 2 +icon = ExtResource("15_qwc1o") + +[node name="PanelContainer" type="PanelContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 10 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/PanelContainer2/HBoxContainer/PanelContainer"] +layout_mode = 2 + +[node name="Clear" type="Button" parent="VBoxContainer/PanelContainer2/HBoxContainer/PanelContainer/HBoxContainer"] +layout_mode = 2 +icon = ExtResource("6_xrr4a") + +[connection signal="pressed" from="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageBack" to="." method="_on_page_back_pressed"] +[connection signal="item_selected" from="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageSelect" to="." method="set_page"] +[connection signal="pressed" from="VBoxContainer/PanelMenuBar/HBoxContainer/PageButtons/HBoxContainer/PageFowards" to="." method="_on_page_fowards_pressed"] +[connection signal="item_selected" from="VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer/ZoneSelect" to="." method="_on_zone_select_item_selected"] +[connection signal="item_selected" from="VBoxContainer/PanelMenuBar/HBoxContainer/ViewButtons/HBoxContainer/ValueMode" to="." method="_on_random_mode_item_selected"] +[connection signal="pressed" from="VBoxContainer/PanelContainer2/HBoxContainer/PanelContainer/HBoxContainer/Clear" to="." method="_on_clear_pressed"] [editable path="VBoxContainer/PanelMenuBar"] diff --git a/panels/UIVirtualFixtures/FixtureContainer.gd b/panels/UIVirtualFixtures/FixtureContainer.gd index e185b448..fecd1418 100644 --- a/panels/UIVirtualFixtures/FixtureContainer.gd +++ b/panels/UIVirtualFixtures/FixtureContainer.gd @@ -371,8 +371,6 @@ func _update_selection_box() -> void: _selection_rect.position = Vector2(x, y) _selection_rect.size = Vector2(w, h) - var selection_changed: bool = false - for fixture: Fixture in _virtual_fixtures.get_left(): var vf: VirtualFixture = _virtual_fixtures.left(fixture) var just_selected: bool = _selection_rect.intersection(Rect2(vf.position, vf.size)).size != Vector2.ZERO @@ -380,14 +378,10 @@ func _update_selection_box() -> void: if just_selected and fixture not in current_selected: Values.add_to_selection_value("selected_fixtures", [fixture], false) - selection_changed = true elif not just_selected and fixture in current_selected and not Input.is_key_pressed(KEY_SHIFT): Values.remove_from_selection_value("selected_fixtures", [fixture], false) - selection_changed = true - - if selection_changed: - Values.emit_selection_value("selected_fixtures") + #endregion diff --git a/panels/UIVirtualFixtures/VirtualFixture/VirtualFixture.tscn b/panels/UIVirtualFixtures/VirtualFixture/VirtualFixture.tscn index 2b1c8470..3e50237f 100644 --- a/panels/UIVirtualFixtures/VirtualFixture/VirtualFixture.tscn +++ b/panels/UIVirtualFixtures/VirtualFixture/VirtualFixture.tscn @@ -102,6 +102,7 @@ text = "Name" label_settings = SubResource("LabelSettings_6gdxf") horizontal_alignment = 1 vertical_alignment = 1 +autowrap_mode = 3 clip_text = true [connection signal="gui_input" from="." to="." method="_on_gui_input"] diff --git a/project.godot b/project.godot index 03666e9e..61e2d62b 100644 --- a/project.godot +++ b/project.godot @@ -125,6 +125,11 @@ command_palette={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":true,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null) ] } +screenshot={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194343,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} [rendering] diff --git a/scripts/classes/UIBase.gd b/scripts/classes/UIBase.gd index 1313e627..2e212f6c 100644 --- a/scripts/classes/UIBase.gd +++ b/scripts/classes/UIBase.gd @@ -22,6 +22,18 @@ var _self_class_name: String = "UIBase" ## The name of this UIBase var _ui_name: String = _self_class_name +## Stores all queued callables for this frame +var _queue_order: Array[Callable] + +## Stores arguments for each queued callable +var _queue_args: Dictionary[Callable, Array] + +## True if _call_queue has already been call_defered() +var _queue_call_defered: bool = false + +## True if _call_queue is running currently +var _queue_is_calling: bool = false + ## Settings for this component var settings_manager: SettingsManager = SettingsManager.new() @@ -44,6 +56,29 @@ func uuid() -> String: return _uuid +## Queues a callable for execution, will only all it to be called once per frame. Uses call_defered +func queue(p_callable: Callable, p_args: Array[Variant] = []) -> bool: + if _queue_is_calling: + p_callable.callv(p_args) + + return true + + elif _queue_args.has(p_callable): + _queue_args[p_callable] = p_args + + return false + + else: + _queue_order.append(p_callable) + _queue_args[p_callable] = p_args + + if not _queue_call_defered: + _call_queue.call_deferred() + _queue_call_defered = true + + return true + + ## Sets the UI name func set_ui_name(p_ui_name) -> void: _ui_name = p_ui_name @@ -71,6 +106,19 @@ func _set_class_name(p_class_name: String) -> void: _class_tree.append(p_class_name) +## Calls all the Callables in the queue +func _call_queue() -> void: + _queue_is_calling = true + + for callable: Callable in _queue_order: + callable.callv(_queue_args[callable]) + + _queue_order.clear() + _queue_args.clear() + _queue_is_calling = false + _queue_call_defered = false + + ## Serialize this ClientComponent into a Dictionary func serialize() -> Dictionary: return { diff --git a/scripts/global/Input.gd b/scripts/global/Input.gd index 4fb1a17d..f1b447be 100644 --- a/scripts/global/Input.gd +++ b/scripts/global/Input.gd @@ -28,6 +28,7 @@ var _internal_actions: Dictionary[String, Callable] = { "store_mode": _handle_store_mode_action, "ui_cancel": Interface.hide_all_popup_panels, "command_palette": Interface.toggle_popup_visable.bind(Interface.WindowPopup.COMMAND_PALETTE, self), + "screenshot": Interface.take_screenshot, } ## Allowed input events for shortcuts diff --git a/scripts/global/Interface.gd b/scripts/global/Interface.gd index cd5d6a40..faed93fb 100644 --- a/scripts/global/Interface.gd +++ b/scripts/global/Interface.gd @@ -718,6 +718,15 @@ func exit_resolve_mode() -> bool: return true +## Takes a screenshot of all screens +func take_screenshot() -> void: + for window: UIWindow in _windows.get_right(): + var file_name: String = "user://" + str(Time.get_datetime_string_from_system()) + + window.get_viewport().get_texture().get_image().save_png(file_name) + print("Screenshot saved as: ", file_name) + + ## Returns the SettingsManager object for ClientInterface func settings() -> SettingsManager: return settings_manager diff --git a/scripts/global/ThemeManager.gd b/scripts/global/ThemeManager.gd index 4a046018..f9f01a51 100644 --- a/scripts/global/ThemeManager.gd +++ b/scripts/global/ThemeManager.gd @@ -31,6 +31,7 @@ class Colors: static var Caution = Color.YELLOW static var Error = Color.RED static var UnsavedData = Color.ORANGE + static var ProgrammerOverride = Color("ff8400ff") static var UIPanelFlashColor = Color.WHITE * 1.5 static var UICorePrimarySideBarTabModulate = Color(0.51, 0.51, 0.51) @@ -60,6 +61,7 @@ class StyleBoxes: class Constants: class Times: static var InterfaceFadeTime = 0.15 + static var InterfaceFadeFast = 0.08 static var DeskItemMoveTime = 0.1 static var DeskAreaMoveTime = 0.06 static var SelectBoxMoceTime = 0.06 diff --git a/scripts/global/UIDB.gd b/scripts/global/UIDB.gd index 09506d72..2895cf40 100644 --- a/scripts/global/UIDB.gd +++ b/scripts/global/UIDB.gd @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Liam Sherwin, All rights reserved. +# Copyright (c) 2026 Liam Sherwin, All rights reserved. # This file is part of the Spectrum Lighting Engine, licensed under the GPL v3. class_name ClientUIDB extends Node @@ -34,7 +34,10 @@ var _panels: Dictionary[String, PackedScene] = { "UICore": load(_p("UICore")), "UIDebug": load(_p("UIDebug")), "UIVirtualFixtures": load(_p("UIVirtualFixtures")), - "UIProgrammer": load(_p("UIProgrammer")) + "UIProgrammer": load(_p("UIProgrammer")), + "UIColorPicker": load(_p("UIColorPicker")), + "UIClock": load(_p("UIClock")), + "UIWindowManager": load(_p("UIWindowManager")) } @@ -62,6 +65,7 @@ var _data_inputs: Dictionary[Data.Type, PackedScene] = { Data.Type.FLOAT: load(_d("DataInputFloat")), Data.Type.VECTOR2: load(_d("DataInputVector2")), Data.Type.VECTOR2I: load(_d("DataInputVector2")), + Data.Type.COLOR: load(_d("DataInputColor")), Data.Type.ENUM: load(_d("DataInputEnum")), Data.Type.BITFLAGS: load(_d("DataInputBitFlags")), Data.Type.NAME: load(_d("DataInputString")), @@ -78,14 +82,18 @@ var _data_inputs: Dictionary[Data.Type, PackedScene] = { ## All UIPanels sorted by category var _panels_by_category: Dictionary[String, Array] = { - "Panels": [ - "UIDesk", - "UIPlaybacks", + "System": [ "UISettings", "UISaveLoad", - "UIDebug", "UICore", - "UIProgrammer" + "UIWindowManager", + ], + "Playbacks": [ + "UIPlaybacks", + ], + "Programming": [ + "UIProgrammer", + "UIColorPicker" ], "Components": [ "UIUniverses", @@ -94,7 +102,12 @@ var _panels_by_category: Dictionary[String, Array] = { "UIFixtureGroups" ], "Views": [ - "UIVirtualFixtures" + "UIVirtualFixtures", + "UIDesk", + ], + "Utils": [ + "UIClock", + "UIDebug", ] } diff --git a/scripts/global/Values.gd b/scripts/global/Values.gd index c2661985..d17d5273 100644 --- a/scripts/global/Values.gd +++ b/scripts/global/Values.gd @@ -78,13 +78,13 @@ func set_selection_value(value_name: String, value: Array, no_signal: bool = fal selection_values[value_name] = value if not no_signal: - emit_signal(value_name + "_selection_value_callback", selection_values[value_name]) + emit_signal(value_name + "_selection_value_callback", selection_values[value_name].duplicate()) ## Emits a selection value signal func emit_selection_value(value_name: String) -> void: if value_name in selection_values: - emit_signal(value_name + "_selection_value_callback", selection_values[value_name]) + emit_signal(value_name + "_selection_value_callback", selection_values[value_name].duplicate()) ## Add an array of items to a selection value