mirror of
https://github.com/Orama-Interactive/Pixelorama.git
synced 2025-02-07 19:09:50 +00:00
ce7a5e77ba
True by default, when set to false the UI uses multiple windows
1139 lines
48 KiB
GDScript
1139 lines
48 KiB
GDScript
extends Node
|
||
|
||
## The Global autoload of Pixelorama.
|
||
##
|
||
## This Autoload contains signals, enums, constants, variables and
|
||
## references to many UI elements used within Pixelorama.
|
||
|
||
signal pixelorama_opened ## Emitted as soon as Pixelorama fully opens up.
|
||
signal pixelorama_about_to_close ## Emitted just before Pixelorama is about to close.
|
||
signal project_created(project: Project) ## Emitted when a new project class is initialized.
|
||
signal project_about_to_switch ## Emitted before a project is about to be switched
|
||
signal project_switched ## Emitted whenever you switch to some other project tab.
|
||
signal cel_switched ## Emitted whenever you select a different cel.
|
||
signal project_changed(project: Project) ## Emitted when project data is modified.
|
||
|
||
enum LayerTypes { PIXEL, GROUP, THREE_D }
|
||
enum GridTypes { CARTESIAN, ISOMETRIC, ALL }
|
||
## ## Used to tell whether a color is being taken from the current theme,
|
||
## or if it is a custom color.
|
||
enum ColorFrom { THEME, CUSTOM }
|
||
enum ButtonSize { SMALL, BIG }
|
||
enum MeasurementMode { NONE, MOVE }
|
||
|
||
## Enumeration of items present in the File Menu.
|
||
enum FileMenu { NEW, OPEN, OPEN_LAST_PROJECT, RECENT, SAVE, SAVE_AS, EXPORT, EXPORT_AS, QUIT }
|
||
## Enumeration of items present in the Edit Menu.
|
||
enum EditMenu { UNDO, REDO, COPY, CUT, PASTE, PASTE_IN_PLACE, DELETE, NEW_BRUSH, PREFERENCES }
|
||
## Enumeration of items present in the View Menu.
|
||
enum ViewMenu {
|
||
TILE_MODE,
|
||
TILE_MODE_OFFSETS,
|
||
GREYSCALE_VIEW,
|
||
MIRROR_VIEW,
|
||
SHOW_GRID,
|
||
SHOW_PIXEL_GRID,
|
||
SHOW_RULERS,
|
||
SHOW_GUIDES,
|
||
SHOW_MOUSE_GUIDES,
|
||
DISPLAY_LAYER_EFFECTS,
|
||
SNAP_TO,
|
||
}
|
||
## Enumeration of items present in the Window Menu.
|
||
enum WindowMenu { WINDOW_OPACITY, PANELS, LAYOUTS, MOVABLE_PANELS, ZEN_MODE, FULLSCREEN_MODE }
|
||
## Enumeration of items present in the Image Menu.
|
||
enum ImageMenu {
|
||
RESIZE_CANVAS,
|
||
OFFSET_IMAGE,
|
||
SCALE_IMAGE,
|
||
CROP_TO_SELECTION,
|
||
CROP_TO_CONTENT,
|
||
FLIP,
|
||
ROTATE,
|
||
OUTLINE,
|
||
DROP_SHADOW,
|
||
INVERT_COLORS,
|
||
DESATURATION,
|
||
HSV,
|
||
POSTERIZE,
|
||
GRADIENT,
|
||
GRADIENT_MAP,
|
||
SHADER
|
||
}
|
||
## Enumeration of items present in the Select Menu.
|
||
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE }
|
||
## Enumeration of items present in the Help Menu.
|
||
enum HelpMenu {
|
||
VIEW_SPLASH_SCREEN,
|
||
ONLINE_DOCS,
|
||
ISSUE_TRACKER,
|
||
OPEN_LOGS_FOLDER,
|
||
CHANGELOG,
|
||
ABOUT_PIXELORAMA,
|
||
SUPPORT_PIXELORAMA
|
||
}
|
||
|
||
## The file used to save preferences that use [code]ProjectSettings.save_custom()[/code].
|
||
const OVERRIDE_FILE := "override.cfg"
|
||
## The name of folder containing Pixelorama preferences.
|
||
const HOME_SUBDIR_NAME := "pixelorama"
|
||
## The name of folder that contains subdirectories for users to place brushes, palettes, patterns.
|
||
const CONFIG_SUBDIR_NAME := "pixelorama_data"
|
||
const VALUE_SLIDER_V2_TSCN := preload("res://src/UI/Nodes/ValueSliderV2.tscn")
|
||
const GRADIENT_EDIT_TSCN := preload("res://src/UI/Nodes/GradientEdit.tscn")
|
||
|
||
## It is path to the executable's base drectory.
|
||
var root_directory := "."
|
||
## The path where preferences and other subdirectories for stuff like layouts, extensions, logs etc.
|
||
## will get stored by Pixelorama.
|
||
var home_data_directory := OS.get_data_dir().path_join(HOME_SUBDIR_NAME)
|
||
## Only read from these directories. This is an [Array] of directories potentially containing
|
||
## stuff such as Brushes, Palettes and Patterns in sub-directories.[br]
|
||
## ([member home_data_directory] and [member root_directory] are also included in this array).
|
||
var data_directories: PackedStringArray = [home_data_directory]
|
||
## The config file used to get/set preferences, tool settings etc.
|
||
var config_cache := ConfigFile.new()
|
||
|
||
var projects: Array[Project] = [] ## Array of currently open projects.
|
||
var current_project: Project ## The project that currently in focus.
|
||
## The index of project that is currently in focus.
|
||
var current_project_index := 0:
|
||
set(value):
|
||
if value >= projects.size():
|
||
return
|
||
canvas.selection.transform_content_confirm()
|
||
current_project_index = value
|
||
project_about_to_switch.emit()
|
||
current_project = projects[value]
|
||
project_switched.connect(current_project.change_project)
|
||
project_switched.emit()
|
||
project_switched.disconnect(current_project.change_project)
|
||
cel_switched.emit()
|
||
|
||
# Canvas related stuff
|
||
## Tells if the user allowed to draw on the canvas. Usually it is temporarily set to
|
||
## [code]false[/code] when we are moving some gizmo and don't want the current tool to accidentally
|
||
## start drawing.[br](This does not depend on layer invisibility or lock/unlock status).
|
||
var can_draw := false
|
||
## (Intended to be used as getter only) Tells if the user allowed to move the guide while on canvas.
|
||
var move_guides_on_canvas := true
|
||
|
||
var play_only_tags := true ## If [code]true[/code], animation plays only on frames of the same tag.
|
||
## (Intended to be used as getter only) Tells if the x-symmetry guide ( -- ) is visible.
|
||
var show_x_symmetry_axis := false
|
||
## (Intended to be used as getter only) Tells if the y-symmetry guide ( | ) is visible.
|
||
var show_y_symmetry_axis := false
|
||
|
||
# Preferences
|
||
## Found in Preferences. If [code]true[/code], the last saved project will open on startup.
|
||
var open_last_project := false
|
||
## Found in Preferences. If [code]true[/code], asks for permission to quit on exit.
|
||
var quit_confirmation := false
|
||
## Found in Preferences. Refers to the ffmpeg location path.
|
||
var ffmpeg_path := ""
|
||
## Found in Preferences. If [code]true[/code], the zoom is smooth.
|
||
var smooth_zoom := true
|
||
## Found in Preferences. If [code]true[/code], the zoom is restricted to integral multiples of 100%.
|
||
var integer_zoom := false:
|
||
set(value):
|
||
integer_zoom = value
|
||
var zoom_slider: ValueSlider = top_menu_container.get_node("%ZoomSlider")
|
||
if value:
|
||
zoom_slider.min_value = 100
|
||
zoom_slider.snap_step = 100
|
||
zoom_slider.step = 100
|
||
else:
|
||
zoom_slider.min_value = 1
|
||
zoom_slider.snap_step = 1
|
||
zoom_slider.step = 1
|
||
zoom_slider.value = zoom_slider.value # to trigger signal emission
|
||
|
||
## Found in Preferences. The scale of the interface.
|
||
var shrink := 1.0
|
||
## Found in Preferences. The font size used by the interface.
|
||
var font_size := 16:
|
||
set(value):
|
||
font_size = value
|
||
control.theme.default_font_size = value
|
||
control.theme.set_font_size("font_size", "HeaderSmall", value + 2)
|
||
## Found in Preferences. If [code]true[/code], the interface dims on popups.
|
||
var dim_on_popup := true
|
||
## Found in Preferences. If [code]true[/code], the native file dialogs of the
|
||
## operating system are being used, instead of Godot's FileDialog node.
|
||
var use_native_file_dialogs := false:
|
||
set(value):
|
||
use_native_file_dialogs = value
|
||
if not is_inside_tree():
|
||
await tree_entered
|
||
await get_tree().process_frame
|
||
get_tree().set_group(&"FileDialogs", "use_native_dialog", value)
|
||
## Found in Preferences. If [code]true[/code], subwindows are embedded in the main window.
|
||
var single_window_mode := true:
|
||
set(value):
|
||
single_window_mode = value
|
||
if OS.has_feature("editor"):
|
||
return
|
||
ProjectSettings.set_setting("display/window/subwindows/embed_subwindows", value)
|
||
ProjectSettings.save_custom(OVERRIDE_FILE)
|
||
## Found in Preferences. The modulation color (or simply color) of icons.
|
||
var modulate_icon_color := Color.GRAY
|
||
## Found in Preferences. Determines if [member modulate_icon_color] uses custom or theme color.
|
||
var icon_color_from := ColorFrom.THEME:
|
||
set(value):
|
||
icon_color_from = value
|
||
var themes = preferences_dialog.themes
|
||
if icon_color_from == ColorFrom.THEME:
|
||
var current_theme: Theme = themes.themes[themes.theme_index]
|
||
modulate_icon_color = current_theme.get_color("modulate_color", "Icons")
|
||
else:
|
||
modulate_icon_color = custom_icon_color
|
||
themes.change_icon_colors()
|
||
## Found in Preferences. Color of icons when [member icon_color_from] is set to use custom colors.
|
||
var custom_icon_color := Color.GRAY:
|
||
set(value):
|
||
custom_icon_color = value
|
||
if icon_color_from == ColorFrom.CUSTOM:
|
||
modulate_icon_color = custom_icon_color
|
||
preferences_dialog.themes.change_icon_colors()
|
||
## Found in Preferences. The modulation color (or simply color) of canvas background
|
||
## (aside from checker background).
|
||
var modulate_clear_color := Color.GRAY:
|
||
set(value):
|
||
modulate_clear_color = value
|
||
preferences_dialog.themes.change_clear_color()
|
||
## Found in Preferences. Determines if [member modulate_clear_color] uses custom or theme color.
|
||
var clear_color_from := ColorFrom.THEME:
|
||
set(value):
|
||
clear_color_from = value
|
||
preferences_dialog.themes.change_clear_color()
|
||
## Found in Preferences. The selected size mode of tool buttons using [enum ButtonSize] enum.
|
||
var tool_button_size := ButtonSize.SMALL:
|
||
set(value):
|
||
tool_button_size = value
|
||
Tools.set_button_size(tool_button_size)
|
||
## Found in Preferences. The left tool color.
|
||
var left_tool_color := Color("0086cf"):
|
||
set(value):
|
||
left_tool_color = value
|
||
for child in Tools._tool_buttons.get_children():
|
||
var background: NinePatchRect = child.get_node("BackgroundLeft")
|
||
background.modulate = value
|
||
Tools._slots[MOUSE_BUTTON_LEFT].tool_node.color_rect.color = value
|
||
## Found in Preferences. The right tool color.
|
||
var right_tool_color := Color("fd6d14"):
|
||
set(value):
|
||
right_tool_color = value
|
||
for child in Tools._tool_buttons.get_children():
|
||
var background: NinePatchRect = child.get_node("BackgroundRight")
|
||
background.modulate = value
|
||
Tools._slots[MOUSE_BUTTON_RIGHT].tool_node.color_rect.color = value
|
||
|
||
var default_width := 64 ## Found in Preferences. The default width of startup project.
|
||
var default_height := 64 ## Found in Preferences. The default height of startup project.
|
||
## Found in Preferences. The fill color of startup project.
|
||
var default_fill_color := Color(0, 0, 0, 0)
|
||
## Found in Preferences. The distance to the guide or grig below which cursor snapping activates.
|
||
var snapping_distance := 32.0
|
||
## Found in Preferences. The grid type defined by [enum GridTypes] enum.
|
||
var grid_type := GridTypes.CARTESIAN:
|
||
set(value):
|
||
grid_type = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. The size of rectangular grid.
|
||
var grid_size := Vector2i(2, 2):
|
||
set(value):
|
||
grid_size = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. The size of isometric grid.
|
||
var isometric_grid_size := Vector2i(16, 8):
|
||
set(value):
|
||
isometric_grid_size = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. The grid offset from top-left corner of the canvas.
|
||
var grid_offset := Vector2i.ZERO:
|
||
set(value):
|
||
grid_offset = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. If [code]true[/code], The grid draws over the area extended by
|
||
## tile-mode as well.
|
||
var grid_draw_over_tile_mode := false:
|
||
set(value):
|
||
grid_draw_over_tile_mode = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. The color of grid.
|
||
var grid_color := Color.BLACK:
|
||
set(value):
|
||
grid_color = value
|
||
canvas.grid.queue_redraw()
|
||
## Found in Preferences. The minimum zoom after which pixel grid gets drawn if enabled.
|
||
var pixel_grid_show_at_zoom := 1500.0: # percentage
|
||
set(value):
|
||
pixel_grid_show_at_zoom = value
|
||
canvas.pixel_grid.queue_redraw()
|
||
## Found in Preferences. The color of pixel grid.
|
||
var pixel_grid_color := Color("21212191"):
|
||
set(value):
|
||
pixel_grid_color = value
|
||
canvas.pixel_grid.queue_redraw()
|
||
## Found in Preferences. The color of guides.
|
||
var guide_color := Color.PURPLE:
|
||
set(value):
|
||
guide_color = value
|
||
for guide in canvas.get_children():
|
||
if guide is Guide:
|
||
guide.set_color(guide_color)
|
||
## Found in Preferences. The size of checkers in the checker background.
|
||
var checker_size := 10:
|
||
set(value):
|
||
checker_size = value
|
||
transparent_checker.update_rect()
|
||
## Found in Preferences. The color of first checker.
|
||
var checker_color_1 := Color(0.47, 0.47, 0.47, 1):
|
||
set(value):
|
||
checker_color_1 = value
|
||
transparent_checker.update_rect()
|
||
## Found in Preferences. The color of second checker.
|
||
var checker_color_2 := Color(0.34, 0.35, 0.34, 1):
|
||
set(value):
|
||
checker_color_2 = value
|
||
transparent_checker.update_rect()
|
||
## Found in Preferences. The color of second checker.
|
||
var checker_follow_movement := false:
|
||
set(value):
|
||
checker_follow_movement = value
|
||
transparent_checker.update_rect()
|
||
## Found in Preferences. If [code]true[/code], the checker follows zoom.
|
||
var checker_follow_scale := false:
|
||
set(value):
|
||
checker_follow_scale = value
|
||
transparent_checker.update_rect()
|
||
## Found in Preferences. Opacity of the sprites rendered on the extended area of tile-mode.
|
||
var tilemode_opacity := 1.0:
|
||
set(value):
|
||
tilemode_opacity = value
|
||
canvas.tile_mode.queue_redraw()
|
||
|
||
## Found in Preferences. If [code]true[/code], layers get selected when their buttons are pressed.
|
||
var select_layer_on_button_click := false
|
||
## Found in Preferences. The onion color of past frames.
|
||
var onion_skinning_past_color := Color.RED:
|
||
set(value):
|
||
onion_skinning_past_color = value
|
||
canvas.onion_past.blue_red_color = value
|
||
canvas.onion_past.queue_redraw()
|
||
## Found in Preferences. The onion color of future frames.
|
||
var onion_skinning_future_color := Color.BLUE:
|
||
set(value):
|
||
onion_skinning_future_color = value
|
||
canvas.onion_future.blue_red_color = value
|
||
canvas.onion_future.queue_redraw()
|
||
|
||
## Found in Preferences. If [code]true[/code], the selection rect has animated borders.
|
||
var selection_animated_borders := true:
|
||
set(value):
|
||
selection_animated_borders = value
|
||
var marching_ants: Sprite2D = canvas.selection.marching_ants_outline
|
||
marching_ants.material.set_shader_parameter("animated", selection_animated_borders)
|
||
## Found in Preferences. The first color of border.
|
||
var selection_border_color_1 := Color.WHITE:
|
||
set(value):
|
||
selection_border_color_1 = value
|
||
var marching_ants: Sprite2D = canvas.selection.marching_ants_outline
|
||
marching_ants.material.set_shader_parameter("first_color", selection_border_color_1)
|
||
canvas.selection.queue_redraw()
|
||
## Found in Preferences. The second color of border.
|
||
var selection_border_color_2 := Color.BLACK:
|
||
set(value):
|
||
selection_border_color_2 = value
|
||
var marching_ants: Sprite2D = canvas.selection.marching_ants_outline
|
||
marching_ants.material.set_shader_parameter("second_color", selection_border_color_2)
|
||
canvas.selection.queue_redraw()
|
||
|
||
## Found in Preferences. If [code]true[/code], Pixelorama pauses when unfocused to save cpu usage.
|
||
var pause_when_unfocused := true
|
||
## Found in Preferences. The max fps, Pixelorama is allowed to use (does not limit fps if it is 0).
|
||
var fps_limit := 0:
|
||
set(value):
|
||
fps_limit = value
|
||
Engine.max_fps = fps_limit
|
||
|
||
## Found in Preferences. The time (in minutes) after which backup is created (if enabled).
|
||
var autosave_interval := 1.0:
|
||
set(value):
|
||
autosave_interval = value
|
||
OpenSave.update_autosave()
|
||
## Found in Preferences. If [code]true[/code], generation of backups get enabled.
|
||
var enable_autosave := true:
|
||
set(value):
|
||
enable_autosave = value
|
||
OpenSave.update_autosave()
|
||
preferences_dialog.autosave_interval.editable = enable_autosave
|
||
## Found in Preferences. The index of graphics renderer used by Pixelorama.
|
||
var renderer := 0:
|
||
set = _renderer_changed
|
||
## Found in Preferences. The index of tablet driver used by Pixelorama.
|
||
var tablet_driver := 0:
|
||
set(value):
|
||
tablet_driver = value
|
||
if OS.has_feature("editor"):
|
||
return
|
||
var tablet_driver_name := DisplayServer.tablet_get_current_driver()
|
||
ProjectSettings.set_setting("display/window/tablet_driver", tablet_driver_name)
|
||
ProjectSettings.save_custom(OVERRIDE_FILE)
|
||
|
||
# Tools & options
|
||
## Found in Preferences. If [code]true[/code], the cursor's left tool icon is visible.
|
||
var show_left_tool_icon := true
|
||
## Found in Preferences. If [code]true[/code], the cursor's right tool icon is visible.
|
||
var show_right_tool_icon := true
|
||
## Found in Preferences. If [code]true[/code], the left tool's brush indicator is visible.
|
||
var left_square_indicator_visible := true
|
||
## Found in Preferences. If [code]true[/code], the right tool's brush indicator is visible.
|
||
var right_square_indicator_visible := true
|
||
## Found in Preferences. If [code]true[/code], native cursors are used instead of default cursors.
|
||
var native_cursors := false:
|
||
set(value):
|
||
native_cursors = value
|
||
if native_cursors:
|
||
Input.set_custom_mouse_cursor(null, Input.CURSOR_CROSS, Vector2(15, 15))
|
||
else:
|
||
control.set_custom_cursor()
|
||
## Found in Preferences. If [code]true[/code], cursor becomes cross shaped when hovering the canvas.
|
||
var cross_cursor := true:
|
||
set(value):
|
||
cross_cursor = value
|
||
if cross_cursor:
|
||
main_viewport.mouse_default_cursor_shape = Control.CURSOR_CROSS
|
||
else:
|
||
main_viewport.mouse_default_cursor_shape = Control.CURSOR_ARROW
|
||
|
||
# View menu options
|
||
## If [code]true[/code], the canvas is in greyscale.
|
||
var greyscale_view := false
|
||
## If [code]true[/code], the content of canvas is flipped.
|
||
var mirror_view := false
|
||
## If [code]true[/code], the grid is visible.
|
||
var draw_grid := false
|
||
## If [code]true[/code], the pixel grid is visible.
|
||
var draw_pixel_grid := false
|
||
## If [code]true[/code], the rulers are visible.
|
||
var show_rulers := true
|
||
## If [code]true[/code], the guides are visible.
|
||
var show_guides := true
|
||
## If [code]true[/code], the mouse guides are visible.
|
||
var show_mouse_guides := false
|
||
var display_layer_effects := true:
|
||
set(value):
|
||
display_layer_effects = value
|
||
if is_instance_valid(top_menu_container):
|
||
top_menu_container.view_menu.set_item_checked(ViewMenu.DISPLAY_LAYER_EFFECTS, value)
|
||
canvas.queue_redraw()
|
||
## If [code]true[/code], cursor snaps to the boundary of rectangular grid boxes.
|
||
var snap_to_rectangular_grid_boundary := false
|
||
## If [code]true[/code], cursor snaps to the center of rectangular grid boxes.
|
||
var snap_to_rectangular_grid_center := false
|
||
## If [code]true[/code], cursor snaps to regular guides.
|
||
var snap_to_guides := false
|
||
## If [code]true[/code], cursor snaps to perspective guides.
|
||
var snap_to_perspective_guides := false
|
||
|
||
# Onion skinning options
|
||
var onion_skinning := false ## If [code]true[/code], onion skinning is enabled.
|
||
var onion_skinning_past_rate := 1 ## Number of past frames shown when onion skinning is enabled.
|
||
## Number of future frames shown when onion skinning is enabled.
|
||
var onion_skinning_future_rate := 1
|
||
var onion_skinning_blue_red := false ## If [code]true[/code], then blue-red mode is enabled.
|
||
|
||
## The current version of pixelorama
|
||
var current_version: String = ProjectSettings.get_setting("application/config/Version")
|
||
|
||
# Nodes
|
||
## The [PackedScene] of the button used by layers in the timeline.
|
||
var layer_button_node := preload("res://src/UI/Timeline/LayerButton.tscn")
|
||
## The [PackedScene] of the button used by cels in the timeline.
|
||
var cel_button_scene: PackedScene = load("res://src/UI/Timeline/CelButton.tscn")
|
||
|
||
@onready var main_window := get_window() ## The main Pixelorama [Window].
|
||
## The control node (aka Main node). It has the [param Main.gd] script attached.
|
||
@onready var control := get_tree().current_scene
|
||
|
||
## The project tabs bar. It has the [param Tabs.gd] script attached.
|
||
@onready var tabs: TabBar = control.find_child("TabBar")
|
||
## Contains viewport of the main canvas. It has the [param ViewportContainer.gd] script attached.
|
||
@onready var main_viewport: SubViewportContainer = control.find_child("SubViewportContainer")
|
||
## The main canvas node. It has the [param Canvas.gd] script attached.
|
||
@onready var canvas: Canvas = main_viewport.find_child("Canvas")
|
||
## Contains viewport of the second canvas preview.
|
||
## It has the [param ViewportContainer.gd] script attached.
|
||
@onready var second_viewport: SubViewportContainer = control.find_child("Second Canvas")
|
||
## The panel container of the canvas preview.
|
||
## It has the [param CanvasPreviewContainer.gd] script attached.
|
||
@onready var canvas_preview_container: Container = control.find_child("Canvas Preview")
|
||
## The global tool options. It has the [param GlobalToolOptions.gd] script attached.
|
||
@onready var global_tool_options: PanelContainer = control.find_child("Global Tool Options")
|
||
## Contains viewport of the canvas preview.
|
||
@onready var small_preview_viewport: SubViewportContainer = canvas_preview_container.find_child(
|
||
"PreviewViewportContainer"
|
||
)
|
||
## Camera of the main canvas. It has the [param CameraMovement.gd] script attached.
|
||
@onready var camera: Camera2D = main_viewport.find_child("Camera2D")
|
||
## Camera of the second canvas preview. It has the [param CameraMovement.gd] script attached.
|
||
@onready var camera2: Camera2D = second_viewport.find_child("Camera2D2")
|
||
## Camera of the canvas preview. It has the [param CameraMovement.gd] script attached.
|
||
@onready var camera_preview: Camera2D = control.find_child("CameraPreview")
|
||
## Array of cameras used in Pixelorama.
|
||
@onready var cameras := [camera, camera2, camera_preview]
|
||
## Horizontal ruler of the main canvas. It has the [param HorizontalRuler.gd] script attached.
|
||
@onready var horizontal_ruler: BaseButton = control.find_child("HorizontalRuler")
|
||
## Vertical ruler of the main canvas. It has the [param VerticalRuler.gd] script attached.
|
||
@onready var vertical_ruler: BaseButton = control.find_child("VerticalRuler")
|
||
## Transparent checker of the main canvas. It has the [param TransparentChecker.gd] script attached.
|
||
@onready var transparent_checker: ColorRect = control.find_child("TransparentChecker")
|
||
|
||
## The perspective editor. It has the [param PerspectiveEditor.gd] script attached.
|
||
@onready var perspective_editor := control.find_child("Perspective Editor")
|
||
|
||
## The reference panel. It has the [param ReferencesPanel.gd] script attached.
|
||
@onready var reference_panel: ReferencesPanel = control.find_child("Reference Images")
|
||
|
||
## The top menu container. It has the [param TopMenuContainer.gd] script attached.
|
||
@onready var top_menu_container: Panel = control.find_child("TopMenuContainer")
|
||
## The label indicating cursor position.
|
||
@onready var cursor_position_label: Label = top_menu_container.find_child("CursorPosition")
|
||
## The label indicating current frame number.
|
||
@onready var current_frame_mark_label: Label = top_menu_container.find_child("CurrentFrameMark")
|
||
|
||
## The animation timeline. It has the [param AnimationTimeline.gd] script attached.
|
||
@onready var animation_timeline: Panel = control.find_child("Animation Timeline")
|
||
## The timer used by the animation timeline.
|
||
@onready var animation_timer: Timer = animation_timeline.find_child("AnimationTimer")
|
||
## The container of frame buttons
|
||
@onready var frame_hbox: HBoxContainer = animation_timeline.find_child("FrameHBox")
|
||
## The container of layer buttons
|
||
@onready var layer_vbox: VBoxContainer = animation_timeline.find_child("LayerVBox")
|
||
## At runtime HBoxContainers containing cel buttons get added to it.
|
||
@onready var cel_vbox: VBoxContainer = animation_timeline.find_child("CelVBox")
|
||
## The container of animation tags.
|
||
@onready var tag_container: Control = animation_timeline.find_child("TagContainer")
|
||
|
||
## The brushes popup dialog used to display brushes.
|
||
## It has the [param BrushesPopup.gd] script attached.
|
||
@onready var brushes_popup: Popup = control.find_child("BrushesPopup")
|
||
## The patterns popup dialog used to display patterns
|
||
## It has the [param PatternsPopup.gd] script attached.
|
||
@onready var patterns_popup: Popup = control.find_child("PatternsPopup")
|
||
@onready var tile_mode_offset_dialog: AcceptDialog = control.find_child("TileModeOffsetsDialog")
|
||
## Dialog used to navigate and open images and projects.
|
||
@onready var open_sprites_dialog: FileDialog = control.find_child("OpenSprite")
|
||
## Dialog used to save (.pxo) projects.
|
||
@onready var save_sprites_dialog: FileDialog = control.find_child("SaveSprite")
|
||
## Dialog used to export images. It has the [param ExportDialog.gd] script attached.
|
||
@onready var export_dialog: AcceptDialog = control.find_child("ExportDialog")
|
||
## The preferences dialog. It has the [param PreferencesDialog.gd] script attached.
|
||
@onready var preferences_dialog: AcceptDialog = control.find_child("PreferencesDialog")
|
||
## An error dialog to show errors.
|
||
@onready var error_dialog: AcceptDialog = control.find_child("ErrorDialog")
|
||
|
||
|
||
func _init() -> void:
|
||
if OS.has_feature("template"):
|
||
root_directory = OS.get_executable_path().get_base_dir()
|
||
data_directories.append(root_directory.path_join(CONFIG_SUBDIR_NAME))
|
||
if OS.get_name() in ["Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD"]:
|
||
# Checks the list of files var, and processes them.
|
||
if OS.has_environment("XDG_DATA_DIRS"):
|
||
var raw_env_var := OS.get_environment("XDG_DATA_DIRS") # includes empties.
|
||
var unappended_subdirs := raw_env_var.split(":", true)
|
||
for unapp_subdir in unappended_subdirs:
|
||
data_directories.append(unapp_subdir.path_join(HOME_SUBDIR_NAME))
|
||
else:
|
||
# Create defaults
|
||
for default_loc in ["/usr/local/share", "/usr/share"]:
|
||
data_directories.append(default_loc.path_join(HOME_SUBDIR_NAME))
|
||
if ProjectSettings.get_setting("display/window/tablet_driver") == "winink":
|
||
tablet_driver = 1
|
||
single_window_mode = ProjectSettings.get_setting("display/window/subwindows/embed_subwindows")
|
||
|
||
|
||
func _ready() -> void:
|
||
_initialize_keychain()
|
||
# Load settings from the config file
|
||
config_cache.load("user://cache.ini")
|
||
|
||
default_width = config_cache.get_value("preferences", "default_width", default_width)
|
||
default_height = config_cache.get_value("preferences", "default_height", default_height)
|
||
default_fill_color = config_cache.get_value(
|
||
"preferences", "default_fill_color", default_fill_color
|
||
)
|
||
var proj_size := Vector2i(default_width, default_height)
|
||
projects.append(Project.new([], tr("untitled"), proj_size))
|
||
current_project = projects[0]
|
||
current_project.fill_color = default_fill_color
|
||
|
||
await get_tree().process_frame
|
||
project_switched.emit()
|
||
|
||
|
||
func _initialize_keychain() -> void:
|
||
Keychain.config_file = config_cache
|
||
Keychain.actions = {
|
||
"new_file": Keychain.InputAction.new("", "File menu", true),
|
||
"open_file": Keychain.InputAction.new("", "File menu", true),
|
||
"open_last_project": Keychain.InputAction.new("", "File menu", true),
|
||
"save_file": Keychain.InputAction.new("", "File menu", true),
|
||
"save_file_as": Keychain.InputAction.new("", "File menu", true),
|
||
"export_file": Keychain.InputAction.new("", "File menu", true),
|
||
"export_file_as": Keychain.InputAction.new("", "File menu", true),
|
||
"quit": Keychain.InputAction.new("", "File menu", true),
|
||
"redo": Keychain.InputAction.new("", "Edit menu", true),
|
||
"undo": Keychain.InputAction.new("", "Edit menu", true),
|
||
"cut": Keychain.InputAction.new("", "Edit menu", true),
|
||
"copy": Keychain.InputAction.new("", "Edit menu", true),
|
||
"paste": Keychain.InputAction.new("", "Edit menu", true),
|
||
"paste_in_place": Keychain.InputAction.new("", "Edit menu", true),
|
||
"delete": Keychain.InputAction.new("", "Edit menu", true),
|
||
"new_brush": Keychain.InputAction.new("", "Edit menu", true),
|
||
"preferences": Keychain.InputAction.new("", "Edit menu", true),
|
||
"scale_image": Keychain.InputAction.new("", "Image menu", true),
|
||
"crop_to_selection": Keychain.InputAction.new("", "Image menu", true),
|
||
"crop_to_content": Keychain.InputAction.new("", "Image menu", true),
|
||
"resize_canvas": Keychain.InputAction.new("", "Image menu", true),
|
||
"offset_image": Keychain.InputAction.new("", "Image menu", true),
|
||
"mirror_image": Keychain.InputAction.new("", "Image menu", true),
|
||
"rotate_image": Keychain.InputAction.new("", "Image menu", true),
|
||
"invert_colors": Keychain.InputAction.new("", "Image menu", true),
|
||
"desaturation": Keychain.InputAction.new("", "Image menu", true),
|
||
"outline": Keychain.InputAction.new("", "Image menu", true),
|
||
"drop_shadow": Keychain.InputAction.new("", "Image menu", true),
|
||
"adjust_hsv": Keychain.InputAction.new("", "Image menu", true),
|
||
"gradient": Keychain.InputAction.new("", "Image menu", true),
|
||
"gradient_map": Keychain.InputAction.new("", "Image menu", true),
|
||
"posterize": Keychain.InputAction.new("", "Image menu", true),
|
||
"mirror_view": Keychain.InputAction.new("", "View menu", true),
|
||
"show_grid": Keychain.InputAction.new("", "View menu", true),
|
||
"show_pixel_grid": Keychain.InputAction.new("", "View menu", true),
|
||
"show_guides": Keychain.InputAction.new("", "View menu", true),
|
||
"show_rulers": Keychain.InputAction.new("", "View menu", true),
|
||
&"display_layer_effects": Keychain.InputAction.new("", "View menu", true),
|
||
"moveable_panels": Keychain.InputAction.new("", "Window menu", true),
|
||
"zen_mode": Keychain.InputAction.new("", "Window menu", true),
|
||
"toggle_fullscreen": Keychain.InputAction.new("", "Window menu", true),
|
||
"clear_selection": Keychain.InputAction.new("", "Select menu", true),
|
||
"select_all": Keychain.InputAction.new("", "Select menu", true),
|
||
"invert_selection": Keychain.InputAction.new("", "Select menu", true),
|
||
"view_splash_screen": Keychain.InputAction.new("", "Help menu", true),
|
||
"open_docs": Keychain.InputAction.new("", "Help menu", true),
|
||
"issue_tracker": Keychain.InputAction.new("", "Help menu", true),
|
||
"open_logs_folder": Keychain.InputAction.new("", "Help menu", true),
|
||
"changelog": Keychain.InputAction.new("", "Help menu", true),
|
||
"about_pixelorama": Keychain.InputAction.new("", "Help menu", true),
|
||
"zoom_in": Keychain.InputAction.new("", "Canvas"),
|
||
"zoom_out": Keychain.InputAction.new("", "Canvas"),
|
||
"camera_left": Keychain.InputAction.new("", "Canvas"),
|
||
"camera_right": Keychain.InputAction.new("", "Canvas"),
|
||
"camera_up": Keychain.InputAction.new("", "Canvas"),
|
||
"camera_down": Keychain.InputAction.new("", "Canvas"),
|
||
"pan": Keychain.InputAction.new("", "Canvas"),
|
||
"activate_left_tool": Keychain.InputAction.new("", "Canvas"),
|
||
"activate_right_tool": Keychain.InputAction.new("", "Canvas"),
|
||
"move_mouse_left": Keychain.InputAction.new("", "Cursor movement"),
|
||
"move_mouse_right": Keychain.InputAction.new("", "Cursor movement"),
|
||
"move_mouse_up": Keychain.InputAction.new("", "Cursor movement"),
|
||
"move_mouse_down": Keychain.InputAction.new("", "Cursor movement"),
|
||
"reset_colors_default": Keychain.InputAction.new("", "Buttons"),
|
||
"switch_colors": Keychain.InputAction.new("", "Buttons"),
|
||
"horizontal_mirror": Keychain.InputAction.new("", "Buttons"),
|
||
"vertical_mirror": Keychain.InputAction.new("", "Buttons"),
|
||
"pixel_perfect": Keychain.InputAction.new("", "Buttons"),
|
||
"new_layer": Keychain.InputAction.new("", "Buttons"),
|
||
"remove_layer": Keychain.InputAction.new("", "Buttons"),
|
||
"move_layer_up": Keychain.InputAction.new("", "Buttons"),
|
||
"move_layer_down": Keychain.InputAction.new("", "Buttons"),
|
||
"clone_layer": Keychain.InputAction.new("", "Buttons"),
|
||
"merge_down_layer": Keychain.InputAction.new("", "Buttons"),
|
||
"add_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"remove_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"clone_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"manage_frame_tags": Keychain.InputAction.new("", "Buttons"),
|
||
"move_frame_left": Keychain.InputAction.new("", "Buttons"),
|
||
"move_frame_right": Keychain.InputAction.new("", "Buttons"),
|
||
"go_to_first_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"go_to_last_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"go_to_previous_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"go_to_next_frame": Keychain.InputAction.new("", "Buttons"),
|
||
"play_backwards": Keychain.InputAction.new("", "Buttons"),
|
||
"play_forward": Keychain.InputAction.new("", "Buttons"),
|
||
"onion_skinning_toggle": Keychain.InputAction.new("", "Buttons"),
|
||
"loop_toggle": Keychain.InputAction.new("", "Buttons"),
|
||
"onion_skinning_settings": Keychain.InputAction.new("", "Buttons"),
|
||
"new_palette": Keychain.InputAction.new("", "Buttons"),
|
||
"edit_palette": Keychain.InputAction.new("", "Buttons"),
|
||
"brush_size_increment": Keychain.InputAction.new("", "Buttons"),
|
||
"brush_size_decrement": Keychain.InputAction.new("", "Buttons"),
|
||
"change_tool_mode": Keychain.InputAction.new("", "Tool modifiers", false),
|
||
"draw_create_line": Keychain.InputAction.new("", "Draw tools", false),
|
||
"draw_snap_angle": Keychain.InputAction.new("", "Draw tools", false),
|
||
"draw_color_picker": Keychain.InputAction.new("Quick color picker", "Draw tools", false),
|
||
"shape_perfect": Keychain.InputAction.new("", "Shape tools", false),
|
||
"shape_center": Keychain.InputAction.new("", "Shape tools", false),
|
||
"shape_displace": Keychain.InputAction.new("", "Shape tools", false),
|
||
"selection_add": Keychain.InputAction.new("", "Selection tools", false),
|
||
"selection_subtract": Keychain.InputAction.new("", "Selection tools", false),
|
||
"selection_intersect": Keychain.InputAction.new("", "Selection tools", false),
|
||
"transformation_confirm": Keychain.InputAction.new("", "Transformation tools", false),
|
||
"transformation_cancel": Keychain.InputAction.new("", "Transformation tools", false),
|
||
"transform_snap_axis": Keychain.InputAction.new("", "Transformation tools", false),
|
||
"transform_snap_grid": Keychain.InputAction.new("", "Transformation tools", false),
|
||
"transform_move_selection_only":
|
||
Keychain.InputAction.new("", "Transformation tools", false),
|
||
"transform_copy_selection_content":
|
||
Keychain.InputAction.new("", "Transformation tools", false),
|
||
"reference_rotate": Keychain.InputAction.new("", "Reference images", false),
|
||
"reference_scale": Keychain.InputAction.new("", "Reference images", false),
|
||
"reference_quick_menu": Keychain.InputAction.new("", "Reference images", false),
|
||
"cancel_reference_transform": Keychain.InputAction.new("", "Reference images", false)
|
||
}
|
||
|
||
Keychain.groups = {
|
||
"Canvas": Keychain.InputGroup.new("", false),
|
||
"Cursor movement": Keychain.InputGroup.new("Canvas"),
|
||
"Buttons": Keychain.InputGroup.new(),
|
||
"Tools": Keychain.InputGroup.new(),
|
||
"Left": Keychain.InputGroup.new("Tools"),
|
||
"Right": Keychain.InputGroup.new("Tools"),
|
||
"Menu": Keychain.InputGroup.new(),
|
||
"File menu": Keychain.InputGroup.new("Menu"),
|
||
"Edit menu": Keychain.InputGroup.new("Menu"),
|
||
"View menu": Keychain.InputGroup.new("Menu"),
|
||
"Select menu": Keychain.InputGroup.new("Menu"),
|
||
"Image menu": Keychain.InputGroup.new("Menu"),
|
||
"Window menu": Keychain.InputGroup.new("Menu"),
|
||
"Help menu": Keychain.InputGroup.new("Menu"),
|
||
"Tool modifiers": Keychain.InputGroup.new(),
|
||
"Draw tools": Keychain.InputGroup.new("Tool modifiers"),
|
||
"Shape tools": Keychain.InputGroup.new("Tool modifiers"),
|
||
"Selection tools": Keychain.InputGroup.new("Tool modifiers"),
|
||
"Transformation tools": Keychain.InputGroup.new("Tool modifiers"),
|
||
"Reference images": Keychain.InputGroup.new("Canvas")
|
||
}
|
||
Keychain.ignore_actions = ["left_mouse", "right_mouse", "middle_mouse", "shift", "ctrl"]
|
||
|
||
|
||
## Generates an animated notification label showing [param text].
|
||
func notification_label(text: String) -> void:
|
||
var notif := NotificationLabel.new()
|
||
notif.text = tr(text)
|
||
notif.position = main_viewport.global_position
|
||
notif.position.y += main_viewport.size.y
|
||
control.add_child(notif)
|
||
|
||
|
||
## Performs the general, bare minimum stuff needed after an undo is done.
|
||
func general_undo(project := current_project) -> void:
|
||
project.undos -= 1
|
||
var action_name := project.undo_redo.get_current_action_name()
|
||
notification_label("Undo: %s" % action_name)
|
||
|
||
|
||
## Performs the general, bare minimum stuff needed after a redo is done.
|
||
func general_redo(project := current_project) -> void:
|
||
if project.undos < project.undo_redo.get_version(): # If we did undo and then redo
|
||
project.undos = project.undo_redo.get_version()
|
||
if control.redone:
|
||
var action_name := project.undo_redo.get_current_action_name()
|
||
notification_label("Redo: %s" % action_name)
|
||
|
||
|
||
## Performs actions done after an undo or redo is done. this takes [member general_undo] and
|
||
## [member general_redo] a step further. Does further work if the current action requires it
|
||
## like refreshing textures, redraw UI elements etc...[br]
|
||
## [param frame_index] and [param layer_index] are there for optimizzation. if the undo or redo
|
||
## happens only in one cel then the cel's frame and layer should be passed to [param frame_index]
|
||
## and [param layer_index] respectively, otherwise the entire timeline will be refreshed.
|
||
func undo_or_redo(
|
||
undo: bool, frame_index := -1, layer_index := -1, project := current_project
|
||
) -> void:
|
||
if undo:
|
||
general_undo(project)
|
||
else:
|
||
general_redo(project)
|
||
var action_name := project.undo_redo.get_current_action_name()
|
||
if (
|
||
action_name
|
||
in [
|
||
"Draw",
|
||
"Draw Shape",
|
||
"Select",
|
||
"Move Selection",
|
||
"Scale",
|
||
"Center Frames",
|
||
"Merge Layer",
|
||
"Link Cel",
|
||
"Unlink Cel"
|
||
]
|
||
):
|
||
if layer_index > -1 and frame_index > -1:
|
||
canvas.update_texture(layer_index, frame_index, project)
|
||
else:
|
||
for i in project.frames.size():
|
||
for j in project.layers.size():
|
||
canvas.update_texture(j, i, project)
|
||
|
||
canvas.selection.queue_redraw()
|
||
if action_name == "Scale":
|
||
for i in project.frames.size():
|
||
for j in project.layers.size():
|
||
var current_cel := project.frames[i].cels[j]
|
||
if current_cel is Cel3D:
|
||
current_cel.size_changed(project.size)
|
||
else:
|
||
current_cel.image_texture.set_image(current_cel.get_image())
|
||
canvas.camera_zoom()
|
||
canvas.grid.queue_redraw()
|
||
canvas.pixel_grid.queue_redraw()
|
||
project.selection_map_changed()
|
||
cursor_position_label.text = "[%s×%s]" % [project.size.x, project.size.y]
|
||
|
||
await RenderingServer.frame_post_draw
|
||
canvas.queue_redraw()
|
||
second_viewport.get_child(0).get_node("CanvasPreview").queue_redraw()
|
||
canvas_preview_container.canvas_preview.queue_redraw()
|
||
if !project.has_changed:
|
||
if project == current_project:
|
||
main_window.title = main_window.title + "(*)"
|
||
project.has_changed = true
|
||
|
||
|
||
func _renderer_changed(value: int) -> void:
|
||
renderer = value
|
||
|
||
|
||
# if OS.has_feature("editor"):
|
||
# return
|
||
#
|
||
# # Sets GLES2 as the default value in `override.cfg`.
|
||
# # Without this, switching to GLES3 does not work, because it will default to GLES2.
|
||
# ProjectSettings.set_initial_value("rendering/quality/driver/driver_name", "GLES2")
|
||
# var renderer_name := OS.get_video_driver_name(renderer)
|
||
# ProjectSettings.set_setting("rendering/quality/driver/driver_name", renderer_name)
|
||
# ProjectSettings.save_custom(OVERRIDE_FILE)
|
||
|
||
|
||
## Use this to prepare Pixelorama before opening a dialog.
|
||
func dialog_open(open: bool) -> void:
|
||
var dim_color := Color.WHITE
|
||
if open:
|
||
if dim_on_popup:
|
||
dim_color = Color(0.5, 0.5, 0.5)
|
||
|
||
var tween := create_tween().set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_OUT)
|
||
tween.tween_property(control, "modulate", dim_color, 0.1)
|
||
|
||
|
||
func popup_error(text: String) -> void:
|
||
error_dialog.set_text(text)
|
||
error_dialog.popup_centered()
|
||
dialog_open(true)
|
||
|
||
|
||
## sets the [member BaseButton.disabled] property of the [param button] to [param disable],
|
||
## changes the cursor shape for it accordingly, and dims/brightens any textures it may have.
|
||
func disable_button(button: BaseButton, disable: bool) -> void:
|
||
button.disabled = disable
|
||
if disable:
|
||
button.mouse_default_cursor_shape = Control.CURSOR_FORBIDDEN
|
||
else:
|
||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
|
||
if button is Button:
|
||
for c in button.get_children():
|
||
if c is TextureRect:
|
||
c.modulate.a = 0.5 if disable else 1.0
|
||
break
|
||
|
||
|
||
## Changes the texture of the [param texture_rect] to another texture of name [param new_file_name]
|
||
## present in the same directory as the old one.
|
||
func change_button_texturerect(texture_rect: TextureRect, new_file_name: String) -> void:
|
||
if !texture_rect.texture:
|
||
return
|
||
var file_name := texture_rect.texture.resource_path.get_basename().get_file()
|
||
var directory_path := texture_rect.texture.resource_path.get_basename().replace(file_name, "")
|
||
texture_rect.texture = load(directory_path.path_join(new_file_name))
|
||
|
||
|
||
## Joins each [String] path in [param basepaths] with [param subpath] using
|
||
## [method String.path_join]
|
||
func path_join_array(basepaths: PackedStringArray, subpath: String) -> PackedStringArray:
|
||
var res := PackedStringArray()
|
||
for _path in basepaths:
|
||
res.append(_path.path_join(subpath))
|
||
return res
|
||
|
||
|
||
## Used by undo/redo operations to store compressed images in memory.
|
||
func undo_redo_compress_images(
|
||
redo_data: Dictionary, undo_data: Dictionary, project := current_project
|
||
) -> void:
|
||
for image in redo_data:
|
||
if not image is Image:
|
||
continue
|
||
var new_image: Dictionary = redo_data[image]
|
||
var new_size := Vector2i(new_image["width"], new_image["height"])
|
||
var buffer_size: int = new_image["data"].size()
|
||
var compressed_data: PackedByteArray = new_image["data"].compress()
|
||
project.undo_redo.add_do_method(
|
||
undo_redo_draw_op.bind(image, new_size, compressed_data, buffer_size)
|
||
)
|
||
for image in undo_data:
|
||
if not image is Image:
|
||
continue
|
||
var new_image: Dictionary = undo_data[image]
|
||
var new_size := Vector2i(new_image["width"], new_image["height"])
|
||
var buffer_size: int = new_image["data"].size()
|
||
var compressed_data: PackedByteArray = new_image["data"].compress()
|
||
project.undo_redo.add_undo_method(
|
||
undo_redo_draw_op.bind(image, new_size, compressed_data, buffer_size)
|
||
)
|
||
|
||
|
||
## Decompresses the [param compressed_image_data] with [param buffer_size] to the [param image]
|
||
## This is an optimization method used while performing undo/redo drawing operations.
|
||
func undo_redo_draw_op(
|
||
image: Image, new_size: Vector2i, compressed_image_data: PackedByteArray, buffer_size: int
|
||
) -> void:
|
||
var decompressed := compressed_image_data.decompress(buffer_size)
|
||
image.set_data(new_size.x, new_size.y, image.has_mipmaps(), image.get_format(), decompressed)
|
||
|
||
|
||
## Used by the Move tool for undo/redo, moves all of the [Image]s in [param images]
|
||
## by [param diff] pixels.
|
||
func undo_redo_move(diff: Vector2i, images: Array[Image]) -> void:
|
||
for image in images:
|
||
var image_copy := Image.new()
|
||
image_copy.copy_from(image)
|
||
image.fill(Color(0, 0, 0, 0))
|
||
image.blit_rect(image_copy, Rect2i(Vector2i.ZERO, image.get_size()), diff)
|
||
|
||
|
||
func create_ui_for_shader_uniforms(
|
||
shader: Shader,
|
||
params: Dictionary,
|
||
parent_node: Control,
|
||
value_changed: Callable,
|
||
file_selected: Callable
|
||
) -> void:
|
||
var code := shader.code.split("\n")
|
||
var uniforms: PackedStringArray = []
|
||
for line in code:
|
||
if line.begins_with("uniform"):
|
||
uniforms.append(line)
|
||
|
||
for uniform in uniforms:
|
||
# Example uniform:
|
||
# uniform float parameter_name : hint_range(0, 255) = 100.0;
|
||
var uniform_split := uniform.split("=")
|
||
var u_value := ""
|
||
if uniform_split.size() > 1:
|
||
u_value = uniform_split[1].replace(";", "").strip_edges()
|
||
else:
|
||
uniform_split[0] = uniform_split[0].replace(";", "").strip_edges()
|
||
|
||
var u_left_side := uniform_split[0].split(":")
|
||
var u_hint := ""
|
||
if u_left_side.size() > 1:
|
||
u_hint = u_left_side[1].strip_edges()
|
||
u_hint = u_hint.replace(";", "")
|
||
|
||
var u_init := u_left_side[0].split(" ")
|
||
var u_type := u_init[1]
|
||
var u_name := u_init[2]
|
||
var humanized_u_name := Keychain.humanize_snake_case(u_name) + ":"
|
||
|
||
if u_type == "float" or u_type == "int":
|
||
var label := Label.new()
|
||
label.text = humanized_u_name
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var slider := ValueSlider.new()
|
||
slider.allow_greater = true
|
||
slider.allow_lesser = true
|
||
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var min_value := 0.0
|
||
var max_value := 255.0
|
||
var step := 1.0
|
||
var range_values_array: PackedStringArray
|
||
if "hint_range" in u_hint:
|
||
var range_values: String = u_hint.replace("hint_range(", "")
|
||
range_values = range_values.replace(")", "").strip_edges()
|
||
range_values_array = range_values.split(",")
|
||
|
||
if u_type == "float":
|
||
if range_values_array.size() >= 1:
|
||
min_value = float(range_values_array[0])
|
||
else:
|
||
min_value = 0.01
|
||
|
||
if range_values_array.size() >= 2:
|
||
max_value = float(range_values_array[1])
|
||
|
||
if range_values_array.size() >= 3:
|
||
step = float(range_values_array[2])
|
||
else:
|
||
step = 0.01
|
||
|
||
if u_value != "":
|
||
slider.value = float(u_value)
|
||
else:
|
||
if range_values_array.size() >= 1:
|
||
min_value = int(range_values_array[0])
|
||
|
||
if range_values_array.size() >= 2:
|
||
max_value = int(range_values_array[1])
|
||
|
||
if range_values_array.size() >= 3:
|
||
step = int(range_values_array[2])
|
||
|
||
if u_value != "":
|
||
slider.value = int(u_value)
|
||
if params.has(u_name):
|
||
slider.value = params[u_name]
|
||
else:
|
||
params[u_name] = slider.value
|
||
slider.min_value = min_value
|
||
slider.max_value = max_value
|
||
slider.step = step
|
||
slider.value_changed.connect(value_changed.bind(u_name))
|
||
slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_child(label)
|
||
hbox.add_child(slider)
|
||
parent_node.add_child(hbox)
|
||
elif u_type == "vec2":
|
||
var label := Label.new()
|
||
label.text = humanized_u_name
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var vector2 := _vec2str_to_vector2(u_value)
|
||
var slider := VALUE_SLIDER_V2_TSCN.instantiate() as ValueSliderV2
|
||
slider.allow_greater = true
|
||
slider.allow_lesser = true
|
||
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
slider.value = vector2
|
||
if params.has(u_name):
|
||
slider.value = params[u_name]
|
||
else:
|
||
params[u_name] = slider.value
|
||
slider.value_changed.connect(value_changed.bind(u_name))
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_child(label)
|
||
hbox.add_child(slider)
|
||
parent_node.add_child(hbox)
|
||
elif u_type == "vec4":
|
||
if "source_color" in u_hint:
|
||
var label := Label.new()
|
||
label.text = humanized_u_name
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var color := _vec4str_to_color(u_value)
|
||
var color_button := ColorPickerButton.new()
|
||
color_button.custom_minimum_size = Vector2(20, 20)
|
||
color_button.color = color
|
||
if params.has(u_name):
|
||
color_button.color = params[u_name]
|
||
else:
|
||
params[u_name] = color_button.color
|
||
color_button.color_changed.connect(value_changed.bind(u_name))
|
||
color_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
color_button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_child(label)
|
||
hbox.add_child(color_button)
|
||
parent_node.add_child(hbox)
|
||
elif u_type == "sampler2D":
|
||
if u_name == "selection":
|
||
continue
|
||
var label := Label.new()
|
||
label.text = humanized_u_name
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_child(label)
|
||
if u_name.begins_with("gradient_"):
|
||
var gradient_edit := GRADIENT_EDIT_TSCN.instantiate() as GradientEditNode
|
||
gradient_edit.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
if params.has(u_name) and params[u_name] is GradientTexture2D:
|
||
gradient_edit.set_gradient_texture(params[u_name])
|
||
else:
|
||
params[u_name] = gradient_edit.texture
|
||
value_changed.call(gradient_edit.get_node("TextureRect").texture, u_name)
|
||
gradient_edit.updated.connect(
|
||
func(_gradient, _cc): value_changed.call(gradient_edit.texture, u_name)
|
||
)
|
||
hbox.add_child(gradient_edit)
|
||
else:
|
||
var file_dialog := FileDialog.new()
|
||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
|
||
file_dialog.size = Vector2(384, 281)
|
||
file_dialog.file_selected.connect(file_selected.bind(u_name))
|
||
file_dialog.use_native_dialog = use_native_file_dialogs
|
||
var button := Button.new()
|
||
button.text = "Load texture"
|
||
button.pressed.connect(file_dialog.popup_centered)
|
||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
hbox.add_child(button)
|
||
parent_node.add_child(file_dialog)
|
||
parent_node.add_child(hbox)
|
||
|
||
elif u_type == "bool":
|
||
var label := Label.new()
|
||
label.text = humanized_u_name
|
||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
var checkbox := CheckBox.new()
|
||
checkbox.text = "On"
|
||
if u_value == "true":
|
||
checkbox.button_pressed = true
|
||
if params.has(u_name):
|
||
checkbox.button_pressed = params[u_name]
|
||
else:
|
||
params[u_name] = checkbox.button_pressed
|
||
checkbox.toggled.connect(value_changed.bind(u_name))
|
||
checkbox.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||
checkbox.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
||
var hbox := HBoxContainer.new()
|
||
hbox.add_child(label)
|
||
hbox.add_child(checkbox)
|
||
parent_node.add_child(hbox)
|
||
|
||
|
||
func _vec2str_to_vector2(vec2: String) -> Vector2:
|
||
vec2 = vec2.replace("vec2(", "")
|
||
vec2 = vec2.replace(")", "")
|
||
var vec_values := vec2.split(",")
|
||
if vec_values.size() == 0:
|
||
return Vector2.ZERO
|
||
var y := float(vec_values[0])
|
||
if vec_values.size() == 2:
|
||
y = float(vec_values[1])
|
||
var vector2 := Vector2(float(vec_values[0]), y)
|
||
return vector2
|
||
|
||
|
||
func _vec4str_to_color(vec4: String) -> Color:
|
||
vec4 = vec4.replace("vec4(", "")
|
||
vec4 = vec4.replace(")", "")
|
||
var rgba_values := vec4.split(",")
|
||
var red := float(rgba_values[0])
|
||
|
||
var green := float(rgba_values[0])
|
||
if rgba_values.size() >= 2:
|
||
green = float(rgba_values[1])
|
||
|
||
var blue := float(rgba_values[0])
|
||
if rgba_values.size() >= 3:
|
||
blue = float(rgba_values[2])
|
||
|
||
var alpha := float(rgba_values[0])
|
||
if rgba_values.size() == 4:
|
||
alpha = float(rgba_values[3])
|
||
var color := Color(red, green, blue, alpha)
|
||
return color
|