Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c7bf5cc
Added top-level bookmark menu
williamchange Jan 18, 2026
5e13989
simplify subgraph check
williamchange Jan 19, 2026
fdc06fa
fix doubleclick editing in unpinned panel, add null check
williamchange Jan 20, 2026
d33dd53
Support subgraph bookmarks
williamchange Jan 21, 2026
bdd219b
comments
williamchange Jan 21, 2026
e93baa8
remove unused signal
williamchange Jan 21, 2026
101bd42
added class name for reroute
williamchange Jan 21, 2026
e3b7a9e
Bookmark new reroute/comment nodes
williamchange Jan 21, 2026
75b3dc3
Add material node as a default bookmark
williamchange Jan 21, 2026
4f72e98
minor comment update
williamchange Jan 21, 2026
c4fd78d
bookmark comment line nodes automatically
williamchange Jan 21, 2026
7c1f197
simplify getting node path
williamchange Jan 22, 2026
813ca38
fix editing bookmarks
williamchange Jan 22, 2026
0b59960
simplify getting graphedit from panel
williamchange Jan 22, 2026
9181439
implement bookmarks save/load from file
williamchange Jan 22, 2026
a8fab28
implement bookmark save/load when switching project tab
williamchange Jan 22, 2026
7ce8ba9
fix bookmark context menu actions
williamchange Jan 22, 2026
3c1769f
fix loading bookmarks from crash state
williamchange Jan 22, 2026
e2c8b16
fix loading bookmarks during rescue
williamchange Jan 22, 2026
93d9e38
use node title as bookmark label
williamchange Jan 22, 2026
0bb6ef7
Added X/Delete key to remove bookmarks
williamchange Jan 22, 2026
99edc20
Check for selection in tree before deletion
williamchange Jan 22, 2026
caec3c3
add visual inidication
williamchange Jan 22, 2026
9631a47
bookmark title from node/generator
williamchange Jan 23, 2026
948332b
Merge branch 'master' into bookmarks
williamchange Mar 19, 2026
bad845a
Merge branch 'master' into bookmarks
williamchange Mar 26, 2026
1e2a42f
format
williamchange Mar 26, 2026
7f42fd9
fix bookmark icon
williamchange Mar 26, 2026
b118e67
use existing name when grouping bookmarked node
williamchange Mar 26, 2026
a00a3a1
Merge branch 'master' into bookmarks
williamchange Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions addons/material_maker/engine/nodes/gen_graph.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var transmits_seed : bool = true

var current_mesh : Mesh = null

var bookmarks = null
var scroll_offset = null

signal graph_changed()
Expand All @@ -23,11 +24,14 @@ signal hierarchy_changed()
func _ready() -> void:
super._ready()

func emit_hierarchy_changed():
func get_top() -> MMGenGraph:
var top = self
while top.get_parent() != null and top.get_parent().get_script() == get_script():
top = top.get_parent()
top.emit_signal("hierarchy_changed")
return top

func emit_hierarchy_changed():
get_top().emit_signal("hierarchy_changed")

func fix_remotes() -> void:
for c in get_children():
Expand Down Expand Up @@ -535,6 +539,8 @@ func _serialize(data: Dictionary) -> Dictionary:
data.nodes.append(c.serialize())
#data.connections = connections_to_compact(connections)
data.connections = connections
if self == get_top() and bookmarks != null:
data.bookmarks = bookmarks
if scroll_offset:
data.scroll_offset = { x=scroll_offset.x, y=scroll_offset.y }
return data
Expand All @@ -556,6 +562,8 @@ func _deserialize(data : Dictionary) -> void:
if data.has("scroll_offset"):
scroll_offset = Vector2(data.scroll_offset.x, data.scroll_offset.y)
var new_stuff = await mm_loader.add_to_gen_graph(self, nodes, connection_array)
if self == get_top() and data.has("bookmarks"):
bookmarks = data.bookmarks

#region Node Graph Diff
func apply_diff_from(graph : MMGenGraph) -> void:
Expand Down
2 changes: 1 addition & 1 deletion material_maker/main_window.gd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var preview_tesselation_detail : int = 256

@onready var node_library_manager = $NodeLibraryManager
@onready var brush_library_manager = $BrushLibraryManager

@onready var bookmark_manager = $BookmarkManager

@onready var projects_panel = $VBoxContainer/Layout/FlexibleLayout/Main

