feat(stage): complete phase 20b action plans
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@ var _row_font_size: int = 0
|
||||
var _title_font_size: int = 0
|
||||
var _title_font: Font = null
|
||||
var _use_target_summary_resolver: Callable = Callable()
|
||||
var _prop_target_name_resolver: Callable = Callable()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -62,6 +63,11 @@ func set_use_target_summary_resolver(resolver: Callable) -> void:
|
||||
refresh()
|
||||
|
||||
|
||||
func set_prop_target_name_resolver(resolver: Callable) -> void:
|
||||
_prop_target_name_resolver = resolver
|
||||
refresh()
|
||||
|
||||
|
||||
func refresh() -> void:
|
||||
if _list == null:
|
||||
return
|
||||
@@ -151,7 +157,7 @@ func _make_row(index: int, action: Dictionary, status: int) -> PanelContainer:
|
||||
|
||||
var summary := Label.new()
|
||||
summary.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var action_summary: String = ACTION_REGISTRY.summarize(action)
|
||||
var action_summary: String = ACTION_REGISTRY.summarize(action, _prop_target_name_resolver)
|
||||
if String(action.get("type", "")) == "use_prop" and _use_target_summary_resolver.is_valid():
|
||||
action_summary = String(_use_target_summary_resolver.call(
|
||||
String(action.get("target_prop_id", "")),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -283,7 +283,7 @@ func set_down_target(position: Vector2) -> void:
|
||||
|
||||
func show_action_editor() -> void:
|
||||
if _action_editor != null:
|
||||
_action_editor.popup_centered()
|
||||
_action_editor.resume_editor()
|
||||
|
||||
|
||||
func request_action_target_capture() -> void:
|
||||
@@ -304,7 +304,7 @@ func request_action_target_capture() -> void:
|
||||
var pace: String = "run" if _action_editor.current_move_style() == ACTION_REGISTRY.RUN else "walk"
|
||||
_emit_capture_request(&"position", "🚩 Click where they should %s (Esc or RMB to cancel)" % pace, Callable(self, "set_walk_target"), Callable(self, "cancel_target_capture"))
|
||||
"pick_up_prop":
|
||||
_emit_capture_request(&"prop", "🫴 Click the prop to pick up (Esc or RMB to cancel)", Callable(self, "accept_prop_target"), Callable(self, "cancel_target_capture"))
|
||||
_emit_capture_request(&"pickup_prop", "Choose a prop to pick up", Callable(self, "accept_prop_target"), Callable(self, "cancel_target_capture"))
|
||||
"use_prop":
|
||||
_emit_capture_request(&"use_prop", "Click a prop, then choose its Use spot", Callable(self, "set_use_target_from_capture"), Callable(self, "cancel_target_capture"))
|
||||
"throw_prop":
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@ signal reset_requested
|
||||
|
||||
const TOP_BAR_HEIGHT: float = 64.0
|
||||
const STATUS_BAR_HEIGHT: float = 38.0
|
||||
const COMPACT_TOP_BAR_WIDTH: float = 900.0
|
||||
const COMPACT_TOP_BAR_WIDTH: float = 1100.0
|
||||
|
||||
var _stage_theme: SandboxTheme
|
||||
var _current_mode: int = 0
|
||||
@@ -39,6 +39,7 @@ var _accent_direct: Color = Color("#FFB300")
|
||||
var _accent_play: Color = Color("#33DD77")
|
||||
var _status_pill_font_size: int = 16
|
||||
var _tooltip_font_size: int = 18
|
||||
var _direct_hint_requested: bool = false
|
||||
|
||||
var _mode_buttons: Dictionary = {}
|
||||
var _palette_buttons: Dictionary = {}
|
||||
@@ -58,6 +59,7 @@ var _pause_button: Button
|
||||
var _resume_button: Button
|
||||
var _reset_button: Button
|
||||
var _tooltip: Label
|
||||
var _action_tooltip_avoid_rect: Rect2 = Rect2()
|
||||
var _grid_check: CheckBox
|
||||
var _snap_check: CheckBox
|
||||
var _grid_size_spin: SpinBox
|
||||
@@ -293,21 +295,76 @@ func set_action_tooltip(visible: bool, text_value: String = "") -> void:
|
||||
_tooltip.visible = visible
|
||||
if not visible:
|
||||
return
|
||||
_tooltip.text = text_value
|
||||
_tooltip.text = _capture_tooltip_text(text_value)
|
||||
var style: StyleBox = _tooltip.get_theme_stylebox("normal")
|
||||
var extra: Vector2 = style.get_minimum_size() if style != null else Vector2.ZERO
|
||||
_tooltip.size = _tooltip.get_minimum_size() + extra
|
||||
var required_size: Vector2 = _tooltip.get_minimum_size() + extra
|
||||
var tooltip_width: float = clampf(required_size.x, 320.0, 420.0)
|
||||
_tooltip.size = Vector2(tooltip_width, maxf(required_size.y, 50.0))
|
||||
var mouse: Vector2 = get_viewport().get_mouse_position()
|
||||
var size: Vector2 = _tooltip.size
|
||||
var position: Vector2 = mouse + Vector2(20.0, -20.0)
|
||||
var viewport_size: Vector2 = get_viewport().get_visible_rect().size
|
||||
if position.x + size.x > viewport_size.x:
|
||||
position.x = mouse.x - size.x - 20.0
|
||||
if position.y + size.y > viewport_size.y:
|
||||
position.y = mouse.y - size.y - 20.0
|
||||
if position.y < 0.0:
|
||||
position.y = mouse.y + 20.0
|
||||
_tooltip.position = position
|
||||
var avoid_rects: Array[Rect2] = []
|
||||
if _action_tooltip_avoid_rect.has_area():
|
||||
avoid_rects.append(_action_tooltip_avoid_rect)
|
||||
if _mode_badge != null and _mode_badge.visible:
|
||||
avoid_rects.append(_mode_badge.get_global_rect().grow(6.0))
|
||||
if _timestamp_badge != null and _timestamp_badge.visible:
|
||||
avoid_rects.append(_timestamp_badge.get_global_rect().grow(6.0))
|
||||
if _palette_dock != null and _palette_dock.visible:
|
||||
avoid_rects.append(_palette_dock.get_global_rect().grow(6.0))
|
||||
_tooltip.position = _find_action_tooltip_position(mouse, size, viewport_size, avoid_rects)
|
||||
|
||||
|
||||
func _find_action_tooltip_position(mouse: Vector2, size: Vector2, viewport_size: Vector2, avoid_rects: Array[Rect2]) -> Vector2:
|
||||
var min_x: float = 8.0
|
||||
var max_x: float = maxf(min_x, viewport_size.x - size.x - 8.0)
|
||||
var min_y: float = TOP_BAR_HEIGHT + 8.0
|
||||
var max_y: float = maxf(min_y, viewport_size.y - size.y - 8.0)
|
||||
var candidates: Array[Vector2] = [
|
||||
mouse + Vector2(20.0, -20.0),
|
||||
mouse + Vector2(-size.x - 20.0, -20.0),
|
||||
mouse + Vector2(20.0, 20.0),
|
||||
mouse + Vector2(-size.x - 20.0, 20.0),
|
||||
]
|
||||
for avoid_rect: Rect2 in avoid_rects:
|
||||
candidates.append(Vector2(avoid_rect.position.x - size.x - 12.0, avoid_rect.get_center().y - size.y * 0.5))
|
||||
candidates.append(Vector2(avoid_rect.end.x + 12.0, avoid_rect.get_center().y - size.y * 0.5))
|
||||
candidates.append(Vector2(avoid_rect.get_center().x - size.x * 0.5, avoid_rect.position.y - size.y - 12.0))
|
||||
candidates.append(Vector2(avoid_rect.get_center().x - size.x * 0.5, avoid_rect.end.y + 12.0))
|
||||
candidates.append(Vector2(min_x, min_y))
|
||||
candidates.append(Vector2(max_x, min_y))
|
||||
candidates.append(Vector2(min_x, max_y))
|
||||
candidates.append(Vector2(max_x, max_y))
|
||||
for candidate: Vector2 in candidates:
|
||||
var position: Vector2 = Vector2(clampf(candidate.x, min_x, max_x), clampf(candidate.y, min_y, max_y))
|
||||
var tooltip_rect: Rect2 = Rect2(position, size)
|
||||
var overlaps: bool = false
|
||||
for avoid_rect: Rect2 in avoid_rects:
|
||||
if tooltip_rect.intersects(avoid_rect):
|
||||
overlaps = true
|
||||
break
|
||||
if not overlaps:
|
||||
return position
|
||||
return Vector2(min_x, min_y)
|
||||
|
||||
|
||||
func set_action_tooltip_avoid_rect(rect: Rect2) -> void:
|
||||
_action_tooltip_avoid_rect = rect
|
||||
|
||||
|
||||
func _capture_tooltip_text(text_value: String) -> String:
|
||||
var task: String = text_value.strip_edges()
|
||||
for old_copy: String in ["(Esc or RMB to cancel)", "(Esc or right-click to cancel)", "Esc cancels."]:
|
||||
task = task.replace(old_copy, "").strip_edges()
|
||||
while task.ends_with("\n"):
|
||||
task = task.substr(0, task.length() - 1).strip_edges()
|
||||
var cancel_hint: String = "Press Escape or right-click to cancel"
|
||||
if task.is_empty():
|
||||
return cancel_hint
|
||||
if task.contains(cancel_hint):
|
||||
return task
|
||||
return "%s\n%s" % [task, cancel_hint]
|
||||
|
||||
|
||||
func set_edit_controls_visible(visible: bool) -> void:
|
||||
@@ -326,8 +383,9 @@ func set_edit_controls_visible(visible: bool) -> void:
|
||||
|
||||
|
||||
func set_direct_controls_visible(visible: bool) -> void:
|
||||
_direct_hint_requested = visible
|
||||
if _direct_hint_label != null:
|
||||
_direct_hint_label.visible = visible
|
||||
_direct_hint_label.visible = visible and get_viewport().get_visible_rect().size.x > COMPACT_TOP_BAR_WIDTH
|
||||
|
||||
|
||||
func is_stage_frame_position(viewport_pos: Vector2) -> bool:
|
||||
@@ -506,6 +564,8 @@ func _apply_responsive_top_bar_layout() -> void:
|
||||
return
|
||||
var viewport_width: float = get_viewport().get_visible_rect().size.x
|
||||
var compact: bool = viewport_width <= COMPACT_TOP_BAR_WIDTH
|
||||
if _direct_hint_label != null:
|
||||
_direct_hint_label.visible = _direct_hint_requested and not compact
|
||||
if compact:
|
||||
_file_menu.custom_minimum_size = Vector2(64.0, 48.0)
|
||||
_file_menu.add_theme_font_size_override("font_size", 18)
|
||||
@@ -656,7 +716,7 @@ func _build_timestamp_badge() -> void:
|
||||
_timestamp_badge.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_timestamp_badge.offset_left = -220.0
|
||||
_timestamp_badge.offset_top = TOP_BAR_HEIGHT + 62.0
|
||||
_timestamp_badge.offset_right = -12.0
|
||||
_timestamp_badge.offset_right = -_theme_number("stage_panels.shell.edge_inset", 12.0, 0.0, 48.0)
|
||||
_timestamp_badge.offset_bottom = TOP_BAR_HEIGHT + 106.0
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = _theme_color("chrome.timestamp_badge.bg_color", Color("#171B24F2"))
|
||||
@@ -683,6 +743,8 @@ func _build_action_tooltip() -> void:
|
||||
_tooltip.name = "ActionTooltip"
|
||||
_tooltip.visible = false
|
||||
_tooltip.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_tooltip.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_tooltip.max_lines_visible = 3
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = Color(0.1, 0.1, 0.1, 0.9)
|
||||
style.border_color = _accent_direct
|
||||
|
||||
@@ -75,7 +75,7 @@ func build_ui(parent: Node, sandbox_theme: SandboxTheme, ui_font: Font, emoji_fo
|
||||
_action_popup.add_separator()
|
||||
_action_popup.add_item("⚡ When...", int(action_ids.get("when", 5)))
|
||||
_action_popup.add_separator()
|
||||
_action_popup.add_item("📋 Edit Queue…", int(action_ids.get("edit_queue", 7)))
|
||||
_action_popup.add_item("📋 Step Stack…", int(action_ids.get("edit_queue", 7)))
|
||||
_action_popup.add_item("⚡ Edit Rules…", int(action_ids.get("edit_rules", 8)))
|
||||
_action_popup.id_pressed.connect(_on_action_popup_id_pressed)
|
||||
_action_popup.about_to_popup.connect(_on_action_popup_about_to_popup)
|
||||
@@ -308,7 +308,7 @@ func refresh_action_popup_items(rules: Array[Dictionary]) -> void:
|
||||
if _action_popup == null:
|
||||
return
|
||||
var valid: bool = context_rig != null and is_instance_valid(context_rig)
|
||||
_disable_popup_item(_action_popup, 7, not valid or context_rig.get_queue().is_empty())
|
||||
_disable_popup_item(_action_popup, 7, not valid)
|
||||
_disable_popup_item(_action_popup, 8, not valid or not rig_has_rules(context_rig, rules))
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ func refresh_rig_context_items(rig: StickmanRig, popup: PopupMenu, rules: Array[
|
||||
if popup == null:
|
||||
return
|
||||
var valid: bool = rig != null and is_instance_valid(rig)
|
||||
_disable_popup_item(popup, 0, not valid or rig.get_queue().is_empty())
|
||||
_disable_popup_item(popup, 0, not valid)
|
||||
_disable_popup_item(popup, 1, not valid or not rig_has_rules(rig, rules))
|
||||
|
||||
|
||||
@@ -455,10 +455,13 @@ func update_action_overlay(
|
||||
if awaiting:
|
||||
var origin: Vector2 = action_origin(capture_kind, set_down_rig, throw_rig, throw_cache, _action_queue.panel_rig if _action_queue != null else null)
|
||||
var target: Vector2 = _camera.get_global_mouse_position() if _camera != null else Vector2.ZERO
|
||||
var movement_target_capture: bool = capture_kind == "position" or pending_walk_target or (_rule_authoring != null and _rule_authoring.step == RULE_STEP_ACTION_POSITION)
|
||||
if snap_enabled and (capture_kind in ["position", "throw", "set_down"] or (_rule_authoring != null and _rule_authoring.step == RULE_STEP_ACTION_POSITION)):
|
||||
if grid_size > 0.0:
|
||||
target = Vector2(roundf(target.x / grid_size) * grid_size, roundf(target.y / grid_size) * grid_size)
|
||||
if capture_kind == "set_down":
|
||||
if capture_kind in ["plan_place", "plan_prop"]:
|
||||
_placement_overlay.set_action_marker(target)
|
||||
elif capture_kind == "set_down":
|
||||
var known: bool = bool(set_down_cache.get("known", false))
|
||||
var valid: bool = bool(set_down_cache.get("valid", false))
|
||||
if known:
|
||||
@@ -487,11 +490,15 @@ func update_action_overlay(
|
||||
throw_preview["start_position"], target, throw_preview["landing_position"], target_reachable,
|
||||
trajectory_points, effective_target, collision_position, preview_planned, preview_uncertain
|
||||
)
|
||||
elif movement_target_capture:
|
||||
_placement_overlay.set_grounded_action_trajectory(origin, target, is_target_valid(target))
|
||||
else:
|
||||
_placement_overlay.set_action_trajectory(origin, target, is_target_valid(target))
|
||||
# Object/actor/rule target selection needs a cursor marker, not a
|
||||
# movement-looking line from the actor to the hovered target.
|
||||
_placement_overlay.set_action_marker(target)
|
||||
elif direct_move_target.is_finite() and context_rig != null and is_instance_valid(context_rig):
|
||||
var origin: Vector2 = context_rig.global_position - STICKMAN_LOCOMOTION.FOOT_OFFSET
|
||||
_placement_overlay.set_action_trajectory(origin, direct_move_target, true)
|
||||
_placement_overlay.set_grounded_action_trajectory(origin, direct_move_target, true)
|
||||
else:
|
||||
_placement_overlay.clear_action()
|
||||
if _chrome != null:
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
class_name StageInspectorCaptureOverlay
|
||||
extends Control
|
||||
## Keeps an active world-target choice visible beside the inspector while
|
||||
## dimming and blocking the inspector controls underneath it.
|
||||
|
||||
signal capture_action_requested(action_id: StringName)
|
||||
|
||||
const CAPTURE_STRIP_HEIGHT: float = 72.0
|
||||
const CAPTURE_MULTI_ACTION_STRIP_HEIGHT: float = 124.0
|
||||
const CAPTURE_BUTTON_HEIGHT: float = 48.0
|
||||
const CAPTURE_CANCEL_WIDTH: float = 84.0
|
||||
const CAPTURE_ACTION_WIDTH: float = 112.0
|
||||
|
||||
var _stage_theme: SandboxTheme
|
||||
var _inspector: Control
|
||||
var _inspector_scrim: Panel
|
||||
var _capture_strip: PanelContainer
|
||||
var _content_column: VBoxContainer
|
||||
var _title_row: HBoxContainer
|
||||
var _caption: Label
|
||||
var _caption_base_font_size: int = 17
|
||||
var _actions_row: HBoxContainer
|
||||
var _action_buttons: Array[Button] = []
|
||||
var _active_signature: String = ""
|
||||
var _focus_before_capture: Control
|
||||
|
||||
|
||||
func configure(stage_theme: SandboxTheme) -> void:
|
||||
_stage_theme = stage_theme
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
name = "StageInspectorCaptureOverlay"
|
||||
set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
z_index = 120
|
||||
visible = false
|
||||
_build_controls()
|
||||
get_viewport().size_changed.connect(_layout_capture_overlay)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_layout_capture_overlay()
|
||||
|
||||
|
||||
func show_capture(inspector: Control, prompt: String, actions: Array[Dictionary]) -> bool:
|
||||
if inspector == null or not is_instance_valid(inspector) or not inspector.is_visible_in_tree():
|
||||
hide_capture()
|
||||
return false
|
||||
var normalized_actions: Array[Dictionary] = actions.duplicate(true)
|
||||
if normalized_actions.is_empty():
|
||||
normalized_actions.append({"id": "cancel_capture", "label": "Cancel", "primary": true})
|
||||
_set_stacked_actions(normalized_actions.size() > 1)
|
||||
var action_signature: Array[String] = []
|
||||
for action: Dictionary in normalized_actions:
|
||||
action_signature.append("%s:%s" % [String(action.get("id", "")), String(action.get("label", ""))])
|
||||
var signature: String = "%d|%s|%s" % [inspector.get_instance_id(), prompt, ";".join(action_signature)]
|
||||
if _active_signature != signature:
|
||||
if _active_signature.is_empty():
|
||||
_focus_before_capture = get_viewport().gui_get_focus_owner()
|
||||
_active_signature = signature
|
||||
_inspector = inspector
|
||||
_caption.text = prompt
|
||||
_rebuild_actions(normalized_actions)
|
||||
visible = true
|
||||
if not _action_buttons.is_empty():
|
||||
_action_buttons[_action_buttons.size() - 1].grab_focus()
|
||||
_layout_capture_overlay()
|
||||
return true
|
||||
|
||||
|
||||
func hide_capture() -> void:
|
||||
if _active_signature.is_empty():
|
||||
return
|
||||
_active_signature = ""
|
||||
_inspector = null
|
||||
visible = false
|
||||
for button: Button in _action_buttons:
|
||||
button.queue_free()
|
||||
_action_buttons.clear()
|
||||
if _focus_before_capture != null and is_instance_valid(_focus_before_capture) and _focus_before_capture.is_visible_in_tree():
|
||||
_focus_before_capture.grab_focus()
|
||||
_focus_before_capture = null
|
||||
|
||||
|
||||
func has_capture() -> bool:
|
||||
return not _active_signature.is_empty()
|
||||
|
||||
|
||||
func get_inspector_rect() -> Rect2:
|
||||
if not has_capture() or _inspector == null or not is_instance_valid(_inspector):
|
||||
return Rect2()
|
||||
return _inspector.get_global_rect()
|
||||
|
||||
|
||||
func _build_controls() -> void:
|
||||
_inspector_scrim = Panel.new()
|
||||
_inspector_scrim.name = "InspectorInputBlocker"
|
||||
_inspector_scrim.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_inspector_scrim.focus_mode = Control.FOCUS_NONE
|
||||
_inspector_scrim.gui_input.connect(_on_capture_area_gui_input)
|
||||
var scrim_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
scrim_style.bg_color = Color(0.015, 0.02, 0.035, 0.56)
|
||||
scrim_style.set_corner_radius_all(_integer("stage_panels.shell.corner_radius", 12, 8, 32))
|
||||
_inspector_scrim.add_theme_stylebox_override("panel", scrim_style)
|
||||
add_child(_inspector_scrim)
|
||||
|
||||
_capture_strip = PanelContainer.new()
|
||||
_capture_strip.name = "CaptureCancelStrip"
|
||||
_capture_strip.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
_capture_strip.gui_input.connect(_on_capture_area_gui_input)
|
||||
_capture_strip.add_theme_stylebox_override("panel", _strip_style())
|
||||
add_child(_capture_strip)
|
||||
|
||||
var margin: MarginContainer = MarginContainer.new()
|
||||
margin.add_theme_constant_override("margin_left", 10)
|
||||
margin.add_theme_constant_override("margin_right", 10)
|
||||
margin.add_theme_constant_override("margin_top", 7)
|
||||
margin.add_theme_constant_override("margin_bottom", 7)
|
||||
margin.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_capture_strip.add_child(margin)
|
||||
|
||||
_content_column = VBoxContainer.new()
|
||||
_content_column.name = "CaptureContent"
|
||||
_content_column.add_theme_constant_override("separation", 4)
|
||||
_content_column.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
margin.add_child(_content_column)
|
||||
|
||||
_title_row = HBoxContainer.new()
|
||||
_title_row.name = "CaptureTitleRow"
|
||||
_title_row.add_theme_constant_override("separation", 8)
|
||||
_title_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||
_title_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_title_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_content_column.add_child(_title_row)
|
||||
|
||||
var marker: Label = Label.new()
|
||||
marker.text = "⊕"
|
||||
marker.add_theme_font_size_override("font_size", 25)
|
||||
marker.add_theme_color_override("font_color", Color("#22C6FF"))
|
||||
marker.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_title_row.add_child(marker)
|
||||
|
||||
_caption = Label.new()
|
||||
_caption.name = "CapturePrompt"
|
||||
_caption.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
_caption.text = "Choosing a target"
|
||||
_caption.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||
_caption.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
_caption.clip_text = true
|
||||
_caption.custom_minimum_size.y = 48.0
|
||||
_caption_base_font_size = _integer("stage_panels.type.body_size", 17, 14, 28)
|
||||
_caption.add_theme_font_size_override("font_size", _caption_base_font_size)
|
||||
_caption.add_theme_color_override("font_color", _color("stage_panels.type.text_color", Color("#F5F7FA")))
|
||||
_caption.size_flags_horizontal = Control.SIZE_EXPAND_FILL | Control.SIZE_SHRINK_BEGIN
|
||||
_caption.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||
_caption.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_title_row.add_child(_caption)
|
||||
|
||||
_actions_row = HBoxContainer.new()
|
||||
_actions_row.name = "CaptureActions"
|
||||
_actions_row.add_theme_constant_override("separation", 6)
|
||||
_actions_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
_actions_row.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
_actions_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_title_row.add_child(_actions_row)
|
||||
|
||||
|
||||
func _set_stacked_actions(stacked: bool) -> void:
|
||||
if _actions_row == null or _content_column == null or _title_row == null:
|
||||
return
|
||||
var target_parent: Node = _content_column if stacked else _title_row
|
||||
if _actions_row.get_parent() != target_parent:
|
||||
_actions_row.reparent(target_parent)
|
||||
_actions_row.size_flags_horizontal = Control.SIZE_EXPAND_FILL if stacked else Control.SIZE_SHRINK_END
|
||||
_actions_row.alignment = BoxContainer.ALIGNMENT_END
|
||||
|
||||
|
||||
func _rebuild_actions(actions: Array[Dictionary]) -> void:
|
||||
for button: Button in _action_buttons:
|
||||
button.queue_free()
|
||||
_action_buttons.clear()
|
||||
for action: Dictionary in actions:
|
||||
var action_id: StringName = StringName(String(action.get("id", "")))
|
||||
if action_id.is_empty():
|
||||
continue
|
||||
var button: Button = Button.new()
|
||||
button.name = "Capture_%s" % String(action_id)
|
||||
button.text = String(action.get("label", "Cancel"))
|
||||
button.tooltip_text = button.text
|
||||
button.focus_mode = Control.FOCUS_ALL
|
||||
button.custom_minimum_size = Vector2(CAPTURE_CANCEL_WIDTH if action_id == &"cancel_capture" else CAPTURE_ACTION_WIDTH, CAPTURE_BUTTON_HEIGHT)
|
||||
button.add_theme_font_size_override("font_size", _integer("stage_panels.action.label_size", 16, 14, 24))
|
||||
_style_action_button(button, action_id == &"cancel_capture")
|
||||
button.pressed.connect(_on_action_pressed.bind(action_id))
|
||||
_actions_row.add_child(button)
|
||||
_action_buttons.append(button)
|
||||
_trap_action_focus()
|
||||
|
||||
|
||||
func _trap_action_focus() -> void:
|
||||
for index: int in _action_buttons.size():
|
||||
var button: Button = _action_buttons[index]
|
||||
button.set_focus_neighbor(SIDE_TOP, button.get_path())
|
||||
button.set_focus_neighbor(SIDE_BOTTOM, button.get_path())
|
||||
button.set_focus_neighbor(SIDE_LEFT, _action_buttons[maxi(0, index - 1)].get_path())
|
||||
button.set_focus_neighbor(SIDE_RIGHT, _action_buttons[mini(_action_buttons.size() - 1, index + 1)].get_path())
|
||||
|
||||
|
||||
func _on_action_pressed(action_id: StringName) -> void:
|
||||
capture_action_requested.emit(action_id)
|
||||
|
||||
|
||||
func _on_capture_area_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventMouseButton:
|
||||
var mouse_button: InputEventMouseButton = event as InputEventMouseButton
|
||||
if mouse_button.pressed and mouse_button.button_index == MOUSE_BUTTON_RIGHT:
|
||||
capture_action_requested.emit(&"cancel_capture")
|
||||
accept_event()
|
||||
|
||||
|
||||
func _layout_capture_overlay() -> void:
|
||||
if not has_capture() or _inspector == null or not is_instance_valid(_inspector) or not _inspector.is_visible_in_tree():
|
||||
hide_capture()
|
||||
return
|
||||
var viewport_rect: Rect2 = get_viewport().get_visible_rect()
|
||||
var inspector_rect: Rect2 = _inspector.get_global_rect().intersection(viewport_rect)
|
||||
if not inspector_rect.has_area():
|
||||
hide_capture()
|
||||
return
|
||||
var caption_font_size: int = mini(_caption_base_font_size, 16) if inspector_rect.size.x < 340.0 else _caption_base_font_size
|
||||
_caption.add_theme_font_size_override("font_size", caption_font_size)
|
||||
_inspector_scrim.position = inspector_rect.position
|
||||
_inspector_scrim.size = inspector_rect.size
|
||||
_capture_strip.position = inspector_rect.position
|
||||
var strip_height: float = CAPTURE_MULTI_ACTION_STRIP_HEIGHT if _actions_row.get_parent() == _content_column else CAPTURE_STRIP_HEIGHT
|
||||
_capture_strip.size = Vector2(inspector_rect.size.x, strip_height)
|
||||
_capture_strip.custom_minimum_size = Vector2(inspector_rect.size.x, strip_height)
|
||||
|
||||
|
||||
func _strip_style() -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = _color("stage_panels.shell.surface_color", Color("#202634"))
|
||||
style.border_color = _color("mode_colors.direct", Color("#FFB300"))
|
||||
style.set_border_width_all(2)
|
||||
style.set_corner_radius_all(_integer("stage_panels.shell.corner_radius", 12, 8, 32))
|
||||
style.content_margin_left = 8.0
|
||||
style.content_margin_right = 8.0
|
||||
style.content_margin_top = 4.0
|
||||
style.content_margin_bottom = 4.0
|
||||
return style
|
||||
|
||||
|
||||
func _style_action_button(button: Button, is_cancel: bool) -> void:
|
||||
var normal: Color = Color("#B83A43") if is_cancel else _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
var hover: Color = Color("#CF4B54") if is_cancel else _color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
var pressed: Color = Color("#96323A") if is_cancel else _color("stage_panels.action.selected_surface", Color("#12394A"))
|
||||
var border: Color = Color("#FF8790") if is_cancel else _color("stage_panels.choice.border_color", Color("#FFFFFF55"))
|
||||
button.add_theme_stylebox_override("normal", _button_style(normal, border, 2))
|
||||
button.add_theme_stylebox_override("hover", _button_style(hover, border.lightened(0.12), 2))
|
||||
button.add_theme_stylebox_override("pressed", _button_style(pressed, border, 2))
|
||||
button.add_theme_stylebox_override("focus", _button_style(normal, Color("#8BD9FF"), 3))
|
||||
button.add_theme_color_override("font_color", Color("#FFFFFF"))
|
||||
button.add_theme_color_override("font_hover_color", Color("#FFFFFF"))
|
||||
button.add_theme_color_override("font_pressed_color", Color("#FFFFFF"))
|
||||
button.add_theme_color_override("font_focus_color", Color("#FFFFFF"))
|
||||
|
||||
|
||||
func _button_style(background: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
style.border_color = border
|
||||
style.set_border_width_all(border_width)
|
||||
style.set_corner_radius_all(_integer("stage_panels.shell.corner_radius", 10, 0, 32))
|
||||
style.content_margin_left = 10.0
|
||||
style.content_margin_right = 10.0
|
||||
style.content_margin_top = 5.0
|
||||
style.content_margin_bottom = 5.0
|
||||
return style
|
||||
|
||||
|
||||
func _color(path: String, fallback: Color) -> Color:
|
||||
return _stage_theme.color(path, fallback) if _stage_theme != null else fallback
|
||||
|
||||
|
||||
func _integer(path: String, fallback: int, minimum: int, maximum: int) -> int:
|
||||
return _stage_theme.integer(path, fallback, minimum, maximum) if _stage_theme != null else fallback
|
||||
@@ -0,0 +1 @@
|
||||
uid://71weltw84wr
|
||||
@@ -37,6 +37,7 @@ var waypoint_context: PopupMenu
|
||||
var confirm_dialog: ConfirmationDialog
|
||||
var queue_panel: QueuePanel
|
||||
var action_editor: ActionEditor
|
||||
var action_editor_backdrop: ColorRect
|
||||
var speech_composer: Control
|
||||
var speech_style_toolbar: Control
|
||||
var speech_show_toolbar: Control
|
||||
@@ -181,7 +182,7 @@ func build_context_and_editor_views(
|
||||
action_size: Vector2i
|
||||
) -> void:
|
||||
rig_context_popup = PopupMenu.new()
|
||||
rig_context_popup.add_item("📋 Edit Queue…", 0)
|
||||
rig_context_popup.add_item("📋 Step Stack…", 0)
|
||||
rig_context_popup.add_item("⚡ Edit Rules…", 1)
|
||||
parent.add_child(rig_context_popup)
|
||||
|
||||
@@ -202,11 +203,29 @@ func build_context_and_editor_views(
|
||||
queue_panel.apply_font(font, emoji_font, font_sizes)
|
||||
queue_panel.min_size = queue_size
|
||||
|
||||
action_editor_backdrop = ColorRect.new()
|
||||
action_editor_backdrop.name = "ActionEditorBackdrop"
|
||||
action_editor_backdrop.color = _sandbox_theme.color("popups.confirmation.backdrop_color", Color("#080D15B8")) if _sandbox_theme != null else Color("#080D15B8")
|
||||
action_editor_backdrop.mouse_filter = Control.MOUSE_FILTER_STOP
|
||||
action_editor_backdrop.z_index = 100
|
||||
action_editor_backdrop.z_as_relative = false
|
||||
action_editor_backdrop.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
action_editor_backdrop.visible = false
|
||||
parent.add_child(action_editor_backdrop)
|
||||
|
||||
action_editor = ACTION_EDITOR_SCENE.instantiate() as ActionEditor
|
||||
parent.add_child(action_editor)
|
||||
action_editor.apply_font(font, emoji_font, font_sizes)
|
||||
action_editor.apply_theme(_sandbox_theme)
|
||||
action_editor.min_size = action_size
|
||||
action_editor.set_preferred_size(action_size)
|
||||
action_editor.about_to_popup.connect(func() -> void:
|
||||
if is_instance_valid(action_editor_backdrop):
|
||||
action_editor_backdrop.show()
|
||||
)
|
||||
action_editor.popup_hide.connect(func() -> void:
|
||||
if is_instance_valid(action_editor_backdrop):
|
||||
action_editor_backdrop.hide()
|
||||
)
|
||||
|
||||
speech_composer = SPEECH_COMPOSER_SCENE.instantiate() as Control
|
||||
parent.add_child(speech_composer)
|
||||
@@ -394,6 +413,16 @@ func show_objects_here(
|
||||
popup.window_input.connect(_on_objects_here_window_input.bind(popup))
|
||||
popup.position = position
|
||||
popup.popup()
|
||||
popup.size_changed.connect(_fit_objects_here_popup_after_resize.bind(
|
||||
popup,
|
||||
pointer_position,
|
||||
viewport_size,
|
||||
avoid_rects.duplicate(),
|
||||
edge_inset,
|
||||
safe_top,
|
||||
safe_bottom
|
||||
))
|
||||
_fit_objects_here_popup_after_resize(popup, pointer_position, viewport_size, avoid_rects, edge_inset, safe_top, safe_bottom)
|
||||
cancel.call_deferred("grab_focus")
|
||||
|
||||
|
||||
@@ -463,6 +492,31 @@ func _objects_here_popup_position(
|
||||
return best_position
|
||||
|
||||
|
||||
func _fit_objects_here_popup_after_resize(
|
||||
popup: PopupPanel,
|
||||
pointer_position: Vector2,
|
||||
viewport_size: Vector2,
|
||||
avoid_rects: Array[Rect2],
|
||||
edge_inset: int,
|
||||
safe_top: int,
|
||||
safe_bottom: int
|
||||
) -> void:
|
||||
if popup == null or not is_instance_valid(popup) or not popup.visible:
|
||||
return
|
||||
var actual_size: Vector2i = popup.size
|
||||
var safe_position: Vector2i = _objects_here_popup_position(
|
||||
pointer_position,
|
||||
viewport_size,
|
||||
actual_size,
|
||||
avoid_rects,
|
||||
edge_inset,
|
||||
safe_top,
|
||||
safe_bottom
|
||||
)
|
||||
if popup.position != safe_position:
|
||||
popup.position = safe_position
|
||||
|
||||
|
||||
func _style_objects_here_candidate(button: Button, selected: bool) -> void:
|
||||
if button == null:
|
||||
return
|
||||
|
||||
@@ -120,8 +120,8 @@ func collect(
|
||||
entry["stage_name"] = rig.stage_name
|
||||
var facing_profile: int = int(authored_state.get("facing_profile", rig.get_facing_profile()))
|
||||
entry["facing_profile"] = StickmanRig.FacingProfile.keys()[facing_profile]
|
||||
var queue_result: Dictionary = STAGE_SCENE_IO.encode_queue(rig.get_queue())
|
||||
entry["action_queue"] = queue_result["actions"]
|
||||
var plan_result: Dictionary = STAGE_SCENE_IO.encode_plan(rig.get_action_plan())
|
||||
entry["action_plan"] = plan_result["steps"]
|
||||
if type == "stickman" and entry.has("stk_path"):
|
||||
entry["stk_path"] = CONTENT_PATHS.canonicalize(String(entry.get("stk_path", "")))
|
||||
elif type == "prop" and String(entry.get("asset_id", "")).ends_with(".prop"):
|
||||
@@ -284,18 +284,16 @@ func apply(data: Dictionary, world: Node2D, spawner: StageSpawner) -> Dictionary
|
||||
var facing_index: int = facing_names.find(String(entry.get("facing_profile", "FORWARD")))
|
||||
if facing_index >= 0:
|
||||
(node as StickmanRig).set_facing_profile(facing_index)
|
||||
var raw_queue: Variant = entry.get("action_queue", [])
|
||||
if raw_queue is Array:
|
||||
var queue: Dictionary = STAGE_SCENE_IO.decode_queue(raw_queue as Array)
|
||||
for warning: String in queue.get("warnings", []):
|
||||
var character_name: String = String(entry.get("stk_path", "")).get_file()
|
||||
if character_name.is_empty():
|
||||
character_name = file_id
|
||||
warnings.append("%s: %s Open this character's queue to review the skipped action." % [character_name, warning])
|
||||
for action: Dictionary in queue["actions"]:
|
||||
(node as StickmanRig).queue_action(action)
|
||||
var raw_plan: Variant = entry.get("action_plan", [])
|
||||
if raw_plan is Array:
|
||||
var decoded_plan: Dictionary = STAGE_SCENE_IO.decode_plan(raw_plan as Array)
|
||||
if bool(decoded_plan.get("ok", false)):
|
||||
if not (node as StickmanRig).set_action_plan(decoded_plan.get("steps", [])):
|
||||
warnings.append("%s: the action plan could not be restored." % String(entry.get("stage_name", file_id)))
|
||||
else:
|
||||
warnings.append("%s: %s" % [String(entry.get("stage_name", file_id)), String(decoded_plan.get("error", "Invalid action plan."))])
|
||||
else:
|
||||
warnings.append("%s: invalid action queue. Open this character's queue and review its actions." % file_id)
|
||||
warnings.append("%s: invalid action plan." % String(entry.get("stage_name", file_id)))
|
||||
(node as StickmanRig).reset_collision_proxy_velocity_baselines()
|
||||
loaded_nodes.append(node)
|
||||
if node is TerrainBlock:
|
||||
|
||||
@@ -4,7 +4,7 @@ extends RefCounted
|
||||
|
||||
const STICKMAN_ACTION_RUNNER := preload("res://scripts/characters/stickman/stickman_action_runner.gd")
|
||||
|
||||
enum Kind { NONE, WAYPOINT, AREA, PROP, STICKMAN, POSITION, THROW_POSITION, SET_DOWN_POSITION, SPOT_ADD, SPOT_MOVE, USE_PROP, USE_SPOT }
|
||||
enum Kind { NONE, WAYPOINT, AREA, PROP, STICKMAN, POSITION, THROW_POSITION, SET_DOWN_POSITION, PLAN_PLACE, PLAN_PROP, PICKUP_PROP, SPOT_ADD, SPOT_MOVE, USE_PROP, USE_SPOT }
|
||||
|
||||
var kind: int = Kind.NONE
|
||||
var hint: String = ""
|
||||
@@ -31,6 +31,14 @@ var use_target_preview_prefix: Array[Dictionary] = []
|
||||
var use_target_prefix_known: bool = false
|
||||
|
||||
|
||||
static func normalize_plan_destination(value: Variant) -> Dictionary:
|
||||
if value is Vector2:
|
||||
return {"kind": "place", "position": value}
|
||||
if value is Dictionary:
|
||||
return (value as Dictionary).duplicate(true)
|
||||
return {}
|
||||
|
||||
|
||||
func is_stage_custom_capture() -> bool:
|
||||
return kind in [Kind.SPOT_ADD, Kind.SPOT_MOVE, Kind.USE_PROP, Kind.USE_SPOT]
|
||||
|
||||
@@ -152,10 +160,40 @@ func resolve_target(world_pos: Vector2, selection: StageSelection, director_visu
|
||||
var prop_hit: Node2D = selection.hit_test(world_pos)
|
||||
if prop_hit is PropBlock:
|
||||
value = prop_hit
|
||||
Kind.PICKUP_PROP:
|
||||
var pickup_hit: PropBlock = null
|
||||
var ineligible_hit: PropBlock = null
|
||||
for candidate: Node2D in selection.hit_test_all(world_pos):
|
||||
if candidate is PropBlock and (candidate as PropBlock).can_be_picked_up():
|
||||
pickup_hit = candidate as PropBlock
|
||||
break
|
||||
elif candidate is PropBlock and ineligible_hit == null:
|
||||
ineligible_hit = candidate as PropBlock
|
||||
if pickup_hit == null:
|
||||
var reason: String = "Click a highlighted prop to pick it up."
|
||||
if ineligible_hit != null:
|
||||
reason = "This prop needs pickup enabled and a grip in Edit."
|
||||
return {"valid": false, "value": null, "reason": reason}
|
||||
value = pickup_hit
|
||||
Kind.STICKMAN:
|
||||
var stickman_hit: Node2D = selection.hit_test(world_pos)
|
||||
if stickman_hit is StickmanRig:
|
||||
value = stickman_hit
|
||||
Kind.PLAN_PROP:
|
||||
var plan_prop_hit: Node2D = selection.hit_test(world_pos)
|
||||
if plan_prop_hit is PropBlock:
|
||||
var prop: PropBlock = plan_prop_hit as PropBlock
|
||||
var approach: Vector2 = world_pos
|
||||
value = {
|
||||
"kind": "prop",
|
||||
"target_prop_id": prop.stage_object_id,
|
||||
"approach_offset": approach - prop.global_position,
|
||||
"last_known_approach": approach,
|
||||
}
|
||||
Kind.PLAN_PLACE:
|
||||
var place_hit: Node2D = selection.hit_test(world_pos)
|
||||
if place_hit == null or (not place_hit is PropBlock and not place_hit is StickmanRig and not place_hit is TriggerArea):
|
||||
value = _snap_to_grid(world_pos, snap_enabled, grid_size)
|
||||
Kind.POSITION, Kind.THROW_POSITION:
|
||||
value = _snap_to_grid(world_pos, snap_enabled, grid_size)
|
||||
Kind.SET_DOWN_POSITION:
|
||||
|
||||
@@ -302,11 +302,16 @@ func _draw_pill(center: Vector2, text_value: String, accent: Color) -> void:
|
||||
if text_value.is_empty():
|
||||
return
|
||||
var font: Font = ThemeDB.fallback_font
|
||||
var text_size: Vector2 = font.get_string_size(text_value, HORIZONTAL_ALIGNMENT_LEFT, -1, 16)
|
||||
var rect := Rect2(center + Vector2(-text_size.x * 0.5 - _pill_padding, -13.0), Vector2(text_size.x + _pill_padding * 2.0, 26.0))
|
||||
var lines: PackedStringArray = text_value.split("\n", false)
|
||||
var text_width: float = 0.0
|
||||
for line: String in lines:
|
||||
text_width = maxf(text_width, font.get_string_size(line, HORIZONTAL_ALIGNMENT_LEFT, -1, 16).x)
|
||||
var rect_size: Vector2 = Vector2(text_width + _pill_padding * 2.0, maxf(26.0, float(lines.size()) * 18.0 + 8.0))
|
||||
var rect := Rect2(center - rect_size * 0.5, rect_size)
|
||||
draw_rect(rect, Color("#171B24"), true)
|
||||
draw_rect(rect, accent, false, 2.0)
|
||||
draw_string(font, rect.position + Vector2(10.0, 18.0), text_value, HORIZONTAL_ALIGNMENT_LEFT, -1, 16, Color.WHITE)
|
||||
for index: int in lines.size():
|
||||
draw_string(font, rect.position + Vector2(10.0, 17.0 + index * 18.0), lines[index], HORIZONTAL_ALIGNMENT_LEFT, -1, 16, Color.WHITE)
|
||||
|
||||
|
||||
func _draw_reach_marker(center: Vector2, glyph: String, accent: Color) -> void:
|
||||
|
||||
@@ -7,6 +7,7 @@ signal change_prop_requested
|
||||
signal capture_action_requested(action_id: StringName)
|
||||
|
||||
const MAX_BAR_WIDTH: float = 800.0
|
||||
const PLAN_CAPTURE_MAX_WIDTH: float = 440.0
|
||||
const SCENERY_CAPTURE_MAX_WIDTH: float = 900.0
|
||||
const VIEWPORT_WIDTH_RATIO: float = 0.68
|
||||
const HORIZONTAL_LAYOUT_MIN_WIDTH: float = 760.0
|
||||
@@ -53,6 +54,8 @@ var _safe_left: float = 0.0
|
||||
var _safe_right: float = 0.0
|
||||
var _scenery_edit_active: bool = false
|
||||
var _scenery_capture_active: bool = false
|
||||
var _plan_capture_active: bool = false
|
||||
var _plan_target_cancel_only: bool = false
|
||||
|
||||
|
||||
func configure(stage_theme: SandboxTheme, top_bar_height: float) -> void:
|
||||
@@ -73,6 +76,9 @@ func _ready() -> void:
|
||||
func set_capture_state(prop_selected: bool, prop_has_use_spots: bool = true) -> void:
|
||||
if _prompt == null:
|
||||
return
|
||||
_plan_capture_active = false
|
||||
_plan_target_cancel_only = false
|
||||
_prompt.visible = true
|
||||
_show_standard_capture_layout()
|
||||
_clear_custom_actions()
|
||||
if not prop_selected:
|
||||
@@ -88,10 +94,20 @@ func set_capture_state(prop_selected: bool, prop_has_use_spots: bool = true) ->
|
||||
|
||||
|
||||
func set_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
_plan_capture_active = true
|
||||
_plan_target_cancel_only = true
|
||||
_set_capture_actions(prompt, actions, false)
|
||||
|
||||
|
||||
func set_target_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
_plan_capture_active = false
|
||||
_plan_target_cancel_only = false
|
||||
_set_capture_actions(prompt, actions, false)
|
||||
|
||||
|
||||
func set_scenery_capture_actions(prompt: String, actions: Array[Dictionary]) -> void:
|
||||
_plan_capture_active = false
|
||||
_plan_target_cancel_only = false
|
||||
_set_capture_actions(prompt, actions, true)
|
||||
|
||||
|
||||
@@ -100,6 +116,7 @@ func _set_capture_actions(prompt: String, actions: Array[Dictionary], scenery_ca
|
||||
return
|
||||
_show_standard_capture_layout()
|
||||
_prompt.text = prompt
|
||||
_prompt.visible = not _plan_target_cancel_only
|
||||
_change_prop_button.visible = false
|
||||
_cancel_button.visible = false
|
||||
_clear_custom_actions()
|
||||
@@ -160,6 +177,9 @@ func set_scenery_edit_state(selected_count: int) -> void:
|
||||
return
|
||||
_scenery_capture_active = false
|
||||
_scenery_edit_active = true
|
||||
_plan_capture_active = false
|
||||
_plan_target_cancel_only = false
|
||||
_prompt.visible = true
|
||||
_clear_custom_actions()
|
||||
if _active_layout.get_parent() == self:
|
||||
_active_layout.visible = false
|
||||
@@ -198,6 +218,9 @@ func clear_horizontal_safe_bounds() -> void:
|
||||
|
||||
func hide_capture() -> void:
|
||||
visible = false
|
||||
_plan_capture_active = false
|
||||
_plan_target_cancel_only = false
|
||||
_prompt.visible = true
|
||||
_show_standard_capture_layout()
|
||||
|
||||
|
||||
@@ -245,7 +268,10 @@ func apply_theme() -> void:
|
||||
if body_font != null:
|
||||
button.add_theme_font_override("font", body_font)
|
||||
button.add_theme_font_size_override("font_size", body_size)
|
||||
_style_button(button, button.name.ends_with("Keep") or button.name.ends_with("Done"))
|
||||
if _plan_target_cancel_only:
|
||||
_style_plan_target_cancel_button(button)
|
||||
else:
|
||||
_style_button(button, button.name.ends_with("Keep") or button.name.ends_with("Done"))
|
||||
var button_height: float = maxf(DEFAULT_BUTTON_HEIGHT, float(_integer("stage_panels.action.min_target_size", 56, 48, 96)))
|
||||
_change_prop_button.custom_minimum_size.y = button_height
|
||||
_cancel_button.custom_minimum_size.y = button_height
|
||||
@@ -628,6 +654,23 @@ func _style_button(button: Button, prominent: bool) -> void:
|
||||
button.add_theme_color_override("font_focus_color", text_color)
|
||||
|
||||
|
||||
func _style_plan_target_cancel_button(button: Button) -> void:
|
||||
var accent: Color = _color("mode_colors.direct", Color("#FFB300"))
|
||||
var surface: Color = _color("stage_panels.action.surface_color", Color("#171B24"))
|
||||
var hover: Color = _color("stage_panels.action.hover_surface", Color("#303A4B"))
|
||||
var pressed: Color = _color("stage_panels.action.selected_surface", Color("#12394A"))
|
||||
var text_color: Color = _color("stage_panels.type.text_color", Color("#F5F7FA"))
|
||||
button.add_theme_stylebox_override("normal", _button_style(surface, accent, 2))
|
||||
button.add_theme_stylebox_override("hover", _button_style(hover, accent.lightened(0.1), 2))
|
||||
button.add_theme_stylebox_override("pressed", _button_style(pressed, accent, 2))
|
||||
button.add_theme_stylebox_override("hover_pressed", _button_style(hover, accent.lightened(0.1), 2))
|
||||
button.add_theme_stylebox_override("focus", _button_style(surface, accent, 3))
|
||||
button.add_theme_color_override("font_color", text_color)
|
||||
button.add_theme_color_override("font_hover_color", text_color)
|
||||
button.add_theme_color_override("font_pressed_color", text_color)
|
||||
button.add_theme_color_override("font_focus_color", text_color)
|
||||
|
||||
|
||||
func _button_style(background: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
style.bg_color = background
|
||||
@@ -651,6 +694,9 @@ func _layout_capture_bar() -> void:
|
||||
if _scenery_capture_active:
|
||||
_layout_scenery_capture_bar(viewport_size)
|
||||
return
|
||||
if _plan_target_cancel_only:
|
||||
_layout_plan_target_cancel_button(viewport_size)
|
||||
return
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var safe_left: float = inset
|
||||
var safe_right: float = viewport_size.x - inset
|
||||
@@ -658,25 +704,59 @@ func _layout_capture_bar() -> void:
|
||||
safe_left = clampf(_safe_left, inset, maxf(inset, viewport_size.x - inset))
|
||||
safe_right = clampf(_safe_right, safe_left + 1.0, maxf(safe_left + 1.0, viewport_size.x - inset))
|
||||
var available_width: float = maxf(1.0, safe_right - safe_left)
|
||||
var width: float = minf(MAX_BAR_WIDTH, minf(viewport_size.x * VIEWPORT_WIDTH_RATIO, available_width))
|
||||
var maximum_width: float = PLAN_CAPTURE_MAX_WIDTH if _plan_capture_active else MAX_BAR_WIDTH
|
||||
var width: float = minf(maximum_width, minf(viewport_size.x * VIEWPORT_WIDTH_RATIO, available_width))
|
||||
var narrow_compact: bool = width < NARROW_COMPACT_WIDTH
|
||||
custom_minimum_size.x = width
|
||||
if _plan_capture_active:
|
||||
custom_minimum_size.y = 0.0
|
||||
size.x = width
|
||||
clip_contents = true
|
||||
_prompt.add_theme_font_size_override("font_size", 18 if narrow_compact else clampi(_integer("stage_panels.type.title_size", 24, 18, 36) + 4, 24, 34))
|
||||
_prompt.max_lines_visible = 2 if narrow_compact else 3
|
||||
_prompt.add_theme_font_size_override("font_size", 19 if _plan_capture_active else (18 if narrow_compact else clampi(_integer("stage_panels.type.title_size", 24, 18, 36) + 4, 24, 34)))
|
||||
_prompt.max_lines_visible = 2 if _plan_capture_active or narrow_compact else 3
|
||||
_set_compact_panel_style(narrow_compact)
|
||||
if _plan_capture_active:
|
||||
_set_plan_capture_panel_style()
|
||||
var left: float = safe_left + (available_width - width) * 0.5 if not _has_horizontal_safe_bounds else safe_left
|
||||
position = Vector2(left, _top_bar_height + 56.0)
|
||||
var many_actions: bool = _custom_action_buttons.size() > 3
|
||||
var stack_actions: bool = width < ACTION_ROW_MIN_WIDTH or (many_actions and width < 620.0)
|
||||
var wrapped: bool = (_has_horizontal_safe_bounds and width < HORIZONTAL_LAYOUT_MIN_WIDTH and width >= ACTION_ROW_MIN_WIDTH) or (many_actions and not stack_actions)
|
||||
_set_layout(width < HORIZONTAL_LAYOUT_MIN_WIDTH or wrapped, stack_actions, wrapped)
|
||||
var stack_actions: bool = false if _plan_capture_active else (width < ACTION_ROW_MIN_WIDTH or (many_actions and width < 620.0))
|
||||
var wrapped: bool = false if _plan_capture_active else ((_has_horizontal_safe_bounds and width < HORIZONTAL_LAYOUT_MIN_WIDTH and width >= ACTION_ROW_MIN_WIDTH) or (many_actions and not stack_actions))
|
||||
var compact_layout: bool = false if _plan_capture_active else (width < HORIZONTAL_LAYOUT_MIN_WIDTH or wrapped)
|
||||
_set_layout(compact_layout, stack_actions, wrapped)
|
||||
_fit_custom_action_widths(width, wrapped)
|
||||
_apply_compact_action_labels(narrow_compact)
|
||||
call_deferred("_fit_height_to_content")
|
||||
|
||||
|
||||
func _layout_plan_target_cancel_button(viewport_size: Vector2) -> void:
|
||||
if _custom_action_buttons.is_empty():
|
||||
return
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var safe_left: float = inset
|
||||
var safe_right: float = viewport_size.x - inset
|
||||
if _has_horizontal_safe_bounds:
|
||||
safe_left = clampf(_safe_left, inset, maxf(inset, viewport_size.x - inset))
|
||||
safe_right = clampf(_safe_right, safe_left + 1.0, maxf(safe_left + 1.0, viewport_size.x - inset))
|
||||
var button: Button = _custom_action_buttons[0]
|
||||
var button_height: float = maxf(DEFAULT_BUTTON_HEIGHT, float(_integer("stage_panels.action.min_target_size", 56, 48, 96)))
|
||||
var available_width: float = maxf(1.0, safe_right - safe_left)
|
||||
var button_width: float = minf(148.0, available_width)
|
||||
button.custom_minimum_size = Vector2(button_width, button_height)
|
||||
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
button.add_theme_font_size_override("font_size", 19)
|
||||
_prompt.visible = false
|
||||
_set_layout(false, false)
|
||||
_actions_row.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||
_actions_row.add_theme_constant_override("separation", 0)
|
||||
custom_minimum_size = Vector2(button_width, 0.0)
|
||||
size = Vector2(button_width, button_height)
|
||||
position = Vector2(safe_left, _top_bar_height + 56.0)
|
||||
clip_contents = false
|
||||
_set_plan_capture_panel_style()
|
||||
call_deferred("_fit_height_to_content")
|
||||
|
||||
|
||||
func _layout_scenery_edit_capture_bar(viewport_size: Vector2) -> void:
|
||||
var inset: float = float(_integer("stage_panels.shell.edge_inset", 16, 12, 48))
|
||||
var safe_left: float = inset
|
||||
@@ -871,14 +951,19 @@ func _fit_custom_action_widths(width: float, wrapped: bool) -> void:
|
||||
var button_width: float = ACTION_BUTTON_WIDTH
|
||||
var body_size: int = clampi(_integer("stage_panels.type.body_size", 20, 16, 32) + 2, 20, 30)
|
||||
var action_font_size: int = maxi(18, body_size - (4 if wrapped else 0))
|
||||
if _plan_capture_active:
|
||||
button_width = 124.0
|
||||
action_font_size = 18
|
||||
if width < NARROW_COMPACT_WIDTH:
|
||||
button_width = 0.0
|
||||
button_width = 124.0 if _plan_capture_active else 0.0
|
||||
action_font_size = 16
|
||||
if wrapped and not _custom_action_buttons.is_empty():
|
||||
var gaps: float = float(maxi(0, _custom_action_buttons.size() - 1) * 12)
|
||||
button_width = minf(ACTION_BUTTON_WIDTH, maxf(144.0, (width - 44.0 - gaps) / float(_custom_action_buttons.size())))
|
||||
for button: Button in _custom_action_buttons:
|
||||
button.custom_minimum_size.x = button_width
|
||||
if _plan_capture_active:
|
||||
button.custom_minimum_size.y = 48.0
|
||||
button.add_theme_font_size_override("font_size", action_font_size)
|
||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL if width < NARROW_COMPACT_WIDTH else Control.SIZE_FILL
|
||||
|
||||
@@ -918,9 +1003,32 @@ func _set_compact_panel_style(compact: bool) -> void:
|
||||
panel_style.content_margin_bottom = 8.0 if compact else 14.0
|
||||
|
||||
|
||||
func _set_plan_capture_panel_style() -> void:
|
||||
if _plan_target_cancel_only:
|
||||
var cancel_style: StyleBoxFlat = StyleBoxFlat.new()
|
||||
cancel_style.bg_color = Color(0.0, 0.0, 0.0, 0.0)
|
||||
cancel_style.set_border_width_all(0)
|
||||
cancel_style.content_margin_left = 0.0
|
||||
cancel_style.content_margin_right = 0.0
|
||||
cancel_style.content_margin_top = 0.0
|
||||
cancel_style.content_margin_bottom = 0.0
|
||||
add_theme_stylebox_override("panel", cancel_style)
|
||||
return
|
||||
var panel_style: StyleBoxFlat = get_theme_stylebox("panel") as StyleBoxFlat
|
||||
if panel_style == null:
|
||||
return
|
||||
panel_style.border_width_left = 4
|
||||
panel_style.content_margin_left = 10.0
|
||||
panel_style.content_margin_right = 10.0
|
||||
panel_style.content_margin_top = 8.0
|
||||
panel_style.content_margin_bottom = 8.0
|
||||
|
||||
|
||||
func _fit_height_to_content() -> void:
|
||||
if not is_inside_tree() or not visible:
|
||||
return
|
||||
if _plan_capture_active:
|
||||
custom_minimum_size.y = 0.0
|
||||
var target_height: float = _active_layout.get_combined_minimum_size().y
|
||||
var panel_style: StyleBox = get_theme_stylebox("panel")
|
||||
if panel_style != null:
|
||||
|
||||
Reference in New Issue
Block a user