From 3bd841a7d326bc8dd5e5a37dbc901088986f7b3e Mon Sep 17 00:00:00 2001 From: LMW Date: Fri, 15 May 2026 01:07:59 +0800 Subject: [PATCH 1/5] Added autocompletion for portal nodes --- material_maker/nodes/portal/completion.gd | 107 ++++++++++++++++++ material_maker/nodes/portal/completion.gd.uid | 1 + material_maker/nodes/portal/completion.tscn | 21 ++++ material_maker/nodes/portal/portal.gd | 29 ++++- material_maker/nodes/portal/portal.tscn | 8 +- material_maker/theme/classic_base.tres | 11 +- 6 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 material_maker/nodes/portal/completion.gd create mode 100644 material_maker/nodes/portal/completion.gd.uid create mode 100644 material_maker/nodes/portal/completion.tscn diff --git a/material_maker/nodes/portal/completion.gd b/material_maker/nodes/portal/completion.gd new file mode 100644 index 000000000..e5d6c872f --- /dev/null +++ b/material_maker/nodes/portal/completion.gd @@ -0,0 +1,107 @@ +class_name PortalCompletionPanel +extends Panel + +var selected_item : int = -1 + +@onready var portal_edit : LineEdit = get_parent() +@export_multiline var slot_svg : String + +func _ready() -> void: + theme = mm_globals.main_window.theme + hide_panel() + setup_scrollbar_theme() + position = Vector2(0, portal_edit.size.y + 10) + +func _gui_input(event: InputEvent) -> void: + if (event is InputEventMouseButton + and event.button_index == MOUSE_BUTTON_WHEEL_UP + or event.button_index == MOUSE_BUTTON_WHEEL_DOWN): + accept_event() + +func _input(event : InputEvent) -> void: + if event is InputEventKey and event.pressed and visible: + match event.get_keycode_with_modifiers(): + KEY_UP: + update_current_selection(KEY_UP) + accept_event() + KEY_DOWN: + update_current_selection(KEY_DOWN) + accept_event() + KEY_ESCAPE: + hide_panel() + accept_event() + elif event is InputEventMouseButton and event.pressed: + if event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT: + if not Rect2(Vector2.ZERO, size).has_point(get_local_mouse_position()) or not visible: + portal_edit.focus_exited.emit() + hide_panel() + else: + if visible: + handle_click_completion() + accept_event() + +func setup_scrollbar_theme() -> void: + $ItemList.add_theme_constant_override("scrollbar_h_separation", 1) + + var vscroll : VScrollBar = $ItemList.get_v_scroll_bar() + vscroll.add_theme_constant_override("padding_left", 2) + vscroll.add_theme_constant_override("padding_right", 1) + + var sb : StyleBoxFlat = StyleBoxFlat.new() + sb.draw_center = true + sb.bg_color = Color.TRANSPARENT + sb.border_color = sb.bg_color + sb.set_border_width_all(2) + vscroll.add_theme_stylebox_override("scroll", sb) + +func show_panel() -> void: + mouse_filter = Control.MOUSE_FILTER_PASS + show() + +func hide_panel() -> void: + mouse_filter = Control.MOUSE_FILTER_IGNORE + hide() + +func request_completion(filter : String, graph : MMGraphEdit) -> void: + if filter.is_empty(): + hide_panel() + return + show_panel() + selected_item = -1 + $ItemList.clear() + + if not filter.is_empty(): + for node in graph.get_children(): + if node is MMGraphPortal and node.is_portal_in(): + var link : String = node.get_link() + if link.begins_with(filter.to_lower()) or link.contains(filter.to_lower()): + var port_color : Color = node.get_input_port_color(0) + var icon : Image = Image.new() + icon.load_svg_from_buffer(slot_svg.replace("#FFF", + "#" + port_color.to_html(false)).to_utf8_buffer()) + var index : int = $ItemList.add_icon_item(ImageTexture.create_from_image(icon)) + $ItemList.set_item_text(index, link) + $ItemList.set_item_tooltip_enabled(index, false) + + if $ItemList.item_count == 0: + hide_panel() + +func update_current_selection(input : Key) -> void: + selected_item = wrapi(selected_item + + (1 if input == KEY_DOWN else -1), 0, $ItemList.item_count) + if $ItemList.item_count != 0: + $ItemList.select(selected_item) + $ItemList.ensure_current_is_visible() + set_portal_edit_text($ItemList.get_item_text(selected_item)) + +func handle_click_completion() -> void: + var item : int = $ItemList.get_item_at_position(get_local_mouse_position(), true) + if item != -1: + $ItemList.select(item) + set_portal_edit_text($ItemList.get_item_text(item)) + portal_edit.focus_exited.emit() + hide_panel() + +func set_portal_edit_text(new_text : String) -> void: + portal_edit.text = new_text + portal_edit.caret_column = new_text.length() diff --git a/material_maker/nodes/portal/completion.gd.uid b/material_maker/nodes/portal/completion.gd.uid new file mode 100644 index 000000000..9633f3964 --- /dev/null +++ b/material_maker/nodes/portal/completion.gd.uid @@ -0,0 +1 @@ +uid://buqmwxkc40t11 diff --git a/material_maker/nodes/portal/completion.tscn b/material_maker/nodes/portal/completion.tscn new file mode 100644 index 000000000..ce69ac70d --- /dev/null +++ b/material_maker/nodes/portal/completion.tscn @@ -0,0 +1,21 @@ +[gd_scene format=3 uid="uid://s1vtfefwu7cr"] + +[ext_resource type="Script" uid="uid://buqmwxkc40t11" path="res://material_maker/nodes/portal/completion.gd" id="1_v4kch"] + +[node name="Completion" type="Panel" unique_id=1214631678] +custom_minimum_size = Vector2(200, 100) +offset_right = 200.0 +offset_bottom = 100.0 +script = ExtResource("1_v4kch") +slot_svg = " + +" + +[node name="ItemList" type="ItemList" parent="." unique_id=1794413207] +layout_mode = 0 +offset_right = 200.0 +offset_bottom = 100.0 +grow_horizontal = 2 +grow_vertical = 2 +allow_search = false +wraparound_items = false diff --git a/material_maker/nodes/portal/portal.gd b/material_maker/nodes/portal/portal.gd index 0116b2bfe..0084fd8a0 100644 --- a/material_maker/nodes/portal/portal.gd +++ b/material_maker/nodes/portal/portal.gd @@ -78,12 +78,19 @@ func _exit_tree() -> void: func _gui_input(event : InputEvent) -> void: if event is InputEventMouseButton and event.double_click: setup_portal_edit() + accept_event() func _input(event : InputEvent) -> void: - if event is InputEventKey and event.pressed and selected and not is_editing: + if event is InputEventKey and event.pressed and selected: match event.get_keycode_with_modifiers(): KEY_F2, KEY_ENTER: - setup_portal_edit() + if not is_editing: + setup_portal_edit() + KEY_UP, KEY_DOWN: + if is_editing: + accept_event() + KEY_ENTER: + accept_event() elif event is InputEventMouseMotion: if Rect2(Vector2.ZERO, size).has_point(get_local_mouse_position()): mm_globals.set_tip_text(tr("#LMB: Select node, #LMB#LMB/F2/Enter: Rename link"), 1.0, 2) @@ -285,11 +292,19 @@ func setup_portal_edit() -> void: position_offset_changed.connect(edit_box_set_position.bind(edit)) graph.draw.connect(edit_box_set_position.bind(edit)) + var completion_panel : PortalCompletionPanel = preload( + "res://material_maker/nodes/portal/completion.tscn").instantiate() + if is_portal_out(): + completion_panel.visibility_changed.connect( + func() -> void: visible = not completion_panel.visible) + edit.add_child(completion_panel) + edit.modulate = link_collision_warning_color(get_link()) edit.text_submitted.connect( func(new_text : String) -> void: if not is_editing: return + var new_link := new_text.strip_edges() if not new_link.is_empty(): if is_link_unique(new_link): @@ -301,20 +316,28 @@ func setup_portal_edit() -> void: graph.undoredo.end_group() else: on_parameter_changed("link", old_link) + else: + edit.text = old_link is_editing = false generator.editable = false edit.reset_size() - edit.queue_free()) + edit.queue_free() + graph.grab_focus() + selected = true + queue_redraw()) edit.text_changed.connect( func(new_text : String) -> void: var new_link := new_text.strip_edges() if not new_link.is_empty(): on_parameter_changed("link", new_link) edit.modulate = link_collision_warning_color(new_link) + if completion_panel: + completion_panel.request_completion(new_link, graph) edit_box_set_position(edit)) edit.focus_exited.connect(func(): edit.text_submitted.emit(edit.text)) edit.tree_exiting.connect( func() -> void: + show() position_offset_changed.disconnect(edit_box_set_position) graph.draw.disconnect(edit_box_set_position)) diff --git a/material_maker/nodes/portal/portal.tscn b/material_maker/nodes/portal/portal.tscn index 40b737c09..6e20ec394 100644 --- a/material_maker/nodes/portal/portal.tscn +++ b/material_maker/nodes/portal/portal.tscn @@ -1,10 +1,10 @@ -[gd_scene load_steps=3 format=3 uid="uid://8ne0p5ahkdw1"] +[gd_scene format=3 uid="uid://8ne0p5ahkdw1"] [ext_resource type="Script" uid="uid://bqruhogmtlu81" path="res://material_maker/nodes/portal/portal.gd" id="1_qhgt7"] [sub_resource type="Theme" id="Theme_fybc5"] -[node name="Portal" type="GraphNode"] +[node name="Portal" type="GraphNode" unique_id=97108403] offset_right = 60.0 offset_bottom = 79.0 theme = SubResource("Theme_fybc5") @@ -21,12 +21,12 @@ slot/0/right_icon = null slot/0/draw_stylebox = true script = ExtResource("1_qhgt7") -[node name="Control" type="Control" parent="."] +[node name="Control" type="Control" parent="." unique_id=1244434570] custom_minimum_size = Vector2(24, 24) layout_mode = 2 mouse_filter = 2 -[node name="Dragger" type="Control" parent="Control"] +[node name="Dragger" type="Control" parent="Control" unique_id=467501132] unique_name_in_owner = true anchors_preset = 0 offset_right = 40.0 diff --git a/material_maker/theme/classic_base.tres b/material_maker/theme/classic_base.tres index 5e759d08f..d90d16687 100644 --- a/material_maker/theme/classic_base.tres +++ b/material_maker/theme/classic_base.tres @@ -219,15 +219,10 @@ content_margin_left = 3.0 content_margin_top = 3.0 content_margin_right = 3.0 content_margin_bottom = 3.0 -draw_center = false -border_width_left = 1 -border_width_top = 1 -border_width_right = 1 -border_width_bottom = 1 -border_color = Color(0.473976, 0.473976, 0.473975, 1) +bg_color = Color(0.234, 0.26970002, 0.36, 1) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fbnbn"] -bg_color = Color(0.14902, 0.172549, 0.231373, 1) +bg_color = Color(0.1625, 0.18729167, 0.25, 1) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_attdh"] content_margin_left = 3.0 @@ -1068,7 +1063,7 @@ ItemList/colors/guide_color = Color(0.701961, 0.701961, 0.701961, 0.129412) ItemList/styles/focus = SubResource("StyleBoxEmpty_v2584") ItemList/styles/hovered = SubResource("StyleBoxFlat_m2221") ItemList/styles/panel = SubResource("StyleBoxFlat_fbnbn") -ItemList/styles/selected = SubResource("StyleBoxFlat_attdh") +ItemList/styles/selected = SubResource("StyleBoxFlat_m2221") ItemList/styles/selected_focus = SubResource("StyleBoxFlat_attdh") Label/colors/font_color = Color(0.8, 0.807843, 0.827451, 1) Label/colors/font_outline_color = Color(0, 0, 0, 0) From a0850f52a2ae0cce0a428030d68f1d7497e58424 Mon Sep 17 00:00:00 2001 From: LMW Date: Fri, 15 May 2026 19:00:45 +0800 Subject: [PATCH 2/5] fix portal edit not focusing --- material_maker/nodes/portal/completion.gd | 1 - 1 file changed, 1 deletion(-) diff --git a/material_maker/nodes/portal/completion.gd b/material_maker/nodes/portal/completion.gd index e5d6c872f..5941c9ce9 100644 --- a/material_maker/nodes/portal/completion.gd +++ b/material_maker/nodes/portal/completion.gd @@ -33,7 +33,6 @@ func _input(event : InputEvent) -> void: elif event is InputEventMouseButton and event.pressed: if event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT: if not Rect2(Vector2.ZERO, size).has_point(get_local_mouse_position()) or not visible: - portal_edit.focus_exited.emit() hide_panel() else: if visible: From c1089d5ab360687191abf0f4c14711d9d6232732 Mon Sep 17 00:00:00 2001 From: LMW Date: Fri, 15 May 2026 20:10:54 +0800 Subject: [PATCH 3/5] center box, prevent panel from adding in input portal edit --- material_maker/nodes/portal/completion.gd | 12 +++++++++++- material_maker/nodes/portal/completion.tscn | 19 +++++++++++++------ material_maker/nodes/portal/portal.gd | 5 +++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/material_maker/nodes/portal/completion.gd b/material_maker/nodes/portal/completion.gd index 5941c9ce9..ca7b2a96f 100644 --- a/material_maker/nodes/portal/completion.gd +++ b/material_maker/nodes/portal/completion.gd @@ -2,15 +2,20 @@ class_name PortalCompletionPanel extends Panel var selected_item : int = -1 +const HEIGHT_MAX_ITEMS : int = 8 @onready var portal_edit : LineEdit = get_parent() @export_multiline var slot_svg : String +## Emitted when selected item changes(i.e. via up/down key presses) +signal selection_updated + func _ready() -> void: theme = mm_globals.main_window.theme hide_panel() setup_scrollbar_theme() - position = Vector2(0, portal_edit.size.y + 10) + position = Vector2(portal_edit.size.x * 0.5 - size.x * 0.5, + portal_edit.size.y + 10) func _gui_input(event: InputEvent) -> void: if (event is InputEventMouseButton @@ -82,6 +87,9 @@ func request_completion(filter : String, graph : MMGraphEdit) -> void: $ItemList.set_item_text(index, link) $ItemList.set_item_tooltip_enabled(index, false) + # approx height adjustment, min 2 items, max 7 + size.y = clampf(4 + $ItemList.item_count * 25, 50, HEIGHT_MAX_ITEMS * 25) + custom_minimum_size = size if $ItemList.item_count == 0: hide_panel() @@ -92,6 +100,8 @@ func update_current_selection(input : Key) -> void: $ItemList.select(selected_item) $ItemList.ensure_current_is_visible() set_portal_edit_text($ItemList.get_item_text(selected_item)) + portal_edit.size.x = 0 + selection_updated.emit() func handle_click_completion() -> void: var item : int = $ItemList.get_item_at_position(get_local_mouse_position(), true) diff --git a/material_maker/nodes/portal/completion.tscn b/material_maker/nodes/portal/completion.tscn index ce69ac70d..711b4ca4d 100644 --- a/material_maker/nodes/portal/completion.tscn +++ b/material_maker/nodes/portal/completion.tscn @@ -3,19 +3,26 @@ [ext_resource type="Script" uid="uid://buqmwxkc40t11" path="res://material_maker/nodes/portal/completion.gd" id="1_v4kch"] [node name="Completion" type="Panel" unique_id=1214631678] -custom_minimum_size = Vector2(200, 100) -offset_right = 200.0 -offset_bottom = 100.0 +custom_minimum_size = Vector2(225, 0) +anchors_preset = 5 +anchor_left = 0.5 +anchor_right = 0.5 +offset_left = -100.0 +offset_right = 100.0 +grow_horizontal = 2 script = ExtResource("1_v4kch") slot_svg = " " [node name="ItemList" type="ItemList" parent="." unique_id=1794413207] -layout_mode = 0 -offset_right = 200.0 -offset_bottom = 100.0 +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 allow_search = false wraparound_items = false +fixed_column_width = 200 +metadata/_edit_use_anchors_ = true diff --git a/material_maker/nodes/portal/portal.gd b/material_maker/nodes/portal/portal.gd index 0084fd8a0..2defd59fa 100644 --- a/material_maker/nodes/portal/portal.gd +++ b/material_maker/nodes/portal/portal.gd @@ -292,9 +292,10 @@ func setup_portal_edit() -> void: position_offset_changed.connect(edit_box_set_position.bind(edit)) graph.draw.connect(edit_box_set_position.bind(edit)) - var completion_panel : PortalCompletionPanel = preload( - "res://material_maker/nodes/portal/completion.tscn").instantiate() + var completion_panel : PortalCompletionPanel if is_portal_out(): + completion_panel = preload("res://material_maker/nodes/portal/completion.tscn").instantiate() + completion_panel.selection_updated.connect(edit_box_set_position.bind(edit)) completion_panel.visibility_changed.connect( func() -> void: visible = not completion_panel.visible) edit.add_child(completion_panel) From 7087b9d716edf39c7a4e8b863b6993b2eeb771ad Mon Sep 17 00:00:00 2001 From: LMW Date: Fri, 15 May 2026 20:12:18 +0800 Subject: [PATCH 4/5] comment --- material_maker/nodes/portal/completion.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/material_maker/nodes/portal/completion.gd b/material_maker/nodes/portal/completion.gd index ca7b2a96f..8e5be2366 100644 --- a/material_maker/nodes/portal/completion.gd +++ b/material_maker/nodes/portal/completion.gd @@ -87,7 +87,7 @@ func request_completion(filter : String, graph : MMGraphEdit) -> void: $ItemList.set_item_text(index, link) $ItemList.set_item_tooltip_enabled(index, false) - # approx height adjustment, min 2 items, max 7 + # approx height adjustment size.y = clampf(4 + $ItemList.item_count * 25, 50, HEIGHT_MAX_ITEMS * 25) custom_minimum_size = size if $ItemList.item_count == 0: From 92bbc21dbd9f0d00b01499f81fce3a9f799ffd80 Mon Sep 17 00:00:00 2001 From: LMW Date: Wed, 20 May 2026 19:08:52 +0800 Subject: [PATCH 5/5] add completion panel as a graph child --- material_maker/nodes/portal/completion.gd | 30 +++++++++++++++++------ material_maker/nodes/portal/portal.gd | 5 ++-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/material_maker/nodes/portal/completion.gd b/material_maker/nodes/portal/completion.gd index 8e5be2366..bc46e5ac7 100644 --- a/material_maker/nodes/portal/completion.gd +++ b/material_maker/nodes/portal/completion.gd @@ -4,18 +4,33 @@ extends Panel var selected_item : int = -1 const HEIGHT_MAX_ITEMS : int = 8 -@onready var portal_edit : LineEdit = get_parent() @export_multiline var slot_svg : String +@onready var graph : MMGraphEdit = get_parent() + +var portal_edit : LineEdit = null + ## Emitted when selected item changes(i.e. via up/down key presses) signal selection_updated func _ready() -> void: - theme = mm_globals.main_window.theme hide_panel() - setup_scrollbar_theme() - position = Vector2(portal_edit.size.x * 0.5 - size.x * 0.5, - portal_edit.size.y + 10) + setup_theme() + update_position() + setup_signals() + +func setup_signals() -> void: + graph.draw.connect(update_position) + portal_edit.tree_exiting.connect(portal_edit_tree_exiting) + +func portal_edit_tree_exiting() -> void: + graph.draw.disconnect(update_position) + queue_free() + +func update_position() -> void: + scale = portal_edit.scale + position = Vector2((portal_edit.size.x - size.x) * 0.5, + portal_edit.size.y + 10) * graph.zoom + portal_edit.position func _gui_input(event: InputEvent) -> void: if (event is InputEventMouseButton @@ -44,7 +59,8 @@ func _input(event : InputEvent) -> void: handle_click_completion() accept_event() -func setup_scrollbar_theme() -> void: +func setup_theme() -> void: + theme = mm_globals.main_window.theme $ItemList.add_theme_constant_override("scrollbar_h_separation", 1) var vscroll : VScrollBar = $ItemList.get_v_scroll_bar() @@ -66,7 +82,7 @@ func hide_panel() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE hide() -func request_completion(filter : String, graph : MMGraphEdit) -> void: +func request_completion(filter : String) -> void: if filter.is_empty(): hide_panel() return diff --git a/material_maker/nodes/portal/portal.gd b/material_maker/nodes/portal/portal.gd index 2defd59fa..8e6b2d311 100644 --- a/material_maker/nodes/portal/portal.gd +++ b/material_maker/nodes/portal/portal.gd @@ -295,10 +295,11 @@ func setup_portal_edit() -> void: var completion_panel : PortalCompletionPanel if is_portal_out(): completion_panel = preload("res://material_maker/nodes/portal/completion.tscn").instantiate() + completion_panel.portal_edit = edit completion_panel.selection_updated.connect(edit_box_set_position.bind(edit)) completion_panel.visibility_changed.connect( func() -> void: visible = not completion_panel.visible) - edit.add_child(completion_panel) + graph.add_child(completion_panel) edit.modulate = link_collision_warning_color(get_link()) edit.text_submitted.connect( @@ -333,7 +334,7 @@ func setup_portal_edit() -> void: on_parameter_changed("link", new_link) edit.modulate = link_collision_warning_color(new_link) if completion_panel: - completion_panel.request_completion(new_link, graph) + completion_panel.request_completion(new_link) edit_box_set_position(edit)) edit.focus_exited.connect(func(): edit.text_submitted.emit(edit.text)) edit.tree_exiting.connect(