Expand Down
10 changes: 9 additions & 1 deletion material_maker/main_window.tscn
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[gd_scene load_steps=21 format=3 uid="uid://cgfeik04a5qqs"]
[gd_scene load_steps=22 format=3 uid="uid://cgfeik04a5qqs"]

[ext_resource type="Script" uid="uid://cbfcjtm6e4t8h" path="res://material_maker/main_window.gd" id="1"]
[ext_resource type="Theme" uid="uid://b628lwfk6ig2c" path="res://material_maker/theme/default.tres" id="1_2qcba"]
[ext_resource type="Script" uid="uid://csdtiyrrw4pxg" path="res://material_maker/main_window_layout.gd" id="2"]
[ext_resource type="Script" uid="uid://dbvsf3qyuyrq6" path="res://material_maker/tools/bookmark_manager/bookmark_manager.gd" id="5_jxedr"]
[ext_resource type="PackedScene" uid="uid://eiq3i53x72m2" path="res://addons/flexible_layout/flexible_layout.tscn" id="6_ygla4"]
[ext_resource type="PackedScene" uid="uid://clw8sb0p8webl" path="res://material_maker/windows/add_node_popup/add_node_popup.tscn" id="7"]
[ext_resource type="PackedScene" uid="uid://bnqq3vhwmudkw" path="res://material_maker/projects_panel.tscn" id="7_ih0ps"]
Expand Down Expand Up @@ -141,6 +142,9 @@ config_section = "brush_lib"

[node name="EnvironmentManager" parent="." instance=ExtResource("13")]

[node name="BookmarkManager" type="Node" parent="."]
script = ExtResource("5_jxedr")

