added weapon wheel and model texture creator
This commit is contained in:
1
addons/anthonyec.camera_preview/GuiResizerTopLeft.svg
Normal file
1
addons/anthonyec.camera_preview/GuiResizerTopLeft.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4 11V4h7" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" stroke-opacity=".6"/></svg>
|
||||
|
After Width: | Height: | Size: 221 B |
37
addons/anthonyec.camera_preview/GuiResizerTopLeft.svg.import
Normal file
37
addons/anthonyec.camera_preview/GuiResizerTopLeft.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btc01wc11tiid"
|
||||
path="res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg"
|
||||
dest_files=["res://.godot/imported/GuiResizerTopLeft.svg-eb563f557424c74239e878a1213a5bf4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/anthonyec.camera_preview/GuiResizerTopRight.svg
Normal file
1
addons/anthonyec.camera_preview/GuiResizerTopRight.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M12 11V4h-7" fill="none" stroke="#fff" stroke-opacity=".6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://04l05jxuyt7k"
|
||||
path="res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg"
|
||||
dest_files=["res://.godot/imported/GuiResizerTopRight.svg-cc1dc8556d51357c5eb0b01d09d8f049.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
addons/anthonyec.camera_preview/Pin.svg
Normal file
1
addons/anthonyec.camera_preview/Pin.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m4 1v1l1 1v3h6v-3l1-1v-1zm1 6-2 3h10l-2-3zm2 4v2l1 2 1-2v-2z" fill="#e0e0e0"/></svg>
|
||||
|
After Width: | Height: | Size: 177 B |
37
addons/anthonyec.camera_preview/Pin.svg.import
Normal file
37
addons/anthonyec.camera_preview/Pin.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://do6d60od41vmg"
|
||||
path="res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/anthonyec.camera_preview/Pin.svg"
|
||||
dest_files=["res://.godot/imported/Pin.svg-83b09f5c00a829c5d8b136bf5bae65bc.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=2.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
7
addons/anthonyec.camera_preview/plugin.cfg
Normal file
7
addons/anthonyec.camera_preview/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Little Camera Preview"
|
||||
description="Shows a picture-in-picture preview of the selected 2D or 3D camera"
|
||||
author="Anthony Cossins"
|
||||
version="1.3"
|
||||
script="plugin.gd"
|
||||
87
addons/anthonyec.camera_preview/plugin.gd
Normal file
87
addons/anthonyec.camera_preview/plugin.gd
Normal file
@@ -0,0 +1,87 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const preview_scene = preload("res://addons/anthonyec.camera_preview/preview.tscn")
|
||||
|
||||
var preview: CameraPreview
|
||||
var current_main_screen_name: String
|
||||
|
||||
func _enter_tree() -> void:
|
||||
main_screen_changed.connect(_on_main_screen_changed)
|
||||
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
|
||||
|
||||
# Initialise preview panel and add to main screen.
|
||||
preview = preview_scene.instantiate() as CameraPreview
|
||||
preview.request_hide()
|
||||
|
||||
var main_screen = EditorInterface.get_editor_main_screen()
|
||||
main_screen.add_child(preview)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if preview:
|
||||
preview.queue_free()
|
||||
|
||||
func _ready() -> void:
|
||||
# TODO: Currently there is no API to get the main screen name without
|
||||
# listening to the `EditorPlugin.main_screen_changed` signal:
|
||||
# https://github.com/godotengine/godot-proposals/issues/2081
|
||||
EditorInterface.set_main_screen_editor("Script")
|
||||
EditorInterface.set_main_screen_editor("3D")
|
||||
|
||||
func _on_main_screen_changed(screen_name: String) -> void:
|
||||
current_main_screen_name = screen_name
|
||||
|
||||
# TODO: Bit of a hack to prevent pinned staying between view changes on the same scene.
|
||||
preview.unlink_camera()
|
||||
_on_editor_selection_changed()
|
||||
|
||||
func _on_editor_selection_changed() -> void:
|
||||
if not is_main_screen_viewport():
|
||||
# This hides the preview "container" and not the preview itself, allowing
|
||||
# any locked previews to remain visible once switching back to 3D tab.
|
||||
preview.visible = false
|
||||
return
|
||||
|
||||
preview.visible = true
|
||||
|
||||
var selected_nodes = EditorInterface.get_selection().get_selected_nodes()
|
||||
|
||||
var selected_camera_3d: Camera3D = find_camera_3d_or_null(selected_nodes)
|
||||
var selected_camera_2d: Camera2D = find_camera_2d_or_null(selected_nodes)
|
||||
|
||||
if selected_camera_3d and current_main_screen_name == "3D":
|
||||
preview.link_with_camera_3d(selected_camera_3d)
|
||||
preview.request_show()
|
||||
|
||||
elif selected_camera_2d and current_main_screen_name == "2D":
|
||||
preview.link_with_camera_2d(selected_camera_2d)
|
||||
preview.request_show()
|
||||
|
||||
else:
|
||||
preview.request_hide()
|
||||
|
||||
func is_main_screen_viewport() -> bool:
|
||||
return current_main_screen_name == "3D" or current_main_screen_name == "2D"
|
||||
|
||||
func find_camera_3d_or_null(nodes: Array[Node]) -> Camera3D:
|
||||
var camera: Camera3D
|
||||
|
||||
for node in nodes:
|
||||
if node is Camera3D:
|
||||
camera = node as Camera3D
|
||||
break
|
||||
|
||||
return camera
|
||||
|
||||
func find_camera_2d_or_null(nodes: Array[Node]) -> Camera2D:
|
||||
var camera: Camera2D
|
||||
|
||||
for node in nodes:
|
||||
if node is Camera2D:
|
||||
camera = node as Camera2D
|
||||
break
|
||||
|
||||
return camera
|
||||
|
||||
func _on_selected_camera_3d_tree_exiting() -> void:
|
||||
preview.unlink_camera()
|
||||
404
addons/anthonyec.camera_preview/preview.gd
Normal file
404
addons/anthonyec.camera_preview/preview.gd
Normal file
@@ -0,0 +1,404 @@
|
||||
@tool
|
||||
|
||||
class_name CameraPreview
|
||||
extends Control
|
||||
|
||||
enum CameraType {
|
||||
CAMERA_2D,
|
||||
CAMERA_3D
|
||||
}
|
||||
|
||||
enum PinnedPosition {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
}
|
||||
|
||||
enum InteractionState {
|
||||
NONE,
|
||||
RESIZE,
|
||||
DRAG,
|
||||
|
||||
# Animation is split into 2 seperate states so that the tween is only
|
||||
# invoked once in the "start" state.
|
||||
START_ANIMATE_INTO_PLACE,
|
||||
ANIMATE_INTO_PLACE,
|
||||
}
|
||||
|
||||
const margin_3d: Vector2 = Vector2(10, 10)
|
||||
const margin_2d: Vector2 = Vector2(20, 15)
|
||||
const panel_margin: float = 2
|
||||
const min_panel_size: float = 250
|
||||
|
||||
@onready var panel: Panel = %Panel
|
||||
@onready var placeholder: Panel = %Placeholder
|
||||
@onready var preview_camera_3d: Camera3D = %Camera3D
|
||||
@onready var preview_camera_2d: Camera2D = %Camera2D
|
||||
@onready var sub_viewport: SubViewport = %SubViewport
|
||||
@onready var sub_viewport_text_rect: TextureRect = %TextureRect
|
||||
@onready var resize_left_handle: Button = %ResizeLeftHandle
|
||||
@onready var resize_right_handle: Button = %ResizeRightHandle
|
||||
@onready var lock_button: Button = %LockButton
|
||||
@onready var gradient: TextureRect = %Gradient
|
||||
@onready var viewport_margin_container: MarginContainer = %ViewportMarginContainer
|
||||
@onready var overlay_margin_container: MarginContainer = %OverlayMarginContainer
|
||||
@onready var overlay_container: Control = %OverlayContainer
|
||||
|
||||
var camera_type: CameraType = CameraType.CAMERA_3D
|
||||
var pinned_position: PinnedPosition = PinnedPosition.RIGHT
|
||||
var viewport_ratio: float = 1
|
||||
var editor_scale: float = EditorInterface.get_editor_scale()
|
||||
var is_locked: bool
|
||||
var show_controls: bool
|
||||
var selected_camera_3d: Camera3D
|
||||
var selected_camera_2d: Camera2D
|
||||
|
||||
var state: InteractionState = InteractionState.NONE
|
||||
var initial_mouse_position: Vector2
|
||||
var initial_panel_size: Vector2
|
||||
var initial_panel_position: Vector2
|
||||
|
||||
func _ready() -> void:
|
||||
# Set initial width.
|
||||
panel.size.x = min_panel_size * editor_scale
|
||||
|
||||
# Setting texture to viewport in code instead of directly in the editor
|
||||
# because otherwise an error "Path to node is invalid: Panel/SubViewport"
|
||||
# on first load. This is harmless but doesn't look great.
|
||||
#
|
||||
# This is a known issue:
|
||||
# https://github.com/godotengine/godot/issues/27790#issuecomment-499740220
|
||||
sub_viewport_text_rect.texture = sub_viewport.get_texture()
|
||||
|
||||
# From what I can tell there's something wrong with how an editor theme
|
||||
# scales when used within a plugin. It seems to ignore the screen scale.
|
||||
# For instance, a 30x30px button will appear tiny on a retina display.
|
||||
#
|
||||
# Someone else had the issue with no luck:
|
||||
# https://forum.godotengine.org/t/how-to-scale-plugin-controls-to-look-the-same-in-4k-as-1080p/36151
|
||||
#
|
||||
# And seems Dialogic also scales buttons manually:
|
||||
# https://github.com/dialogic-godot/dialogic/blob/master/addons/dialogic/Editor/Common/sidebar.gd#L25C6-L38
|
||||
#
|
||||
# Maybe I don't know the correct way to do it, so for now the workaround is
|
||||
# to set the correct size in code using screen scale.
|
||||
var button_size = Vector2(30, 30) * editor_scale
|
||||
var margin_size: float = panel_margin * editor_scale
|
||||
|
||||
resize_left_handle.size = button_size
|
||||
resize_left_handle.pivot_offset = Vector2(0, 0) * editor_scale
|
||||
|
||||
resize_right_handle.size = button_size
|
||||
resize_right_handle.pivot_offset = Vector2(30, 30) * editor_scale
|
||||
|
||||
lock_button.size = button_size
|
||||
lock_button.pivot_offset = Vector2(0, 30) * editor_scale
|
||||
|
||||
viewport_margin_container.add_theme_constant_override("margin_left", margin_size)
|
||||
viewport_margin_container.add_theme_constant_override("margin_top", margin_size)
|
||||
viewport_margin_container.add_theme_constant_override("margin_right", margin_size)
|
||||
viewport_margin_container.add_theme_constant_override("margin_bottom", margin_size)
|
||||
|
||||
overlay_margin_container.add_theme_constant_override("margin_left", margin_size)
|
||||
overlay_margin_container.add_theme_constant_override("margin_top", margin_size)
|
||||
overlay_margin_container.add_theme_constant_override("margin_right", margin_size)
|
||||
overlay_margin_container.add_theme_constant_override("margin_bottom", margin_size)
|
||||
|
||||
# Parent node overlay size is not available on first ready, need to wait a
|
||||
# frame for it to be drawn.
|
||||
await get_tree().process_frame
|
||||
|
||||
# Anchors are set in code because setting them in the editor UI doesn't take
|
||||
# editor scale into account.
|
||||
resize_left_handle.position = Vector2(0, 0)
|
||||
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
|
||||
resize_right_handle.position = Vector2(overlay_container.size.x - button_size.x, 0)
|
||||
resize_right_handle.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
||||
|
||||
lock_button.position = Vector2(0, overlay_container.size.y - button_size.y)
|
||||
lock_button.set_anchors_preset(Control.PRESET_BOTTOM_LEFT)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not visible: return
|
||||
|
||||
match state:
|
||||
InteractionState.NONE:
|
||||
panel.size = get_clamped_size(panel.size)
|
||||
panel.position = get_pinned_position(pinned_position)
|
||||
|
||||
InteractionState.RESIZE:
|
||||
var delta_mouse_position = initial_mouse_position - get_global_mouse_position()
|
||||
var resized_size = panel.size
|
||||
|
||||
if pinned_position == PinnedPosition.LEFT:
|
||||
resized_size = initial_panel_size - delta_mouse_position
|
||||
|
||||
if pinned_position == PinnedPosition.RIGHT:
|
||||
resized_size = initial_panel_size + delta_mouse_position
|
||||
|
||||
panel.size = get_clamped_size(resized_size)
|
||||
panel.position = get_pinned_position(pinned_position)
|
||||
|
||||
InteractionState.DRAG:
|
||||
placeholder.size = panel.size
|
||||
|
||||
var global_mouse_position = get_global_mouse_position()
|
||||
var offset = initial_mouse_position - initial_panel_position
|
||||
|
||||
panel.global_position = global_mouse_position - offset
|
||||
|
||||
if global_mouse_position.x < global_position.x + size.x / 2:
|
||||
pinned_position = PinnedPosition.LEFT
|
||||
else:
|
||||
pinned_position = PinnedPosition.RIGHT
|
||||
|
||||
placeholder.position = get_pinned_position(pinned_position)
|
||||
|
||||
InteractionState.START_ANIMATE_INTO_PLACE:
|
||||
var final_position: Vector2 = get_pinned_position(pinned_position)
|
||||
var tween = get_tree().create_tween()
|
||||
|
||||
tween.set_ease(Tween.EASE_OUT)
|
||||
tween.set_trans(Tween.TRANS_CUBIC)
|
||||
tween.tween_property(panel, "position", final_position, 0.3)
|
||||
|
||||
tween.finished.connect(func():
|
||||
panel.position = final_position
|
||||
state = InteractionState.NONE
|
||||
)
|
||||
|
||||
state = InteractionState.ANIMATE_INTO_PLACE
|
||||
|
||||
# I couldn't get `mouse_entered` and `mouse_exited` events to work
|
||||
# nicely, so I use rect method instead. Plus using this method it's easy to
|
||||
# grow the hit area size.
|
||||
var panel_hover_rect = Rect2(panel.global_position, panel.size)
|
||||
panel_hover_rect = panel_hover_rect.grow(40)
|
||||
|
||||
var mouse_position = get_global_mouse_position()
|
||||
|
||||
show_controls = state != InteractionState.NONE or panel_hover_rect.has_point(mouse_position)
|
||||
|
||||
# UI visibility.
|
||||
resize_left_handle.visible = show_controls and pinned_position == PinnedPosition.RIGHT
|
||||
resize_right_handle.visible = show_controls and pinned_position == PinnedPosition.LEFT
|
||||
lock_button.visible = show_controls or is_locked
|
||||
placeholder.visible = state == InteractionState.DRAG or state == InteractionState.ANIMATE_INTO_PLACE
|
||||
gradient.visible = show_controls
|
||||
|
||||
# Sync camera settings.
|
||||
if camera_type == CameraType.CAMERA_3D and selected_camera_3d:
|
||||
sub_viewport.size = panel.size
|
||||
|
||||
# Sync position and rotation without using a `RemoteTransform` node
|
||||
# because if you save a camera as a scene, the remote transform node will
|
||||
# be stored within the scene. Also it's harder to keep the remote
|
||||
# transform `remote_path` up-to-date with scene changes, which causes
|
||||
# many errors.
|
||||
preview_camera_3d.global_position = selected_camera_3d.global_position
|
||||
preview_camera_3d.global_rotation = selected_camera_3d.global_rotation
|
||||
|
||||
preview_camera_3d.fov = selected_camera_3d.fov
|
||||
preview_camera_3d.projection = selected_camera_3d.projection
|
||||
preview_camera_3d.size = selected_camera_3d.size
|
||||
preview_camera_3d.cull_mask = selected_camera_3d.cull_mask
|
||||
preview_camera_3d.keep_aspect = selected_camera_3d.keep_aspect
|
||||
preview_camera_3d.near = selected_camera_3d.near
|
||||
preview_camera_3d.far = selected_camera_3d.far
|
||||
preview_camera_3d.h_offset = selected_camera_3d.h_offset
|
||||
preview_camera_3d.v_offset = selected_camera_3d.v_offset
|
||||
preview_camera_3d.attributes = selected_camera_3d.attributes
|
||||
preview_camera_3d.environment = selected_camera_3d.environment
|
||||
|
||||
if camera_type == CameraType.CAMERA_2D and selected_camera_2d:
|
||||
var project_window_size = get_project_window_size()
|
||||
var ratio = project_window_size.x / panel.size.x
|
||||
|
||||
# TODO: Is there a better way to fix this?
|
||||
# The camera border is visible sometimes due to pixel rounding.
|
||||
# Subtract 1px from right and bottom to hide this.
|
||||
var hide_camera_border_fix = Vector2(1, 1)
|
||||
|
||||
sub_viewport.size = panel.size
|
||||
sub_viewport.size_2d_override = (panel.size - hide_camera_border_fix) * ratio
|
||||
sub_viewport.size_2d_override_stretch = true
|
||||
|
||||
preview_camera_2d.global_position = selected_camera_2d.global_position
|
||||
preview_camera_2d.global_rotation = selected_camera_2d.global_rotation
|
||||
|
||||
preview_camera_2d.offset = selected_camera_2d.offset
|
||||
preview_camera_2d.zoom = selected_camera_2d.zoom
|
||||
preview_camera_2d.ignore_rotation = selected_camera_2d.ignore_rotation
|
||||
preview_camera_2d.anchor_mode = selected_camera_2d.anchor_mode
|
||||
preview_camera_2d.limit_left = selected_camera_2d.limit_left
|
||||
preview_camera_2d.limit_right = selected_camera_2d.limit_right
|
||||
preview_camera_2d.limit_top = selected_camera_2d.limit_top
|
||||
preview_camera_2d.limit_bottom = selected_camera_2d.limit_bottom
|
||||
|
||||
func link_with_camera_3d(camera_3d: Camera3D) -> void:
|
||||
# TODO: Camera may not be ready since this method is called in `_enter_tree`
|
||||
# in the plugin because of a workaround for:
|
||||
# https://github.com/godotengine/godot-proposals/issues/2081
|
||||
if not preview_camera_3d:
|
||||
return request_hide()
|
||||
|
||||
var is_different_camera = camera_3d != preview_camera_3d
|
||||
|
||||
# TODO: A bit messy.
|
||||
if is_different_camera:
|
||||
if preview_camera_3d.tree_exiting.is_connected(unlink_camera):
|
||||
preview_camera_3d.tree_exiting.disconnect(unlink_camera)
|
||||
|
||||
if not camera_3d.tree_exiting.is_connected(unlink_camera):
|
||||
camera_3d.tree_exiting.connect(unlink_camera)
|
||||
|
||||
sub_viewport.disable_3d = false
|
||||
sub_viewport.world_3d = camera_3d.get_world_3d()
|
||||
|
||||
selected_camera_3d = camera_3d
|
||||
camera_type = CameraType.CAMERA_3D
|
||||
|
||||
func link_with_camera_2d(camera_2d: Camera2D) -> void:
|
||||
if not preview_camera_2d:
|
||||
return request_hide()
|
||||
|
||||
var is_different_camera = camera_2d != preview_camera_2d
|
||||
|
||||
# TODO: A bit messy.
|
||||
if is_different_camera:
|
||||
if preview_camera_2d.tree_exiting.is_connected(unlink_camera):
|
||||
preview_camera_2d.tree_exiting.disconnect(unlink_camera)
|
||||
|
||||
if not camera_2d.tree_exiting.is_connected(unlink_camera):
|
||||
camera_2d.tree_exiting.connect(unlink_camera)
|
||||
|
||||
sub_viewport.disable_3d = true
|
||||
sub_viewport.world_2d = camera_2d.get_world_2d()
|
||||
|
||||
selected_camera_2d = camera_2d
|
||||
camera_type = CameraType.CAMERA_2D
|
||||
|
||||
func unlink_camera() -> void:
|
||||
if selected_camera_3d:
|
||||
selected_camera_3d = null
|
||||
|
||||
if selected_camera_2d:
|
||||
selected_camera_2d = null
|
||||
|
||||
is_locked = false
|
||||
lock_button.button_pressed = false
|
||||
|
||||
func request_hide() -> void:
|
||||
if is_locked: return
|
||||
visible = false
|
||||
|
||||
func request_show() -> void:
|
||||
visible = true
|
||||
|
||||
func get_pinned_position(pinned_position: PinnedPosition) -> Vector2:
|
||||
var margin: Vector2 = margin_3d * editor_scale
|
||||
|
||||
if camera_type == CameraType.CAMERA_2D:
|
||||
margin = margin_2d * editor_scale
|
||||
|
||||
match pinned_position:
|
||||
PinnedPosition.LEFT:
|
||||
return Vector2.ZERO - Vector2(0, panel.size.y) - Vector2(-margin.x, margin.y)
|
||||
PinnedPosition.RIGHT:
|
||||
return size - panel.size - margin
|
||||
_:
|
||||
assert(false, "Unknown pinned position %s" % str(pinned_position))
|
||||
|
||||
return Vector2.ZERO
|
||||
|
||||
func get_clamped_size(desired_size: Vector2) -> Vector2:
|
||||
var viewport_ratio = get_project_window_ratio()
|
||||
var editor_viewport_size = get_editor_viewport_size()
|
||||
|
||||
var max_bounds = Vector2(
|
||||
editor_viewport_size.x * 0.6,
|
||||
editor_viewport_size.y * 0.8
|
||||
)
|
||||
|
||||
var clamped_size = desired_size
|
||||
|
||||
# Apply aspect ratio.
|
||||
clamped_size = Vector2(clamped_size.x, clamped_size.x * viewport_ratio)
|
||||
|
||||
# Clamp the max size while respecting the aspect ratio.
|
||||
if clamped_size.y >= max_bounds.y:
|
||||
clamped_size.x = max_bounds.y / viewport_ratio
|
||||
clamped_size.y = max_bounds.y
|
||||
|
||||
if clamped_size.x >= max_bounds.x:
|
||||
clamped_size.x = max_bounds.x
|
||||
clamped_size.y = max_bounds.x * viewport_ratio
|
||||
|
||||
# Clamp the min size based on if it's portrait or landscape. Portrait min
|
||||
# size should be based on it's height. Landscape min size is based on it's
|
||||
# width instead. Applying min width to a portrait size would make it too big.
|
||||
var is_portrait = viewport_ratio > 1
|
||||
|
||||
if is_portrait and clamped_size.y <= min_panel_size * editor_scale:
|
||||
clamped_size.x = min_panel_size / viewport_ratio
|
||||
clamped_size.y = min_panel_size
|
||||
clamped_size = clamped_size * editor_scale
|
||||
|
||||
if not is_portrait and clamped_size.x <= min_panel_size * editor_scale:
|
||||
clamped_size.x = min_panel_size
|
||||
clamped_size.y = min_panel_size * viewport_ratio
|
||||
clamped_size = clamped_size * editor_scale
|
||||
|
||||
# Round down to avoid sub-pixel artifacts, mainly seen around the margins.
|
||||
return clamped_size.floor()
|
||||
|
||||
func get_project_window_size() -> Vector2:
|
||||
var window_width = float(ProjectSettings.get_setting("display/window/size/viewport_width"))
|
||||
var window_height = float(ProjectSettings.get_setting("display/window/size/viewport_height"))
|
||||
|
||||
return Vector2(window_width, window_height)
|
||||
|
||||
func get_project_window_ratio() -> float:
|
||||
var project_window_size = get_project_window_size()
|
||||
|
||||
return project_window_size.y / project_window_size.x
|
||||
|
||||
func get_editor_viewport_size() -> Vector2:
|
||||
var fallback_size = EditorInterface.get_editor_main_screen().size
|
||||
|
||||
# There isn't an API for getting the viewport node. Instead it has to be
|
||||
# found by checking the parent's parent of the subviewport and find
|
||||
# the correct node based on name and class.
|
||||
var editor_sub_viewport_3d = EditorInterface.get_editor_viewport_3d(0)
|
||||
var editor_viewport_container = editor_sub_viewport_3d.get_parent().get_parent().get_parent()
|
||||
|
||||
# Early return incase editor tree structure has changed.
|
||||
if editor_viewport_container.get_class() != "Node3DEditorViewportContainer":
|
||||
return fallback_size
|
||||
|
||||
return editor_viewport_container.size
|
||||
|
||||
func _on_resize_handle_button_down() -> void:
|
||||
if state != InteractionState.NONE: return
|
||||
|
||||
state = InteractionState.RESIZE
|
||||
initial_mouse_position = get_global_mouse_position()
|
||||
initial_panel_size = panel.size
|
||||
|
||||
func _on_resize_handle_button_up() -> void:
|
||||
state = InteractionState.NONE
|
||||
|
||||
func _on_drag_handle_button_down() -> void:
|
||||
if state != InteractionState.NONE: return
|
||||
|
||||
state = InteractionState.DRAG
|
||||
initial_mouse_position = get_global_mouse_position()
|
||||
initial_panel_position = panel.global_position
|
||||
|
||||
func _on_drag_handle_button_up() -> void:
|
||||
if state != InteractionState.DRAG: return
|
||||
|
||||
state = InteractionState.START_ANIMATE_INTO_PLACE
|
||||
|
||||
func _on_lock_button_pressed() -> void:
|
||||
is_locked = !is_locked
|
||||
200
addons/anthonyec.camera_preview/preview.tscn
Normal file
200
addons/anthonyec.camera_preview/preview.tscn
Normal file
@@ -0,0 +1,200 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://xybmfvufjuv"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/anthonyec.camera_preview/preview.gd" id="1_6b32r"]
|
||||
[ext_resource type="Texture2D" uid="uid://do6d60od41vmg" path="res://addons/anthonyec.camera_preview/Pin.svg" id="2_p0pa8"]
|
||||
[ext_resource type="Texture2D" uid="uid://btc01wc11tiid" path="res://addons/anthonyec.camera_preview/GuiResizerTopLeft.svg" id="2_t64ej"]
|
||||
[ext_resource type="Texture2D" uid="uid://04l05jxuyt7k" path="res://addons/anthonyec.camera_preview/GuiResizerTopRight.svg" id="3_6yuab"]
|
||||
|
||||
[sub_resource type="ViewportTexture" id="ViewportTexture_hchdq"]
|
||||
viewport_path = NodePath("Panel/SubViewport")
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_11p6r"]
|
||||
offsets = PackedFloat32Array(0, 0.3, 0.6, 1)
|
||||
colors = PackedColorArray(0, 0, 0, 0.235294, 0, 0, 0, 0.0784314, 0, 0, 0, 0.0784314, 0, 0, 0, 0.235294)
|
||||
|
||||
[sub_resource type="GradientTexture2D" id="GradientTexture2D_4dkve"]
|
||||
gradient = SubResource("Gradient_11p6r")
|
||||
width = 256
|
||||
height = 256
|
||||
fill_to = Vector2(2.08165e-12, 1)
|
||||
|
||||
[node name="Preview" type="Control"]
|
||||
z_index = 999
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_6b32r")
|
||||
|
||||
[node name="Placeholder" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
modulate = Color(1, 1, 1, 0.705882)
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -40.0
|
||||
offset_top = -40.0
|
||||
offset_right = 410.0
|
||||
offset_bottom = 410.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="Panel" type="Panel" parent="."]
|
||||
unique_name_in_owner = true
|
||||
clip_contents = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 3
|
||||
anchor_left = 1.0
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -520.0
|
||||
offset_top = -908.889
|
||||
offset_right = -20.0
|
||||
offset_bottom = -20.0
|
||||
grow_horizontal = 0
|
||||
grow_vertical = 0
|
||||
pivot_offset = Vector2(450, 300)
|
||||
|
||||
[node name="SubViewport" type="SubViewport" parent="Panel"]
|
||||
unique_name_in_owner = true
|
||||
handle_input_locally = false
|
||||
gui_disable_input = true
|
||||
size_2d_override_stretch = true
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="Panel/SubViewport"]
|
||||
unique_name_in_owner = true
|
||||
current = true
|
||||
|
||||
[node name="Camera2D" type="Camera2D" parent="Panel/SubViewport"]
|
||||
unique_name_in_owner = true
|
||||
ignore_rotation = false
|
||||
|
||||
[node name="ViewportMarginContainer" type="MarginContainer" parent="Panel"]
|
||||
unique_name_in_owner = true
|
||||
clip_contents = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="TextureRect" type="TextureRect" parent="Panel/ViewportMarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
texture = SubResource("ViewportTexture_hchdq")
|
||||
expand_mode = 1
|
||||
|
||||
[node name="Gradient" type="TextureRect" parent="Panel"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
texture = SubResource("GradientTexture2D_4dkve")
|
||||
|
||||
[node name="OverlayMarginContainer" type="MarginContainer" parent="Panel"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
theme_override_constants/margin_left = 4
|
||||
theme_override_constants/margin_top = 4
|
||||
theme_override_constants/margin_right = 4
|
||||
theme_override_constants/margin_bottom = 4
|
||||
|
||||
[node name="OverlayContainer" type="Control" parent="Panel/OverlayMarginContainer"]
|
||||
unique_name_in_owner = true
|
||||
clip_contents = true
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="DragHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
focus_mode = 0
|
||||
flat = true
|
||||
|
||||
[node name="ResizeLeftHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
offset_right = 60.0
|
||||
offset_bottom = 60.0
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 0
|
||||
mouse_default_cursor_shape = 12
|
||||
icon = ExtResource("2_t64ej")
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="ResizeRightHandle" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 1
|
||||
anchor_left = 1.0
|
||||
anchor_right = 1.0
|
||||
offset_left = -60.0
|
||||
offset_bottom = 60.0
|
||||
pivot_offset = Vector2(60, 60)
|
||||
size_flags_horizontal = 8
|
||||
size_flags_vertical = 0
|
||||
mouse_default_cursor_shape = 11
|
||||
icon = ExtResource("3_6yuab")
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[node name="LockButton" type="Button" parent="Panel/OverlayMarginContainer/OverlayContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 2
|
||||
anchor_top = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -60.0
|
||||
offset_right = 60.0
|
||||
pivot_offset = Vector2(0, 60)
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 8
|
||||
tooltip_text = "Always Show Preview"
|
||||
toggle_mode = true
|
||||
icon = ExtResource("2_p0pa8")
|
||||
flat = true
|
||||
icon_alignment = 1
|
||||
expand_icon = true
|
||||
|
||||
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_down"]
|
||||
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_button_up"]
|
||||
[connection signal="renamed" from="Panel/OverlayMarginContainer/OverlayContainer/DragHandle" to="." method="_on_drag_handle_renamed"]
|
||||
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_down"]
|
||||
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeLeftHandle" to="." method="_on_resize_handle_button_up"]
|
||||
[connection signal="button_down" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_down"]
|
||||
[connection signal="button_up" from="Panel/OverlayMarginContainer/OverlayContainer/ResizeRightHandle" to="." method="_on_resize_handle_button_up"]
|
||||
[connection signal="pressed" from="Panel/OverlayMarginContainer/OverlayContainer/LockButton" to="." method="_on_lock_button_pressed"]
|
||||
Reference in New Issue
Block a user