Implement Phase 6 features for Stickman Editor

- Updated BUGS.md with new issues related to selection bounding box, touchpad panning speed, and zooming behavior.
- Enhanced PROJECT.md with detailed specifications for touchpad controls, recent colors in the color picker, and a status bar for project information.
- Modified project.godot to update compatibility version to 4.7.
- Added a status bar in stickman_editor.tscn to display cursor coordinates and snap status.
- Updated body_part_panel.gd to handle recent colors and cursor detection over drawing areas.
- Enhanced stickman_editor.gd to manage cursor coordinates display and recent colors tracking.
- Improved whole_stickman_preview.gd to render selection gizmos on top of other parts.
- Created master_rig.tscn for the stickman rig structure with bones and IK targets.
- Developed master_rig_builder.gd to automate the rig building process with remote transforms and IK modifications.
This commit is contained in:
2026-08-14 21:49:06 -04:00
parent b97bc145e4
commit 28e8798021
11 changed files with 647 additions and 42 deletions
+54 -23
View File
@@ -92,6 +92,9 @@ var _pan_start: Vector2 = Vector2.ZERO
var _part_order: Array[String] = []
var _context_menu: PopupMenu
# Phase 6: selection gizmos rendered on top
var _selected_gizmo_bounds: Rect2 = Rect2()
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
@@ -215,6 +218,14 @@ func reset_view() -> void:
preview_area.queue_redraw()
func is_cursor_over_preview(global_pos: Vector2) -> bool:
return preview_area.get_global_rect().has_point(global_pos)
func global_to_world(global_pos: Vector2) -> Vector2:
return _screen_to_world(global_pos - preview_area.global_position)
func get_part_order() -> Array:
return _part_order.duplicate()
@@ -249,6 +260,7 @@ func _on_preview_draw() -> void:
preview_area.draw_set_transform(_pan_offset, 0.0, Vector2(_zoom, _zoom))
_draw_grid()
_selected_gizmo_bounds = Rect2()
for part_name: String in _part_order:
if not _part_shapes.has(part_name):
@@ -305,31 +317,11 @@ func _on_preview_draw() -> void:
var label_pos := Vector2(bounds.position.x, bounds.position.y - 14)
preview_area.draw_string(font, label_pos, label, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, Color.WHITE)
# Phase 4: Draw gizmos for selected part
# Store bounds for selected part (gizmos drawn on top later)
if part_name == _selected_part:
var bounds := _compute_transformed_bounds_from_points(all_raw, center, rot_deg, scl)
if bounds.has_area():
# White bounding box
preview_area.draw_rect(bounds, Color.WHITE, false, 1.5 / _zoom)
# Rotation circle
var rot_center := Vector2(bounds.position.x + bounds.size.x * 0.5, bounds.end.y + ROTATION_CIRCLE_OFFSET)
preview_area.draw_circle(rot_center, ROTATION_CIRCLE_RADIUS / _zoom, Color.WHITE)
# Scale crosses at 4 corners
var cross_half := SCALE_CROSS_SIZE / _zoom
for i: int in range(4):
var corner := _get_corner_position(bounds, i)
# Horizontal line
preview_area.draw_line(
corner + Vector2(-cross_half, 0),
corner + Vector2(cross_half, 0),
Color.WHITE, 1.5 / _zoom)
# Vertical line
preview_area.draw_line(
corner + Vector2(0, -cross_half),
corner + Vector2(0, cross_half),
Color.WHITE, 1.5 / _zoom)
_selected_gizmo_bounds = bounds
# Highlight dragged part
if not _dragging_part.is_empty() and _interaction == Interaction.TRANSLATE:
@@ -337,6 +329,24 @@ func _on_preview_draw() -> void:
if bounds is Rect2:
preview_area.draw_rect(bounds as Rect2, Color(1.0, 0.8, 0.0, 0.35), false, 1.5)
# Draw selection gizmos on top of all parts
if _selected_gizmo_bounds.has_area():
var b: Rect2 = _selected_gizmo_bounds
preview_area.draw_rect(b, Color.WHITE, false, 1.5 / _zoom)
var rot_center := Vector2(b.position.x + b.size.x * 0.5, b.end.y + ROTATION_CIRCLE_OFFSET)
preview_area.draw_circle(rot_center, ROTATION_CIRCLE_RADIUS / _zoom, Color.WHITE)
var cross_half := SCALE_CROSS_SIZE / _zoom
for i: int in range(4):
var corner := _get_corner_position(b, i)
preview_area.draw_line(
corner + Vector2(-cross_half, 0),
corner + Vector2(cross_half, 0),
Color.WHITE, 1.5 / _zoom)
preview_area.draw_line(
corner + Vector2(0, -cross_half),
corner + Vector2(0, cross_half),
Color.WHITE, 1.5 / _zoom)
func _draw_grid() -> void:
var gs := float(_grid_size)
@@ -455,6 +465,21 @@ func _compute_original_bounds_size(part_name: String) -> Vector2:
# ---------------------------------------------------------------------------
func _on_preview_gui_input(event: InputEvent) -> void:
if event is InputEventMagnifyGesture:
var mag := event as InputEventMagnifyGesture
var cursor_local := preview_area.get_local_mouse_position()
var world := _screen_to_world(cursor_local)
_zoom = clampf(_zoom * mag.factor, MIN_ZOOM, MAX_ZOOM)
_pan_offset = cursor_local - world * _zoom
preview_area.queue_redraw()
return
if event is InputEventPanGesture:
var pan := event as InputEventPanGesture
_pan_offset -= pan.delta * 3.0
preview_area.queue_redraw()
return
if event is InputEventMouseButton:
var mb := event as InputEventMouseButton
@@ -470,11 +495,17 @@ func _on_preview_gui_input(event: InputEvent) -> void:
# Mouse wheel -> zoom
if mb.button_index == MOUSE_BUTTON_WHEEL_UP and mb.pressed:
var cursor_local := mb.position
var world := _screen_to_world(cursor_local)
_zoom = clampf(_zoom * ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
_pan_offset = cursor_local - world * _zoom
preview_area.queue_redraw()
return
if mb.button_index == MOUSE_BUTTON_WHEEL_DOWN and mb.pressed:
var cursor_local := mb.position
var world := _screen_to_world(cursor_local)
_zoom = clampf(_zoom / ZOOM_STEP, MIN_ZOOM, MAX_ZOOM)
_pan_offset = cursor_local - world * _zoom
preview_area.queue_redraw()
return
@@ -495,7 +526,7 @@ func _on_preview_gui_input(event: InputEvent) -> void:
var mm := event as InputEventMouseMotion
if _is_panning:
_pan_offset += (mm.position - _pan_start) / _zoom
_pan_offset += (mm.position - _pan_start)
_pan_start = mm.position
preview_area.queue_redraw()
return