[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
Expand All @@ -166,10 +170,14 @@ allow_undock = true
[node name="Main" parent="VBoxContainer/Layout/FlexibleLayout" instance=ExtResource("7_ih0ps")]
layout_mode = 0
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 18.0
offset_top = 4.0
offset_right = 263.0
offset_bottom = 32.0
grow_horizontal = 1
grow_vertical = 1

[node name="ConsoleResizer" type="Control" parent="VBoxContainer"]
unique_name_in_owner = true
Expand Down
1 change: 0 additions & 1 deletion material_maker/nodes/reroute/reroute.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
extends MMGraphNodeMinimal


const PREVIEW_SIZES : Array[int] = [ 0, 64, 128, 192]


Expand Down
2 changes: 2 additions & 0 deletions material_maker/panels/common/menu_bar_button_with_panel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func _input(event : InputEvent) -> void:

if event is InputEventMouseButton:
var node := get_viewport().gui_get_hovered_control()
if node == null:
return
if node != self and not is_ancestor_of(node) and (not pinned or (node and node.script == self.script) and node.owner == owner):
button_pressed = false

Expand Down
194 changes: 194 additions & 0 deletions material_maker/panels/graph_edit/graph_bookmark_menu.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
extends PanelContainer

@onready var tree := $Tree

var bookmark_manager : BookmarkManager

var is_unpinned_double_click_edited := false

enum ContextMenu {
RENAME,
DELETE,
}


func _open() -> void:
update_bookmarks()

func _ready() -> void:
if not mm_globals.main_window.is_node_ready():
await mm_globals.main_window.ready

# Workaround for godot issue #111756 (fixed in 4.6)
for p in tree.get_children(true):
if p is Popup:
p.about_to_popup.connect(fix_tree_line_edit_size.bind(p))
break

bookmark_manager = mm_globals.main_window.bookmark_manager
bookmark_manager.bookmarks_added.connect(update_bookmarks)
bookmark_manager.updated_from_graph.connect(update_bookmarks)
bookmark_manager.bookmarks_edit_removed.connect(save_bookmarks)

%Projects.tab_changed.connect(projects_panel_tab_changed.unbind(1))

func projects_panel_tab_changed() -> void:
var graph : MMGraphEdit = owner.current_graph_edit
if not graph.view_updated.is_connected(update_bookmarks):
graph.view_updated.connect(update_bookmarks)
if graph.top_generator == null:
await get_tree().process_frame
update_bookmarks(graph.top_generator)

func fix_tree_line_edit_size(p : Popup) -> void:
var vbox : VBoxContainer = p.get_child(0)
vbox.minimum_size_changed.connect(
func():
await get_tree().process_frame
var contents_min_size = vbox.get_window().get_contents_minimum_size().y
@warning_ignore("narrowing_conversion")
vbox.get_window().max_size.y = contents_min_size + get_theme_constant("v_separation", "Tree"))

func update_bookmarks(updated_view : MMGenGraph = null) -> void:
validate_bookmarks(updated_view)
save_bookmarks()
rebuild_bookmark_tree()

func save_bookmarks() -> void:
var graph : MMGraphEdit = owner.current_graph_edit
if graph.top_generator != null:
var current_bookmarks = bookmark_manager.bookmarks.duplicate()
graph.top_generator.bookmarks = current_bookmarks

func validate_bookmarks(updated_view : MMGenGraph = null) -> void:
# Update and remove invalid references
var graph : MMGraphEdit = owner.current_graph_edit
if updated_view != null and updated_view == graph.top_generator:
if updated_view.bookmarks != null:
bookmark_manager.bookmarks = updated_view.bookmarks

if tree.get_root() == null:
return

for item : TreeItem in tree.get_root().get_children():
var bookmark_path : String = item.get_metadata(0)

# Material & Brush nodes are always in top level graph
if bookmark_path in ["./Material"]:
continue

# Bookmarked path does not point to anything
if not graph.top_generator.has_node(bookmark_path):
var target_node : String = bookmark_path.split("/")[-1]

# Remove invalid reference
bookmark_manager.remove_bookmark(bookmark_path)

# Check if the node is part of the updated graph view
# i.e. from grouping the currently bookmarked node
if updated_view != null:
var node_path := "node_" + target_node
if graph.has_node(node_path):
var gen : MMGenBase = graph.get_node(node_path).generator
var new_path : String = "./" + str(graph.top_generator.get_path_to(gen))
bookmark_manager.add_bookmark_from_path(new_path, item.get_text(0))

func rebuild_bookmark_tree() -> void:
await get_tree().process_frame
tree.clear()
var root : TreeItem = tree.create_item()

var bookmarks := bookmark_manager.bookmarks
for path : String in bookmarks:
var new_item : TreeItem = tree.create_item(root)
new_item.set_metadata(0, path)
new_item.set_text(0, bookmarks[path])

# Show placeholder/hint if bookmarks are empty
tree.visible = tree.get_root().get_child_count() != 0
$MarginContainer.visible = tree.get_root().get_child_count() == 0

func _on_tree_item_lmb_selected() -> void:
var selected_item : TreeItem = tree.get_selected()
var path : String = selected_item.get_metadata(0)
path = path.get_slice("./", 1)
var graph : MMGraphEdit = owner.current_graph_edit

var target_gen : MMGenBase

# Top-level bookmark
if "/" not in path:
# Jump back to top if we are in a subgraph
if graph.generator != graph.top_generator:
graph.update_view(graph.top_generator)

# Get bookmarked node from current graph
var node_path := NodePath("node_" + path)
if graph.has_node(node_path):
target_gen = graph.get_node(node_path).generator
else:
# Subgraph bookmark
target_gen = graph.top_generator.get_node(NodePath(path))

if target_gen == null:
# bookmark no longer exists
return

if target_gen.get_parent() is MMGenGraph and target_gen.get_parent() != graph.top_generator:
# Jump to node's subgraph if we are not already in it
if graph.generator != target_gen.get_parent():
graph.update_view(target_gen.get_parent())

# Center view on bookmarked node
var bookmark_node_path : NodePath = "node_" + target_gen.name
if graph.has_node(bookmark_node_path):
var node : GraphElement = graph.get_node(bookmark_node_path)
if node != null:
graph.select_none()
node.selected = true
var center := node.position_offset + 0.5 * node.size
var tween := get_tree().create_tween()
var target_offset := center * graph.zoom - 0.5 * graph.size
tween.tween_property(graph, "scroll_offset", target_offset, 0.5).set_ease(
Tween.EASE_IN_OUT).set_trans(Tween.TRANS_CUBIC)

func _on_tree_item_rmb_selected(_mouse_position : Vector2i) -> void:
mm_globals.popup_menu($ContextMenu, self)

func _on_tree_gui_input(event : InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.double_click:
accept_event()
if tree.get_selected():
_on_context_menu_id_pressed(ContextMenu.RENAME)
# keep panel pinned while editing
is_unpinned_double_click_edited = not get_parent().pinned
get_parent().pinned = true
elif event is InputEventKey and event.pressed:
match event.get_keycode_with_modifiers():
KEY_X, KEY_DELETE:
_on_context_menu_id_pressed(ContextMenu.DELETE)

func _on_tree_item_mouse_selected(mouse_position : Vector2, mouse_button_index : int) -> void:
if mouse_button_index == MOUSE_BUTTON_RIGHT:
_on_tree_item_rmb_selected(mouse_position)
elif mouse_button_index == MOUSE_BUTTON_LEFT:
_on_tree_item_lmb_selected()

func _on_context_menu_id_pressed(id : int) -> void:
var item : TreeItem = tree.get_selected()
match id:
ContextMenu.RENAME:
item.set_editable(0, true)
tree.edit_selected()
ContextMenu.DELETE:
if item != null:
bookmark_manager.remove_bookmark(item.get_metadata(0))
rebuild_bookmark_tree()

func _on_tree_item_edited() -> void:
var item : TreeItem = tree.get_selected()
item.set_editable(0, false)
bookmark_manager.edit_bookmark(item.get_metadata(0), item.get_text(0))
if is_unpinned_double_click_edited:
get_parent().pinned = false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://dxreq1nitp4as
27 changes: 26 additions & 1 deletion material_maker/panels/graph_edit/graph_edit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,8 @@ func _gui_input(event) -> void:
accept_event()
KEY_F:
color_comment_nodes()
KEY_B:
bookmark_node()
KEY_G:
if not get_selected_nodes().is_empty():
has_grab = true
Expand Down Expand Up @@ -368,6 +370,7 @@ func add_node(node) -> void:
add_child(node)
move_child(node, 0)
node.connect("delete_request", Callable(self, "remove_node").bind(node))
add_default_bookmark(node)

func swap_node_inputs() -> void:
var selected_nodes : Array = get_selected_nodes()
Expand Down Expand Up @@ -528,6 +531,7 @@ func do_remove_node(node) -> void:
remove_child(node)
node.queue_free()
send_changed_signal()
get_node("/root/MainWindow/BookmarkManager").updated_from_graph.emit()

# Global operations on graph

Expand Down Expand Up @@ -1055,7 +1059,6 @@ func create_subgraph() -> void:
if subgraph != null:
update_view(subgraph)


func _on_ButtonShowTree_pressed() -> void:
var graph_tree : Popup = preload("res://material_maker/widgets/graph_tree/graph_tree.tscn").instantiate()
add_child(graph_tree)
Expand Down Expand Up @@ -1845,6 +1848,28 @@ func color_comment_nodes() -> void:
picker.popup_hide.connect(undoredo.end_group)
picker.popup()

func bookmark_node() -> void:
var bookmark_manager : BookmarkManager = mm_globals.main_window.bookmark_manager
var selected_nodes := get_selected_nodes()
if selected_nodes.size() != 1:
return
var selected_node : GraphElement = selected_nodes[0]
var generator_path : String = "./" + str(top_generator.get_path_to(selected_node.generator))
if not bookmark_manager.bookmarks.has(generator_path):
var tween := get_tree().create_tween()
tween.tween_property(selected_node, "modulate", Color.DIM_GRAY, 0.1)
tween.parallel().tween_property(selected_node, "modulate", Color.WHITE, 0.15).set_delay(0.15)
bookmark_manager.add_bookmark(selected_node, generator_path)

func add_default_bookmark(node : GraphElement) -> void:
if BookmarkManager.is_default_bookmark_node(node):
if not node.generator:
await get_tree().process_frame
var node_path = "./" + str(top_generator.get_path_to(node.generator))
var bookmark_manager : BookmarkManager = mm_globals.main_window.bookmark_manager
if not bookmark_manager.bookmarks.has(node_path) and node.name == "node_Material":
bookmark_manager.add_bookmark_from_path(node_path, BookmarkManager.get_label_from_node(node))

func _on_resized() -> void:
$GraphUI.size = Vector2.ZERO
$GraphUI.position = global_position
Expand Down
1 change: 1 addition & 0 deletions material_maker/panels/hierarchy/hierarchy_panel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func fill_item(item : TreeItem, generator : MMGenGraph, selected : MMGenGraph, i

func _on_Hierarchy_item_double_clicked() -> void:
if tree.get_selected() != null:
print(tree.get_selected().get_metadata(0).get_tree_string())
emit_signal("group_selected", tree.get_selected().get_metadata(0))

func on_view_updated(generator) -> void:
Expand Down
Loading
Loading