From 6c804851a7e02bc907fb706c24cf1a7c9d961b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Wed, 13 Dec 2023 18:01:20 +0300 Subject: [PATCH] UX improvements --- classes/deck/deck.gd | 10 ++- classes/deck/deck_holder.gd | 8 +- graph_node_renderer/deck_holder_renderer.gd | 88 ++++++++++++++----- .../deck_node_renderer_graph_node.gd | 2 + graph_node_renderer/tab_container_custom.gd | 66 +++++++++----- 5 files changed, 127 insertions(+), 47 deletions(-) diff --git a/classes/deck/deck.gd b/classes/deck/deck.gd index d142835..71b79da 100644 --- a/classes/deck/deck.gd +++ b/classes/deck/deck.gd @@ -33,6 +33,7 @@ var group_output_node: String #var group_node: String var emit_group_signals: bool = true +var emit_node_added_signal: bool = true ## Emitted when a node has been added to this deck. signal node_added(node: DeckNode) @@ -74,7 +75,9 @@ func add_node_inst(node: DeckNode, assign_id: String = "", assign_to_self: bool nodes[assign_id] = node node._id = assign_id - node_added.emit(node) + if emit_node_added_signal: + node_added.emit(node) + if is_group && emit_group_signals: node_added_to_group.emit(node, node._id, assign_to_self, self) @@ -215,6 +218,7 @@ func group_nodes(nodes_to_group: Array) -> Deck: midpoint /= nodes_to_group.size() + emit_node_added_signal = false var _group_node := add_node_type("group_node") _group_node.group_id = group.id _group_node.group_instance_id = group.instance_id @@ -222,7 +226,9 @@ func group_nodes(nodes_to_group: Array) -> Deck: _group_node.position.y = midpoint.y _group_node.position_updated.emit(_group_node.position) #group.group_node = _group_node._id - + node_added.emit(_group_node) + emit_node_added_signal = true + var input_node := group.add_node_type("group_input") var output_node := group.add_node_type("group_output") group.group_input_node = input_node._id diff --git a/classes/deck/deck_holder.gd b/classes/deck/deck_holder.gd index 797b092..3d97703 100644 --- a/classes/deck/deck_holder.gd +++ b/classes/deck/deck_holder.gd @@ -110,14 +110,18 @@ static func close_all_group_instances(group_id: String) -> void: decks.erase(group_id) -## Unloads a deck. -static func close_deck(deck_id: String) -> void: +## Unloads a deck. Returns a list of groups that are closed as a result of +## closing this deck. +static func close_deck(deck_id: String) -> Array: if decks.get(deck_id) is Deck: var deck: Deck = decks[deck_id] as Deck var groups := deck.get_referenced_groups() for group in groups: close_all_group_instances(group) decks.erase(deck_id) + return groups + + return [] static func send_event(event_name: StringName, event_data: Dictionary = {}) -> void: diff --git a/graph_node_renderer/deck_holder_renderer.gd b/graph_node_renderer/deck_holder_renderer.gd index 1b3d7cb..fa6fe2a 100644 --- a/graph_node_renderer/deck_holder_renderer.gd +++ b/graph_node_renderer/deck_holder_renderer.gd @@ -62,7 +62,13 @@ func _ready() -> void: tab_container.tab_close_requested.connect( func(tab: int): - DeckHolder.close_deck(tab_container.get_tab_metadata(tab)) + var groups := DeckHolder.close_deck(tab_container.get_tab_metadata(tab, "id")) + # close tabs associated with this deck's groups + for group in groups: + for c_tab in tab_container.get_tab_count(): + if tab_container.get_tab_metadata(c_tab, "id") == group: + tab_container.close_tab(tab) + await get_tree().process_frame tab_container.close_tab(tab) ) @@ -72,7 +78,6 @@ func _ready() -> void: Connections.twitch.chat_socket.chat_received_rich.connect(Connections._twitch_chat_received) - file_popup_menu.set_item_shortcut(FileMenuId.NEW, new_deck_shortcut) file_popup_menu.set_item_shortcut(FileMenuId.OPEN, open_deck_shortcut) file_popup_menu.set_item_shortcut(FileMenuId.SAVE, save_deck_shortcut) @@ -94,14 +99,16 @@ func _on_file_id_pressed(id: int) -> void: open_save_dialog("res://") FileMenuId.CLOSE: close_current_tab() + _ when id in range(FileMenuId.RECENTS, FileMenuId.RECENTS + max_recents + 1): + open_deck_at_path(file_popup_menu.get_item_text(id)) ## Adds an empty [DeckRendererGraphEdit] with a corresponding [Deck] for it's data. func add_empty_deck() -> void: var deck := DeckHolder.add_empty_deck() var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() inst.deck = deck - tab_container.add_content(inst, "Deck %s" % (tab_container.get_tab_count() + 1)) - tab_container.set_tab_metadata(tab_container.get_current_tab(), deck.id) + tab_container.add_content(inst, "") + tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) ## Closes the current tab in [member tab_container] @@ -135,22 +142,35 @@ func _on_file_dialog_save_file(path: String) -> void: return deck.save_path = path + tab_container.set_tab_title(tab_container.get_current_tab(), path.get_file()) + # TODO: put this into DeckHolder instead var json := JSON.stringify(deck.to_dict(), "\t") var f := FileAccess.open(path, FileAccess.WRITE) f.store_string(json) + add_recent_file(get_active_deck().save_path) + ## Connected to [signal FileDialog.open_files] on [member file_dialog]. Opens ## the selected paths, instantiating [DeckRenderGraphEdit]s and [Deck]s for each. func _on_file_dialog_open_files(paths: PackedStringArray) -> void: for path in paths: - var deck := DeckHolder.open_deck_from_file(path) - var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() - inst.deck = deck - tab_container.add_content(inst, "Deck %s" % (tab_container.get_tab_count() + 1)) - tab_container.set_tab_metadata(tab_container.get_current_tab(), deck.id) - inst.initialize_from_deck() - inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) - add_recent_file(path) + open_deck_at_path(path) + + +func open_deck_at_path(path: String) -> void: + for tab in tab_container.get_tab_count(): + if tab_container.get_tab_metadata(tab, "path") == path: + tab_container.set_current_tab(tab) + return + + var deck := DeckHolder.open_deck_from_file(path) + var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() + inst.deck = deck + tab_container.add_content(inst, path.get_file()) + tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) + inst.initialize_from_deck() + inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) + add_recent_file(path) ## Gets the currently active [Deck] from [member tab_container] func get_active_deck() -> Deck: @@ -167,6 +187,7 @@ func save_active_deck() -> void: var json := JSON.stringify(get_active_deck().to_dict(), "\t") var f := FileAccess.open(get_active_deck().save_path, FileAccess.WRITE) f.store_string(json) + add_recent_file(get_active_deck().save_path) ## Disconnects the [FileDialog] signals if they are already connected. func disconnect_file_dialog_signals() -> void: @@ -181,12 +202,16 @@ func disconnect_file_dialog_signals() -> void: ## a corresponding tab to [member tab_container] func _on_deck_renderer_group_enter_requested(group_id: String) -> void: #var group_deck := deck.get_group(group_id) + for tab in tab_container.get_tab_count(): + if tab_container.get_tab_metadata(tab, "id") == group_id: + tab_container.set_current_tab(tab) + return var group_deck := DeckHolder.get_deck(group_id) var deck_renderer: DeckRendererGraphEdit = DECK_SCENE.instantiate() deck_renderer.deck = group_deck deck_renderer.initialize_from_deck() - tab_container.add_content(deck_renderer, "Group %s" % (tab_container.get_tab_count() + 1)) - tab_container.set_tab_metadata(tab_container.get_current_tab(), group_id) + tab_container.add_content(deck_renderer, "(g) %s" % group_id.left(8)) + tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", group_id) deck_renderer.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) @@ -228,7 +253,7 @@ func _on_debug_decks_viewer_item_pressed(deck_id: String, instance_id: String) - var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() inst.deck = deck tab_container.add_content(inst, "" % [deck_id.left(8)]) - tab_container.set_tab_metadata(tab_container.get_current_tab(), deck.id) + tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) inst.initialize_from_deck() inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) else: @@ -236,7 +261,7 @@ func _on_debug_decks_viewer_item_pressed(deck_id: String, instance_id: String) - var inst: DeckRendererGraphEdit = DECK_SCENE.instantiate() inst.deck = deck tab_container.add_content(inst, "" % [deck_id.left(8), instance_id.left(8)]) - tab_container.set_tab_metadata(tab_container.get_current_tab(), deck.id) + tab_container.set_tab_metadata(tab_container.get_current_tab(), "id", deck.id) inst.initialize_from_deck() inst.group_enter_requested.connect(_on_deck_renderer_group_enter_requested) @@ -257,11 +282,32 @@ func add_recents_to_menu() -> void: return if file_popup_menu.get_item_count() > FileMenuId.RECENTS + 1: - for i in range(FileMenuId.RECENTS, file_popup_menu.get_item_count() - FileMenuId.RECENTS + 1): - file_popup_menu.remove_item(i) + var end := file_popup_menu.get_item_count() - FileMenuId.RECENTS - 1 + for i in end: + file_popup_menu.remove_item(file_popup_menu.get_item_count() - 1) + + var reduce_length := recent_files.any(func(x: String): return x.length() > 35) + + for i in recent_files.size(): + var file = recent_files[i] as String + if reduce_length: + # shorten the basepath to be the first letter of all folders + var base: String = Array( + file.get_base_dir().split("/", false) + ).reduce( + func(a: String, s: String): + return a + s[0] + "/", + "/") + var filename := file.get_file() + file = base.path_join(filename) + + file_popup_menu.add_item(file) + var s := Shortcut.new() + var k := InputEventKey.new() + k.keycode = KEY_1 + i + k.ctrl_pressed = true + s.events.append(k) + file_popup_menu.set_item_shortcut(file_popup_menu.get_item_count() - 1, s) - for i in recent_files: - file_popup_menu.add_item(i) - RendererPersistence.set_value(PERSISTENCE_NAMESPACE, "config", "recent_files", recent_files) RendererPersistence.commit(PERSISTENCE_NAMESPACE) diff --git a/graph_node_renderer/deck_node_renderer_graph_node.gd b/graph_node_renderer/deck_node_renderer_graph_node.gd index a79f2d0..b2dc98b 100644 --- a/graph_node_renderer/deck_node_renderer_graph_node.gd +++ b/graph_node_renderer/deck_node_renderer_graph_node.gd @@ -19,6 +19,8 @@ func _ready() -> void: update_port(port) position_offset_changed.connect(_on_position_offset_changed) node.renamed.connect(_on_node_renamed) + if node.node_type == "group_node": + get_titlebar_hbox().tooltip_text = "Group %s" % node.group_id.left(8) ## Connected to [signal GraphElement.position_offset_updated] and updates the ## [member node]s properties diff --git a/graph_node_renderer/tab_container_custom.gd b/graph_node_renderer/tab_container_custom.gd index 818b6cc..48c75d8 100644 --- a/graph_node_renderer/tab_container_custom.gd +++ b/graph_node_renderer/tab_container_custom.gd @@ -3,32 +3,34 @@ class_name TabContainerCustom ## Custom Recreation of [TabContainer] for Flexibility ## -## Allows for more customizability within the [TabBar] thats used mainly. Extra buttons etc. +## Alternative to [TabContainer]. Instead of using the tree hierarchy to add tabs directly, +## tabs must be created by script. -## Reference to the [TabBar] at the top of the Container. +## Reference to the [TabBar] at the top of the container. @onready var tab_bar: TabBar = %TabBar ## Reference to the [Button] at the end of the [TabBar] that's -## used for adding new Tabs +## used for adding new tabs. @onready var add_tab_button: Button = %Button -## Reference to the [MarginContainer] around the Tabs Contents. +## Reference to the [MarginContainer] around the tab's contents. @onready var content_container: MarginContainer = %ContentContainer -## Emitted when the add [Button] within [member tab_bar] is pressed +## Emitted when the add [Button] within [member tab_bar] is pressed. signal add_button_pressed ## Emitted when the current tab in [member tab_bar] is changed. signal tab_changed(tab: int) ## Emitted when a tab in [member tab_bar] has been closed. ## See [signal TabBar.tab_close_requested] signal tab_closed(tab: int) -## Emitted when a request to close a tab in the [member tab_bar] has been -## requested using [signal TabBar.tab_close_pressed] +## Emitted when a request to close a tab in the [member tab_bar] has been requested. signal tab_close_requested(tab: int) ## Emitted when the order of the tabs in [member tab_bar] has been changed. signal tab_rearranged(old: int, new: int) -## Holds the previously active tab in [member tab_bar] +# Holds the previously active tab in the internal tab_bar var _previous_active_tab: int = -1 +var _tab_metadata: Dictionary #Dictionary[int -> tab idx, Dictionary[String -> key, Variant]] + func _ready() -> void: tab_bar.tab_selected.connect( @@ -58,44 +60,64 @@ func _ready() -> void: _previous_active_tab = idx_to ) -## Adds the given [Node] as "content" for the given tabs name as a [String] +## Adds the given [Node] as the displayed content for a tab. func add_content(c: Node, tab_title: String) -> void: tab_bar.add_tab(tab_title) content_container.add_child(c) tab_bar.set_current_tab(tab_bar.tab_count - 1) -## Returns the count of tabs in [member tab_bar] + +## Updates the tab at index [param tab_idx]'s title to [param title]. +func set_tab_title(tab_idx: int, title: String) -> void: + tab_bar.set_tab_title(tab_idx, title) + +## Returns the number of tabs. func get_tab_count() -> int: return tab_bar.tab_count -## Returns [code]true[/code] if [method get_tab_count] returns 0. + +## Returns [code]true[/code] if the tab bar has no tabs. func is_empty() -> bool: return get_tab_count() == 0 -## Closes the tab that is at the given [param tab] in [member tab_bar] + +## Closes a tab at the index [param tab]. func close_tab(tab: int) -> void: content_container.get_child(tab).queue_free() if !tab_bar.select_previous_available(): tab_bar.select_next_available() tab_bar.remove_tab(tab) + _tab_metadata.erase(tab) tab_closed.emit(tab) if tab_bar.tab_count == 0: _previous_active_tab = -1 -## Returns the currently selected tab in [member tab_bar] + +## Returns the currently selected tab. func get_current_tab() -> int: return tab_bar.current_tab -## Returns the child of [member content_container] at the given [param idx] + +## Sets the current tab to the tab at [param idx]. +func set_current_tab(idx: int) -> void: + tab_bar.current_tab = idx + + +## Returns the child of [member content_container] at the [param idx]. func get_content(idx: int) -> Control: return content_container.get_child(idx) -## Sets the metadata value for the tab at index [param tab_idx], which can be -## retrieved later using [method TabBar.get_tab_metadata()] -func set_tab_metadata(tab: int, metadata: Variant) -> void: - tab_bar.set_tab_metadata(tab, metadata) -## Returns the metadata value set to the tab at index [param tab_idx] using set_tab_metadata(). -## If no metadata was previously set, returns null by default. -func get_tab_metadata(tab: int) -> Variant: - return tab_bar.get_tab_metadata(tab) +## Sets the metadata value for the tab at index [param tab_idx] at [param key], which can be +## retrieved later using [method get_tab_metadata]. +func set_tab_metadata(tab: int, key: String, value: Variant) -> void: + var m = _tab_metadata.get(tab, {}) + m[key] = value + _tab_metadata[tab] = m + + +## Returns the metadata value set to the tab at index [param tab_idx] using [method set_tab_metadata]. +## If no metadata was previously set, returns [code]null[/code] by default. +func get_tab_metadata(tab: int, key: String) -> Variant: + var m = _tab_metadata.get(tab, {}) + return m.get(key)