1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-12 16:53:07 +00:00

Compare commits

...

25 commits

Author SHA1 Message Date
Variable c9ad65841a
Merge 5ee7566cb4 into 763783f2f1 2024-11-16 01:28:04 +02:00
Emmanouil Papadeas 763783f2f1 Improve the UI of the tile mode offsets dialog and add an Isometric button 2024-11-15 17:59:57 +02:00
Emmanouil Papadeas e10b0d1b08 Fix crash when opening the tile mode offsets dialog 2024-11-15 17:59:25 +02:00
Emmanouil Papadeas 94735fc08b [skip ci] Update Translations.pot 2024-11-15 02:08:30 +02:00
Emmanouil Papadeas 8077262b32 [skip ci] Update CHANGELOG.md 2024-11-15 02:04:59 +02:00
Emmanouil Papadeas 0d6b140dea Add border selection, fix some missing translation strings 2024-11-15 01:41:44 +02:00
Emmanouil Papadeas dec698024c Implement selection expanding and shrinking via the Select menu 2024-11-14 17:59:53 +02:00
Emmanouil Papadeas 785d8cfc83 Hide the density slider by default
So that it doesn't appear in the shape tools, where it has no effect.
2024-11-14 16:22:53 +02:00
Emmanouil Papadeas 4c7d7da5e7 Fix regression where pressing Enter or Control would not confirm/cancel selection when a selection tool wasn't active 2024-11-14 01:39:41 +02:00
Emmanouil Papadeas 36329efaf6 Add density to the square & circle brushes
00% density means that the brush gets completely drawn, anything less leaves gaps inside the brush, acting like a spray tool.
2024-11-14 01:02:51 +02:00
Emmanouil Papadeas 7c1435e95f When using the mouse wheel over a slider, don't scroll in ScrollContainers 2024-11-13 17:32:01 +02:00
Emmanouil Papadeas ad77d98f42 Slightly optimize circle brushes by only calling the DrawingAlgos methods once while drawing
They keep getting called when size dynamics are enabled, however.
2024-11-13 02:55:15 +02:00
Emmanouil Papadeas 2600180736 Remove the Recorder from the Web version
It's not working anyway, and I'm not sure if there is a way to make it work, at least with a good and user-friendly way. If we find a way we could re-add it in the future.
2024-11-13 00:40:58 +02:00
Emmanouil Papadeas 5739a8b28e [skip ci] Update CHANGELOG.md 2024-11-12 01:46:50 +02:00
Emmanouil Papadeas ce738f02c2 Don't change brush size when resizing the timeline cels and the palette swatches 2024-11-12 00:59:01 +02:00
Emmanouil Papadeas b0b1361722 Fix layer effect slider values being rounded to the nearest integer 2024-11-12 00:47:53 +02:00
Variable 5fa97988b5
Fixed unexpected behavior of resize_selection() (#1132)
* Fixed unexpected behavior of resize_selection()

* Fix typo

---------

Co-authored-by: Emmanouil Papadeas <35376950+OverloadedOrama@users.noreply.github.com>
2024-11-10 23:12:09 +02:00
Variable 5ee7566cb4 formatting and a bugfix 2024-10-26 08:41:48 +05:00
Variable aba08cbc37 linting 2024-10-26 08:09:25 +05:00
Variable df8df9d72b
Merge branch 'Orama-Interactive:master' into grids 2024-10-26 07:58:27 +05:00
Variable fb826995ca removed some left over stuff 2024-10-15 17:22:25 +05:00
Variable d34c7739fb formatting 2024-10-15 17:18:28 +05:00
Variable 8990ff2816 fixed a bug 2024-10-15 16:59:22 +05:00
Variable 6a60050bd3 Fixed more stuff 2024-10-15 16:39:51 +05:00
Variable 8b12eac231 Allow upto 10 grids 2024-10-15 14:13:26 +05:00
30 changed files with 773 additions and 294 deletions

View file

@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). All the dates are in YYYY-MM-DD format.
<br><br>
## [v1.0.5] - Unreleased
This update has been brought to you by the contributions of:
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind))
Built using Godot 4.3
### Added
- Add density to the square & circle brushes. 100% density means that the brush gets completely drawn. Anything less leaves gaps inside the brush, acting like a spray tool.
- Selection expanding, shrinking and borders have been added as options in the Select menu.
- Mouse buttons can now be used as menu shortcuts. [#1070](https://github.com/Orama-Interactive/Pixelorama/issues/1070)
- Added confirm and cancel buttons in the selection tool options to confirm/cancel an active transformation.
- OKHSL Lightness sorting in palettes has been implemented. [#1126](https://github.com/Orama-Interactive/Pixelorama/pull/1126)
### Changed
- The brush size no longer changes by <kbd>Control</kbd> + Mouse Wheel when resizing the timeline cels or the palette swatches.
- The Recorder panel now automatically records for the current project. This also allows for multiple projects to be recorded at the same time.
### Fixed
- Panels no longer get scrolled when using the mouse wheel over a slider.
- Fixed layer effect slider values being rounded to the nearest integer.
- Fixed memory leak where the project remained referenced by a drawing tool, even when its tab was closed.
- Fixed memory leak where the first project remained forever references in memory by the Recorder panel.
- Slightly optimize circle brushes by only calling the ellipse algorithms once while drawing
### Removed
- The Recorder panel has been removed from the Web version. It wasn't functional anyway in a way that was useful, and it's unsure if we can find a way to make it work.
## [v1.0.4] - 2024-10-25
This update has been brought to you by the contributions of:
Fayez Akhtar ([@Variable-ind](https://github.com/Variable-ind)), Mariano Semelman ([@msemelman](https://github.com/msemelman))

View file

@ -205,6 +205,43 @@ msgstr ""
msgid "Invert"
msgstr ""
msgid "Modify"
msgstr ""
#. Found under the Select menu, in the Modify submenu. When selected, it shows a window that lets users expand the active selection.
msgid "Expand"
msgstr ""
#. Title of a window that lets users expand the active selection.
msgid "Expand Selection"
msgstr ""
#. Found under the Select menu, in the Modify submenu. When selected, it shows a window that lets users shrink the active selection.
msgid "Shrink"
msgstr ""
#. Title of a window that lets users shrink the active selection.
msgid "Shrink Selection"
msgstr ""
#. Found under the Select menu, in the Modify submenu. When selected, it shows a window that lets users create a border of the active selection.
msgid "Border"
msgstr ""
#. Title of a window that lets users create a border of the active selection.
msgid "Border Selection"
msgstr ""
#. Refers to a diamond-like shape.
msgid "Diamond"
msgstr ""
msgid "Circle"
msgstr ""
msgid "Square"
msgstr ""
msgid "Grayscale View"
msgstr ""
@ -230,19 +267,16 @@ msgstr ""
msgid "Tile Mode Offsets"
msgstr ""
msgid "X-basis x:"
#. Found under "Tile Mode Offsets". Basis is a linear algebra term. https://en.wikipedia.org/wiki/Basis_(linear_algebra)
msgid "X-basis:"
msgstr ""
msgid "X-basis y:"
#. Found under "Tile Mode Offsets". Basis is a linear algebra term. https://en.wikipedia.org/wiki/Basis_(linear_algebra)
msgid "Y-basis:"
msgstr ""
msgid "Y-basis x:"
msgstr ""
msgid "Y-basis y:"
msgstr ""
msgid "Tile Mask"
#. Found under "Tile Mode Offsets". It's a button that when pressed, enables masking for tile mode. Masking essentially limits drawing to the visible pixels of the image, thus preventing from drawing on transparent pixels.
msgid "Masking:"
msgstr ""
msgid "Reset"
@ -1865,6 +1899,10 @@ msgstr ""
msgid "Fills the drawn shape with color, instead of drawing a hollow shape"
msgstr ""
#. Found in the tool options of the Pencil, Eraser and Shading tools. It is a percentage of how dense the brush is. 100% density means that the brush gets completely drawn, anything less leaves gaps inside the brush, acting like a spray tool.
msgid "Density:"
msgstr ""
msgid "Brush color from"
msgstr ""
@ -2808,6 +2846,10 @@ msgstr ""
msgid "Sort by value"
msgstr ""
#. An option of the Sort palette button found in the palette panel. When selected, the colors of the palette are being sorted based on their OKHSL Lightness.
msgid "Sort by lightness"
msgstr ""
#. An option of the Sort palette button found in the palette panel. When selected, the colors of the palette are being sorted based on their red channel value.
msgid "Sort by red"
msgstr ""

View file

@ -534,6 +534,10 @@ class SelectionAPI:
Global.canvas.selection.move_borders_start()
else:
Global.canvas.selection.transform_content_start()
if Global.canvas.selection.original_bitmap.is_empty(): # To avoid copying twice.
Global.canvas.selection.original_bitmap.copy_from(Global.current_project.selection_map)
Global.canvas.selection.big_bounding_rectangle.size = new_size
Global.canvas.selection.resize_selection()
Global.canvas.selection.move_borders_end()

View file

@ -71,7 +71,7 @@ enum EffectsMenu {
SHADER
}
## Enumeration of items present in the Select Menu.
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE }
enum SelectMenu { SELECT_ALL, CLEAR_SELECTION, INVERT, TILE_MODE, MODIFY }
## Enumeration of items present in the Help Menu.
enum HelpMenu {
VIEW_SPLASH_SCREEN,
@ -332,55 +332,8 @@ var default_height := 64 ## Found in Preferences. The default height of startup
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):
if value == grid_type:
return
grid_type = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The size of rectangular grid.
var grid_size := Vector2i(2, 2):
set(value):
if value == grid_size:
return
grid_size = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The size of isometric grid.
var isometric_grid_size := Vector2i(16, 8):
set(value):
if value == isometric_grid_size:
return
isometric_grid_size = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The grid offset from top-left corner of the canvas.
var grid_offset := Vector2i.ZERO:
set(value):
if value == grid_offset:
return
grid_offset = value
if is_instance_valid(canvas.grid):
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):
if value == grid_draw_over_tile_mode:
return
grid_draw_over_tile_mode = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Found in Preferences. The color of grid.
var grid_color := Color.BLACK:
set(value):
if value == grid_color:
return
grid_color = value
if is_instance_valid(canvas.grid):
canvas.grid.queue_redraw()
## Contains dictionaries of individual grids.
var grids: Array[Grid] = []
## 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):
@ -672,6 +625,62 @@ var cel_button_scene: PackedScene = load("res://src/UI/Timeline/CelButton.tscn")
@onready var error_dialog: AcceptDialog = control.find_child("ErrorDialog")
class Grid:
var grid_type := GridTypes.CARTESIAN:
set(value):
if value == grid_type:
return
grid_type = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The size of rectangular grid.
var grid_size := Vector2i(2, 2):
set(value):
if value == grid_size:
return
grid_size = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The size of isometric grid.
var isometric_grid_size := Vector2i(16, 8):
set(value):
if value == isometric_grid_size:
return
isometric_grid_size = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The grid offset from top-left corner of the canvas.
var grid_offset := Vector2i.ZERO:
set(value):
if value == grid_offset:
return
grid_offset = value
if is_instance_valid(Global.canvas.grid):
Global.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):
if value == grid_draw_over_tile_mode:
return
grid_draw_over_tile_mode = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
## Found in Preferences. The color of grid.
var grid_color := Color.BLACK:
set(value):
if value == grid_color:
return
grid_color = value
if is_instance_valid(Global.canvas.grid):
Global.canvas.grid.queue_redraw()
func _init(properties := {}) -> void:
Global.grids.append(self)
for prop in properties.keys():
set(prop, properties[prop])
func _init() -> void:
# Load settings from the config file
config_cache.load(CONFIG_PATH)
@ -708,6 +717,8 @@ func _init() -> void:
func _ready() -> void:
# Initialize Grid
Grid.new() # gets auto added to grids array
_initialize_keychain()
default_width = config_cache.get_value("preferences", "default_width", default_width)
default_height = config_cache.get_value("preferences", "default_height", default_height)
@ -724,13 +735,23 @@ func _ready() -> void:
if get(pref) == null:
continue
var value = config_cache.get_value("preferences", pref)
set(pref, value)
if pref == "grids":
if value:
update_grids(value)
else:
set(pref, value)
if OS.is_sandboxed():
Global.use_native_file_dialogs = true
await get_tree().process_frame
project_switched.emit()
func update_grids(grids_data: Dictionary):
grids.clear()
for grid_idx in grids_data.size():
Grid.new(grids_data[grid_idx]) # gets auto added to grids array
func _initialize_keychain() -> void:
Keychain.config_file = config_cache
Keychain.actions = {

View file

@ -1,7 +1,8 @@
class_name SelectionMap
extends Image
var invert_shader := preload("res://src/Shaders/Effects/Invert.gdshader")
const INVERT_SHADER := preload("res://src/Shaders/Effects/Invert.gdshader")
const OUTLINE_INLINE_SHADER := preload("res://src/Shaders/Effects/OutlineInline.gdshader")
func is_pixel_selected(pixel: Vector2i, calculate_offset := true) -> bool:
@ -87,8 +88,7 @@ func clear() -> void:
func invert() -> void:
var params := {"red": true, "green": true, "blue": true, "alpha": true}
var gen := ShaderImageEffect.new()
gen.generate_image(self, invert_shader, params, get_size())
self.convert(Image.FORMAT_LA8)
gen.generate_image(self, INVERT_SHADER, params, get_size())
## Returns a copy of itself that is cropped to [param size].
@ -183,3 +183,36 @@ func resize_bitmap_values(
if new_bitmap_size != size:
crop(new_bitmap_size.x, new_bitmap_size.y)
blit_rect(smaller_image, Rect2i(Vector2i.ZERO, new_bitmap_size), dst)
func expand(width: int, brush: int) -> void:
var params := {
"color": Color(1, 1, 1, 1),
"width": width,
"brush": brush,
}
var gen := ShaderImageEffect.new()
gen.generate_image(self, OUTLINE_INLINE_SHADER, params, get_size())
func shrink(width: int, brush: int) -> void:
var params := {
"color": Color(0),
"width": width,
"brush": brush,
"inside": true,
}
var gen := ShaderImageEffect.new()
gen.generate_image(self, OUTLINE_INLINE_SHADER, params, get_size())
func border(width: int, brush: int) -> void:
var params := {
"color": Color(1, 1, 1, 1),
"width": width,
"brush": brush,
"inside": true,
"keep_border_only": true,
}
var gen := ShaderImageEffect.new()
gen.generate_image(self, OUTLINE_INLINE_SHADER, params, get_size())

View file

@ -54,7 +54,7 @@ func generate_image(img: Image, shader: Shader, params: Dictionary, size: Vector
RenderingServer.free_rid(ci_rid)
RenderingServer.free_rid(mat_rid)
RenderingServer.free_rid(texture)
viewport_texture.convert(Image.FORMAT_RGBA8)
viewport_texture.convert(img.get_format())
img.copy_from(viewport_texture)
if resized_width:
img.crop(img.get_width() - 1, img.get_height())

View file

@ -148,13 +148,13 @@ static func create_ui_for_shader_uniforms(
if u_value != "":
slider.value = int(u_value)
slider.min_value = min_value
slider.max_value = max_value
slider.step = step
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
hbox.add_child(slider)

View file

@ -90,3 +90,4 @@ func _on_PaletteScroll_gui_input(event: InputEvent) -> void:
return
resize_grid()
set_sliders(palette_grid.current_palette, palette_grid.grid_window_origin + scroll_vector)
get_window().set_input_as_handled()

View file

@ -0,0 +1,163 @@
extends GridContainer
var grid_preferences: Array[GridPreference] = [
GridPreference.new("grid_type", "GridType", "selected", Global.GridTypes.CARTESIAN),
GridPreference.new("grid_size", "GridSizeValue", "value", Vector2i(2, 2)),
GridPreference.new("isometric_grid_size", "IsometricGridSizeValue", "value", Vector2i(16, 8)),
GridPreference.new("grid_offset", "GridOffsetValue", "value", Vector2i.ZERO),
GridPreference.new("grid_draw_over_tile_mode", "GridDrawOverTileMode", "button_pressed", false),
GridPreference.new("grid_color", "GridColor", "color", Color.BLACK),
]
var grid_selected: int = 0:
set(key):
grid_selected = key
for child: BaseButton in grids_select_container.get_children():
if child.get_index() == grid_selected:
child.self_modulate = Color.WHITE
else:
child.self_modulate = Color.DIM_GRAY
var grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
if grids.has(key):
update_pref_ui(grids[key])
@onready var grids_select_container: HFlowContainer = $GridsSelectContainer
class GridPreference:
var prop_name: String
var node_path: String
var value_type: String
var default_value
func _init(
_prop_name: String,
_node_path: String,
_value_type: String,
_default_value = null,
_require_restart := false
) -> void:
prop_name = _prop_name
node_path = _node_path
value_type = _value_type
if _default_value != null:
default_value = _default_value
func _ready() -> void:
var grids = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
Global.config_cache.set_value("preferences", "grids", grids)
$GridsCount.value = grids.size()
if grids.size() == 1:
add_remove_select_button(0)
for pref in grid_preferences:
if not has_node(pref.node_path):
continue
var node := get_node(pref.node_path)
var restore_default_button := RestoreDefaultButton.new()
restore_default_button.pressed.connect(
_on_grid_pref_value_changed.bind(pref.default_value, pref, restore_default_button)
)
restore_default_button.setting_name = pref.prop_name
restore_default_button.value_type = pref.value_type
restore_default_button.default_value = pref.default_value
restore_default_button.node = node
var node_position := node.get_index()
node.get_parent().add_child(restore_default_button)
node.get_parent().move_child(restore_default_button, node_position)
match pref.value_type:
"button_pressed":
node.toggled.connect(_on_grid_pref_value_changed.bind(pref, restore_default_button))
"value":
node.value_changed.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
"color":
node.get_picker().presets_visible = false
node.color_changed.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
"selected":
node.item_selected.connect(
_on_grid_pref_value_changed.bind(pref, restore_default_button)
)
grid_selected = 0
func _on_grid_pref_value_changed(value, pref: GridPreference, button: RestoreDefaultButton) -> void:
var grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
if grids.has(grid_selected): # Failsafe (Always true)
var grid_info: Dictionary = grids[grid_selected]
var prop := pref.prop_name
grid_info[prop] = value
grids[grid_selected] = grid_info
Global.update_grids(grids)
var default_value = pref.default_value
var disable: bool = Global.grids[grid_selected].get(prop) == default_value
if typeof(value) == TYPE_COLOR:
disable = value.is_equal_approx(default_value)
disable_restore_default_button(button, disable)
Global.config_cache.set_value("preferences", "grids", grids)
func _on_grids_count_value_changed(value: float) -> void:
var grid_idx = int(value - 1)
var grids: Dictionary = Global.config_cache.get_value(
"preferences", "grids", {0: create_default_properties()}
)
if grid_idx >= grids_select_container.get_child_count():
for key in range(grids_select_container.get_child_count(), grid_idx + 1):
if not grids.has(key):
grids[key] = create_default_properties()
add_remove_select_button(key)
else:
for key: int in range(grid_idx + 1, grids.size()):
grids.erase(key)
add_remove_select_button(key, true)
Global.update_grids(grids)
Global.config_cache.set_value("preferences", "grids", grids)
func create_default_properties() -> Dictionary:
var grid_info = {}
for pref in grid_preferences:
grid_info[pref.prop_name] = pref.default_value
return grid_info
func disable_restore_default_button(button: RestoreDefaultButton, disable: bool) -> void:
button.disabled = disable
if disable:
button.mouse_default_cursor_shape = Control.CURSOR_ARROW
button.tooltip_text = ""
else:
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
button.tooltip_text = "Restore default value"
func add_remove_select_button(grid_idx: int, remove := false):
if not remove:
var select_button = Button.new()
select_button.text = str(grid_idx)
grids_select_container.add_child(select_button)
select_button.pressed.connect(func(): grid_selected = grid_idx)
else:
if grid_idx < grids_select_container.get_child_count():
grids_select_container.get_child(grid_idx).queue_free()
grid_selected = min(grid_selected, grid_idx - 1)
func update_pref_ui(grid_data: Dictionary):
for pref in grid_preferences:
var key = pref.prop_name
if grid_data.has(key):
var node := get_node(pref.node_path)
node.set(pref.value_type, grid_data[key])

View file

@ -94,21 +94,6 @@ var preferences: Array[Preference] = [
Preference.new("smooth_zoom", "Canvas/ZoomOptions/SmoothZoom", "button_pressed", true),
Preference.new("integer_zoom", "Canvas/ZoomOptions/IntegerZoom", "button_pressed", false),
Preference.new("snapping_distance", "Canvas/SnappingOptions/DistanceValue", "value", 32.0),
Preference.new(
"grid_type", "Canvas/GridOptions/GridType", "selected", Global.GridTypes.CARTESIAN
),
Preference.new("grid_size", "Canvas/GridOptions/GridSizeValue", "value", Vector2i(2, 2)),
Preference.new(
"isometric_grid_size", "Canvas/GridOptions/IsometricGridSizeValue", "value", Vector2i(16, 8)
),
Preference.new("grid_offset", "Canvas/GridOptions/GridOffsetValue", "value", Vector2i.ZERO),
Preference.new(
"grid_draw_over_tile_mode",
"Canvas/GridOptions/GridDrawOverTileMode",
"button_pressed",
false
),
Preference.new("grid_color", "Canvas/GridOptions/GridColor", "color", Color.BLACK),
Preference.new(
"pixel_grid_show_at_zoom", "Canvas/PixelGridOptions/ShowAtZoom", "value", 1500.0
),

View file

@ -1,8 +1,10 @@
[gd_scene load_steps=9 format=3 uid="uid://b3hkjj3s6pe4x"]
[gd_scene load_steps=11 format=3 uid="uid://b3hkjj3s6pe4x"]
[ext_resource type="Script" path="res://src/Preferences/PreferencesDialog.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://bq7ibhm0txl5p" path="res://addons/keychain/ShortcutEdit.tscn" id="3"]
[ext_resource type="Script" path="res://src/Preferences/ThemesPreferences.gd" id="3_nvl8k"]
[ext_resource type="Script" path="res://src/Preferences/GridPreferences.gd" id="4_76iff"]
[ext_resource type="PackedScene" uid="uid://yjhp0ssng2mp" path="res://src/UI/Nodes/ValueSlider.tscn" id="5_rlmsh"]
[ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="7"]
[ext_resource type="Script" path="res://src/Preferences/ExtensionsPreferences.gd" id="7_8ume5"]
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="8"]
@ -482,6 +484,30 @@ layout_mode = 2
theme_override_constants/h_separation = 4
theme_override_constants/v_separation = 4
columns = 3
script = ExtResource("4_76iff")
[node name="GridsCountLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
text = "Grids Visible:"
[node name="Spacer" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridsCount" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" instance=ExtResource("5_rlmsh")]
layout_mode = 2
min_value = 1.0
max_value = 10.0
value = 1.0
[node name="GridsSelectLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
text = "Editing Grid:"
[node name="Spacer2" type="Control" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridsSelectContainer" type="HFlowContainer" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
[node name="GridTypeLabel" type="Label" parent="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions"]
layout_mode = 2
@ -1478,6 +1504,7 @@ dialog_text = "Are you sure you want to reset the selected options? There will b
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Language/System Language" to="." method="_on_language_pressed" binds= [1]]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/ShrinkContainer/ShrinkApplyButton" to="." method="_on_shrink_apply_button_pressed"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Interface/InterfaceOptions/FontSizeContainer/FontSizeApplyButton" to="." method="_on_font_size_apply_button_pressed"]
[connection signal="value_changed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions/GridsCount" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Canvas/GridOptions" method="_on_grids_count_value_changed"]
[connection signal="pressed" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/ExtensionsHeader/Explore" to="Store" method="_on_explore_pressed"]
[connection signal="empty_clicked" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_empty_clicked"]
[connection signal="item_selected" from="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions/InstalledExtensions" to="HSplitContainer/VBoxContainer/ScrollContainer/RightSide/Extensions" method="_on_InstalledExtensions_item_selected"]

View file

@ -4,9 +4,10 @@ render_mode unshaded;
uniform vec4 color : source_color = vec4(1.0);
uniform float width : hint_range(0, 10, 1) = 1.0;
// uniform_data pattern type:: OptionButton [Diamond||Circle||Square]
uniform int pattern : hint_range(0, 2) = 0;
// uniform_data brush type:: OptionButton [Diamond||Circle||Square]
uniform int brush : hint_range(0, 2) = 0;
uniform bool inside = false;
uniform bool keep_border_only = false;
uniform sampler2D selection : filter_nearest;
bool is_zero_approx(float num) {
@ -17,11 +18,11 @@ bool has_contrary_neighbour(vec2 uv, vec2 texture_pixel_size, sampler2D tex) {
for (float i = -ceil(width); i <= ceil(width); i++) {
float offset;
if (pattern == 0) {
if (brush == 0) {
offset = width - abs(i);
} else if (pattern == 1) {
} else if (brush == 1) {
offset = floor(sqrt(pow(width + 0.5, 2) - i * i));
} else if (pattern == 2) {
} else if (brush == 2) {
offset = width;
}
@ -44,7 +45,15 @@ void fragment() {
if ((output.a > 0.0) == inside && has_contrary_neighbour(UV, TEXTURE_PIXEL_SIZE, TEXTURE)) {
output.rgb = inside ? mix(output.rgb, color.rgb, color.a) : color.rgb;
output.a += (1.0 - output.a) * color.a;
if (is_zero_approx(color.a)) {
output.a = color.a;
}
else {
output.a += (1.0 - output.a) * color.a;
}
}
else if (keep_border_only) {
output.a = 0.0;
}
COLOR = mix(original_color, output, selection_color.a);

View file

@ -1,8 +1,11 @@
extends BaseTool
const IMAGE_BRUSHES := [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM]
var _brush := Brushes.get_default_brush()
var _brush_size := 1
var _brush_size_dynamics := 1
var _brush_density := 100
var _brush_flip_x := false
var _brush_flip_y := false
var _brush_rotate_90 := false
@ -89,6 +92,12 @@ func _reset_dynamics() -> void:
save_config()
func _on_density_value_slider_value_changed(value: int) -> void:
_brush_density = value
update_config()
save_config()
func _on_InterpolateFactor_value_changed(value: float) -> void:
_brush_interpolate = int(value)
update_config()
@ -111,6 +120,7 @@ func get_config() -> Dictionary:
"brush_type": _brush.type,
"brush_index": _brush.index,
"brush_size": _brush_size,
"brush_density": _brush_density,
"brush_interpolate": _brush_interpolate,
"brush_flip_x": _brush_flip_x,
"brush_flip_y": _brush_flip_y,
@ -128,6 +138,7 @@ func set_config(config: Dictionary) -> void:
_brush_size_dynamics = _brush_size
if Tools.dynamics_size != Tools.Dynamics.NONE:
_brush_size_dynamics = Tools.brush_size_min
_brush_density = config.get("brush_density", _brush_density)
_brush_interpolate = config.get("brush_interpolate", _brush_interpolate)
_brush_flip_x = config.get("brush_flip_x", _brush_flip_x)
_brush_flip_y = config.get("brush_flip_y", _brush_flip_y)
@ -177,11 +188,13 @@ func update_brush() -> void:
_brush_texture = ImageTexture.create_from_image(_brush_image)
update_mirror_brush()
_stroke_dimensions = _brush_image.get_size()
_circle_tool_shortcut = []
_indicator = _create_brush_indicator()
_polylines = _create_polylines(_indicator)
$Brush/Type/Texture.texture = _brush_texture
$ColorInterpolation.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM]
$RotationOptions.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM]
$DensityValueSlider.visible = _brush.type not in IMAGE_BRUSHES
$ColorInterpolation.visible = _brush.type in IMAGE_BRUSHES
$RotationOptions.visible = _brush.type in IMAGE_BRUSHES
func update_random_image() -> void:
@ -275,6 +288,7 @@ func draw_end(pos: Vector2i) -> void:
super.draw_end(pos)
_stroke_project = null
_stroke_images = []
_circle_tool_shortcut = []
_brush_size_dynamics = _brush_size
if Tools.dynamics_size != Tools.Dynamics.NONE:
_brush_size_dynamics = Tools.brush_size_min
@ -313,10 +327,6 @@ func _prepare_tool() -> void:
# This may prevent a few tests when setting pixels
_is_mask_size_zero = _mask.size() == 0
match _brush.type:
Brushes.CIRCLE:
_prepare_circle_tool(false)
Brushes.FILLED_CIRCLE:
_prepare_circle_tool(true)
Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM:
# save _brush_image for safe keeping
_brush_image = _create_blended_brush_image(_orignal_brush_image)
@ -326,19 +336,6 @@ func _prepare_tool() -> void:
_stroke_dimensions = _brush_image.get_size()
func _prepare_circle_tool(fill: bool) -> void:
var circle_tool_map := _create_circle_indicator(_brush_size_dynamics, fill)
# Go through that BitMap and build an Array of the "displacement" from the center of the bits
# that are true.
var diameter := _brush_size_dynamics * 2 + 1
for n in range(0, diameter):
for m in range(0, diameter):
if circle_tool_map.get_bitv(Vector2i(m, n)):
_circle_tool_shortcut.append(
Vector2i(m - _brush_size_dynamics, n - _brush_size_dynamics)
)
## Make sure to always have invoked _prepare_tool() before this. This computes the coordinates to be
## drawn if it can (except for the generic brush, when it's actually drawing them)
func _draw_tool(pos: Vector2) -> PackedVector2Array:
@ -507,7 +504,7 @@ func draw_indicator(left: bool) -> void:
func draw_indicator_at(pos: Vector2i, offset: Vector2i, color: Color) -> void:
var canvas: Node2D = Global.canvas.indicators
if _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] and not _draw_line:
if _brush.type in IMAGE_BRUSHES and not _draw_line:
pos -= _brush_image.get_size() / 2
pos -= offset
canvas.draw_texture(_brush_texture, pos)
@ -537,6 +534,8 @@ func _set_pixel(pos: Vector2i, ignore_mirroring := false) -> void:
func _set_pixel_no_cache(pos: Vector2i, ignore_mirroring := false) -> void:
if randi() % 100 >= _brush_density:
return
pos = _stroke_project.tiles.get_canon_position(pos)
if Global.current_project.has_selection:
pos = Global.current_project.selection_map.get_canon_position(pos)
@ -621,10 +620,24 @@ func _create_pixel_indicator(brush_size: int) -> BitMap:
func _create_circle_indicator(brush_size: int, fill := false) -> BitMap:
_circle_tool_shortcut = []
if Tools.dynamics_size != Tools.Dynamics.NONE:
_circle_tool_shortcut = []
var brush_size_v2 := Vector2i(brush_size, brush_size)
var diameter := brush_size_v2 * 2 + Vector2i.ONE
return _fill_bitmap_with_points(_compute_draw_tool_circle(brush_size_v2, fill), diameter)
var diameter_v2 := brush_size_v2 * 2 + Vector2i.ONE
var circle_tool_map := _fill_bitmap_with_points(
_compute_draw_tool_circle(brush_size_v2, fill), diameter_v2
)
if _circle_tool_shortcut.is_empty():
# Go through that BitMap and build an Array of the "displacement"
# from the center of the bits that are true.
var diameter := _brush_size_dynamics * 2 + 1
for n in range(0, diameter):
for m in range(0, diameter):
if circle_tool_map.get_bitv(Vector2i(m, n)):
_circle_tool_shortcut.append(
Vector2i(m - _brush_size_dynamics, n - _brush_size_dynamics)
)
return circle_tool_map
func _create_line_indicator(indicator: BitMap, start: Vector2i, end: Vector2i) -> BitMap:

View file

@ -1,9 +1,10 @@
[gd_scene load_steps=8 format=3 uid="uid://ubyatap3sylf"]
[gd_scene load_steps=9 format=3 uid="uid://ubyatap3sylf"]
[ext_resource type="PackedScene" uid="uid://yjhp0ssng2mp" path="res://src/UI/Nodes/ValueSlider.tscn" id="1"]
[ext_resource type="PackedScene" uid="uid://ctfgfelg0sho8" path="res://src/Tools/BaseTool.tscn" id="2"]
[ext_resource type="Script" path="res://src/Tools/BaseDraw.gd" id="3"]
[ext_resource type="Script" path="res://src/UI/Nodes/CollapsibleContainer.gd" id="3_76bek"]
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="5_kdxku"]
[sub_resource type="ButtonGroup" id="ButtonGroup_7u3x0"]
resource_name = "rotate"
@ -130,7 +131,25 @@ suffix = "px"
global_increment_action = "brush_size_increment"
global_decrement_action = "brush_size_decrement"
[node name="ColorInterpolation" parent="." index="4" instance=ExtResource("1")]
[node name="DensityValueSlider" type="TextureProgressBar" parent="." index="4"]
visible = false
custom_minimum_size = Vector2(0, 24)
layout_mode = 2
focus_mode = 2
mouse_default_cursor_shape = 2
theme_type_variation = &"ValueSlider"
min_value = 1.0
value = 100.0
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource("5_kdxku")
prefix = "Density:"
suffix = "%"
[node name="ColorInterpolation" parent="." index="5" instance=ExtResource("1")]
visible = false
layout_mode = 2
tooltip_text = "0: Color from the brush itself, 100: the currently selected color"
@ -143,4 +162,5 @@ prefix = "Brush color from:"
[connection signal="toggled" from="RotationOptions/GridContainer/Rotate/Rotate270" to="." method="_on_rotate_270_toggled"]
[connection signal="pressed" from="Brush/Type" to="." method="_on_BrushType_pressed"]
[connection signal="value_changed" from="Brush/BrushSize" to="." method="_on_BrushSize_value_changed"]
[connection signal="value_changed" from="DensityValueSlider" to="." method="_on_density_value_slider_value_changed"]
[connection signal="value_changed" from="ColorInterpolation" to="." method="_on_InterpolateFactor_value_changed"]

View file

@ -159,16 +159,17 @@ func draw_move(pos: Vector2i) -> void:
else:
pos.x = _start_pos.x
if Input.is_action_pressed("transform_snap_grid"):
_offset = _offset.snapped(Global.grid_size)
_offset = _offset.snapped(Global.grids[0].grid_size)
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grid_size)
selection_node.big_bounding_rectangle.position = prev_pos.snapped(Global.grids[0].grid_size)
selection_node.marching_ants_outline.offset += Vector2(
selection_node.big_bounding_rectangle.position - prev_pos
)
pos = pos.snapped(Global.grid_size)
var grid_offset := Global.grid_offset
pos = pos.snapped(Global.grids[0].grid_size)
var grid_offset := Global.grids[0].grid_offset
grid_offset = Vector2i(
fmod(grid_offset.x, Global.grid_size.x), fmod(grid_offset.y, Global.grid_size.y)
fmod(grid_offset.x, Global.grids[0].grid_size.x),
fmod(grid_offset.y, Global.grids[0].grid_size.y)
)
pos += grid_offset

View file

@ -129,19 +129,20 @@ func draw_preview() -> void:
func snap_position(pos: Vector2) -> Vector2:
var snapping_distance := Global.snapping_distance / Global.camera.zoom.x
if Global.snap_to_rectangular_grid_boundary:
var grid_pos := pos.snapped(Global.grid_size)
grid_pos += Vector2(Global.grid_offset)
var grid_pos := pos.snapped(Global.grids[0].grid_size)
grid_pos += Vector2(Global.grids[0].grid_offset)
# keeping grid_pos as is would have been fine but this adds extra accuracy as to
# which snap point (from the list below) is closest to mouse and occupy THAT point
var t_l := grid_pos + Vector2(-Global.grid_size.x, -Global.grid_size.y)
var t_c := grid_pos + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
var t_r := grid_pos + Vector2(Global.grid_size.x, -Global.grid_size.y)
var m_l := grid_pos + Vector2(-Global.grid_size.x, 0)
# t_l is for "top left" and so on
var t_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_c := grid_pos + Vector2(0, -Global.grids[0].grid_size.y)
var t_r := grid_pos + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var m_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, 0)
var m_c := grid_pos
var m_r := grid_pos + Vector2(Global.grid_size.x, 0)
var b_l := grid_pos + Vector2(-Global.grid_size.x, Global.grid_size.y)
var b_c := grid_pos + Vector2(0, Global.grid_size.y)
var b_r := grid_pos + Vector2(Global.grid_size)
var m_r := grid_pos + Vector2(Global.grids[0].grid_size.x, 0)
var b_l := grid_pos + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
var b_c := grid_pos + Vector2(0, Global.grids[0].grid_size.y)
var b_r := grid_pos + Vector2(Global.grids[0].grid_size)
var vec_arr: PackedVector2Array = [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
for vec in vec_arr:
if vec.distance_to(pos) < grid_pos.distance_to(pos):
@ -152,19 +153,22 @@ func snap_position(pos: Vector2) -> Vector2:
pos = grid_point.floor()
if Global.snap_to_rectangular_grid_center:
var grid_center := pos.snapped(Global.grid_size) + Vector2(Global.grid_size / 2)
grid_center += Vector2(Global.grid_offset)
var grid_center := (
pos.snapped(Global.grids[0].grid_size) + Vector2(Global.grids[0].grid_size / 2)
)
grid_center += Vector2(Global.grids[0].grid_offset)
# keeping grid_center as is would have been fine but this adds extra accuracy as to
# which snap point (from the list below) is closest to mouse and occupy THAT point
var t_l := grid_center + Vector2(-Global.grid_size.x, -Global.grid_size.y)
var t_c := grid_center + Vector2(0, -Global.grid_size.y) # t_c is for "top centre" and so on
var t_r := grid_center + Vector2(Global.grid_size.x, -Global.grid_size.y)
var m_l := grid_center + Vector2(-Global.grid_size.x, 0)
# t_l is for "top left" and so on
var t_l := grid_center + Vector2(-Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var t_c := grid_center + Vector2(0, -Global.grids[0].grid_size.y)
var t_r := grid_center + Vector2(Global.grids[0].grid_size.x, -Global.grids[0].grid_size.y)
var m_l := grid_center + Vector2(-Global.grids[0].grid_size.x, 0)
var m_c := grid_center
var m_r := grid_center + Vector2(Global.grid_size.x, 0)
var b_l := grid_center + Vector2(-Global.grid_size.x, Global.grid_size.y)
var b_c := grid_center + Vector2(0, Global.grid_size.y)
var b_r := grid_center + Vector2(Global.grid_size)
var m_r := grid_center + Vector2(Global.grids[0].grid_size.x, 0)
var b_l := grid_center + Vector2(-Global.grids[0].grid_size.x, Global.grids[0].grid_size.y)
var b_c := grid_center + Vector2(0, Global.grids[0].grid_size.y)
var b_r := grid_center + Vector2(Global.grids[0].grid_size)
var vec_arr := [t_l, t_c, t_r, m_l, m_c, m_r, b_l, b_c, b_r]
for vec in vec_arr:
if vec.distance_to(pos) < grid_center.distance_to(pos):

View file

@ -16,17 +16,17 @@ func _input(event: InputEvent) -> void:
return
if event.is_action_pressed("transform_snap_grid"):
_snap_to_grid = true
_offset = _offset.snapped(Global.grid_size)
_offset = _offset.snapped(Global.grids[0].grid_size)
if Global.current_project.has_selection and selection_node.is_moving_content:
var prev_pos: Vector2i = selection_node.big_bounding_rectangle.position
selection_node.big_bounding_rectangle.position = Vector2i(
prev_pos.snapped(Global.grid_size)
prev_pos.snapped(Global.grids[0].grid_size)
)
# The first time transform_snap_grid is enabled then _snap_position() is not called
# and the selection had wrong offset, so do selection offsetting here
var grid_offset := Vector2i(
fmod(Global.grid_offset.x, Global.grid_size.x),
fmod(Global.grid_offset.y, Global.grid_size.y)
fmod(Global.grids[0].grid_offset.x, Global.grids[0].grid_size.x),
fmod(Global.grids[0].grid_offset.y, Global.grids[0].grid_size.y)
)
selection_node.big_bounding_rectangle.position += grid_offset
selection_node.marching_ants_outline.offset += Vector2(
@ -107,16 +107,18 @@ func _snap_position(pos: Vector2) -> Vector2:
else:
pos.x = _start_pos.x
if _snap_to_grid: # Snap to grid
pos = pos.snapped(Global.grid_size)
pos = pos.snapped(Global.grids[0].grid_size)
# The part below only corrects the offset for situations when there is no selection
# Offsets when there is selection is controlled in _input() function
if !Global.current_project.has_selection:
var move_offset := Vector2.ZERO
move_offset.x = (
_start_pos.x - (_start_pos.x / Global.grid_size.x) * Global.grid_size.x
_start_pos.x
- (_start_pos.x / Global.grids[0].grid_size.x) * Global.grids[0].grid_size.x
)
move_offset.y = (
_start_pos.y - (_start_pos.y / Global.grid_size.y) * Global.grid_size.y
_start_pos.y
- (_start_pos.y / Global.grids[0].grid_size.y) * Global.grids[0].grid_size.y
)
pos += move_offset

View file

@ -1,5 +1,8 @@
extends Node2D
var unique_rect_lines := PackedVector2Array()
var unique_iso_lines := PackedVector2Array()
func _ready() -> void:
Global.project_switched.connect(queue_redraw)
@ -10,54 +13,60 @@ func _draw() -> void:
return
var target_rect: Rect2i
if Global.grid_draw_over_tile_mode:
target_rect = Global.current_project.tiles.get_bounding_rect()
else:
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
if not target_rect.has_area():
return
unique_rect_lines.clear()
unique_iso_lines.clear()
for grid_idx in range(Global.grids.size() - 1, -1, -1):
if Global.grids[grid_idx].grid_draw_over_tile_mode:
target_rect = Global.current_project.tiles.get_bounding_rect()
else:
target_rect = Rect2i(Vector2i.ZERO, Global.current_project.size)
if not target_rect.has_area():
return
var grid_type := Global.grid_type
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
_draw_cartesian_grid(target_rect)
var grid_type := Global.grids[grid_idx].grid_type
if grid_type == Global.GridTypes.CARTESIAN || grid_type == Global.GridTypes.ALL:
_draw_cartesian_grid(grid_idx, target_rect)
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
_draw_isometric_grid(target_rect)
if grid_type == Global.GridTypes.ISOMETRIC || grid_type == Global.GridTypes.ALL:
_draw_isometric_grid(grid_idx, target_rect)
func _draw_cartesian_grid(target_rect: Rect2i) -> void:
func _draw_cartesian_grid(grid_index: int, target_rect: Rect2i) -> void:
var grid = Global.grids[grid_index]
var grid_multiline_points := PackedVector2Array()
var x: float = (
target_rect.position.x
+ fposmod(Global.grid_offset.x - target_rect.position.x, Global.grid_size.x)
+ fposmod(grid.grid_offset.x - target_rect.position.x, grid.grid_size.x)
)
while x <= target_rect.end.x:
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
x += Global.grid_size.x
if not Vector2(x, target_rect.position.y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(x, target_rect.position.y))
grid_multiline_points.push_back(Vector2(x, target_rect.end.y))
x += grid.grid_size.x
var y: float = (
target_rect.position.y
+ fposmod(Global.grid_offset.y - target_rect.position.y, Global.grid_size.y)
+ fposmod(grid.grid_offset.y - target_rect.position.y, grid.grid_size.y)
)
while y <= target_rect.end.y:
grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
y += Global.grid_size.y
if not Vector2(target_rect.position.x, y) in unique_rect_lines:
grid_multiline_points.push_back(Vector2(target_rect.position.x, y))
grid_multiline_points.push_back(Vector2(target_rect.end.x, y))
y += grid.grid_size.y
unique_rect_lines.append_array(grid_multiline_points)
if not grid_multiline_points.is_empty():
draw_multiline(grid_multiline_points, Global.grid_color)
draw_multiline(grid_multiline_points, grid.grid_color)
func _draw_isometric_grid(target_rect: Rect2i) -> void:
func _draw_isometric_grid(grid_index: int, target_rect: Rect2i) -> void:
var grid = Global.grids[grid_index]
var grid_multiline_points := PackedVector2Array()
var cell_size: Vector2 = Global.isometric_grid_size
var cell_size: Vector2 = grid.isometric_grid_size
var max_cell_count: Vector2 = Vector2(target_rect.size) / cell_size
var origin_offset: Vector2 = Vector2(Global.grid_offset - target_rect.position).posmodv(
cell_size
)
var origin_offset: Vector2 = Vector2(grid.grid_offset - target_rect.position).posmodv(cell_size)
# lines ↗↗↗ (from bottom-left to top-right)
var per_cell_offset: Vector2 = cell_size * Vector2(1, -1)
@ -70,8 +79,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
var cells_to_rect_bounds: float = minf(max_cell_count.x, y / cell_size.y)
var end := start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y
# lines ↗↗↗ starting from the rect's bottom side (left to right):
@ -80,8 +90,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, target_rect.size.y)
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x
# lines ↘↘↘ (from top-left to bottom-right)
@ -93,8 +104,9 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(0, y)
var cells_to_rect_bounds: float = minf(max_cell_count.x, max_cell_count.y - y / cell_size.y)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
y += cell_size.y
# lines ↘↘↘ starting from the rect's top side (left to right):
@ -103,9 +115,11 @@ func _draw_isometric_grid(target_rect: Rect2i) -> void:
var start: Vector2 = Vector2(target_rect.position) + Vector2(x, 0)
var cells_to_rect_bounds: float = minf(max_cell_count.y, max_cell_count.x - x / cell_size.x)
var end: Vector2 = start + cells_to_rect_bounds * per_cell_offset
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
if not start in unique_iso_lines:
grid_multiline_points.push_back(start)
grid_multiline_points.push_back(end)
x += cell_size.x
grid_multiline_points.append_array(grid_multiline_points)
if not grid_multiline_points.is_empty():
draw_multiline(grid_multiline_points, Global.grid_color)
draw_multiline(grid_multiline_points, grid.grid_color)

View file

@ -106,6 +106,11 @@ func _input(event: InputEvent) -> void:
image_current_pixel = canvas.current_pixel
if Global.mirror_view:
image_current_pixel.x = Global.current_project.size.x - image_current_pixel.x
if is_moving_content:
if Input.is_action_just_pressed(&"transformation_confirm"):
transform_content_confirm()
elif Input.is_action_just_pressed(&"transformation_cancel"):
transform_content_cancel()
if not project.layers[project.current_layer].can_layer_get_drawn():
return
if event is InputEventKey:
@ -209,7 +214,7 @@ func _move_with_arrow_keys(event: InputEvent) -> void:
if _is_action_direction(event) and arrow_key_move:
var step := Vector2.ONE
if Input.is_key_pressed(KEY_CTRL):
step = Global.grid_size
step = Global.grids[0].grid_size
var input := Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
var move := input.rotated(snappedf(Global.camera.rotation, PI / 2))
# These checks are needed to fix a bug where the selection got stuck

View file

@ -3,7 +3,7 @@ extends Node2D
var tiles: Tiles
var draw_center := false
@onready var canvas := get_parent() as Canvas
@onready var canvas := Global.canvas
func _draw() -> void:

View file

@ -31,7 +31,7 @@ func commit_action(cel: Image, project := Global.current_project) -> void:
var params := {
"color": color,
"width": anim_thickness,
"pattern": pattern,
"brush": pattern,
"inside": inside_image,
"selection": selection_tex
}

View file

@ -51,16 +51,15 @@ size_flags_horizontal = 3
[node name="PatternLabel" type="Label" parent="VBoxContainer/OutlineOptions" index="4"]
layout_mode = 2
size_flags_horizontal = 3
text = "Pattern:"
text = "Brush:"
[node name="PatternOptionButton" type="OptionButton" parent="VBoxContainer/OutlineOptions" index="5"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
item_count = 3
selected = 0
item_count = 3
popup/item_0/text = "Diamond"
popup/item_0/id = 0
popup/item_1/text = "Circle"
popup/item_1/id = 1
popup/item_2/text = "Square"

View file

@ -0,0 +1,43 @@
extends ConfirmationDialog
enum Types { EXPAND, SHRINK, BORDER }
@export var type := Types.EXPAND:
set(value):
type = value
if type == Types.EXPAND:
title = "Expand Selection"
elif type == Types.SHRINK:
title = "Shrink Selection"
else:
title = "Border Selection"
@onready var width_slider: ValueSlider = $GridContainer/WidthSlider
@onready var brush_option_button: OptionButton = $GridContainer/BrushOptionButton
@onready var selection_node := Global.canvas.selection
func _on_visibility_changed() -> void:
if not visible:
Global.dialog_open(false)
func _on_confirmed() -> void:
var project := Global.current_project
if !project.has_selection:
return
selection_node.transform_content_confirm()
var undo_data_tmp := selection_node.get_undo_data(false)
var width: int = width_slider.value
var brush := brush_option_button.selected
project.selection_map.crop(project.size.x, project.size.y)
if type == Types.EXPAND:
project.selection_map.expand(width, brush)
elif type == Types.SHRINK:
project.selection_map.shrink(width, brush)
else:
project.selection_map.border(width, brush)
selection_node.big_bounding_rectangle = project.selection_map.get_used_rect()
project.selection_offset = Vector2.ZERO
selection_node.commit_undo("Modify Selection", undo_data_tmp)
selection_node.queue_redraw()

View file

@ -0,0 +1,60 @@
[gd_scene load_steps=3 format=3 uid="uid://wcbpnsm7gptu"]
[ext_resource type="Script" path="res://src/UI/Nodes/ValueSlider.gd" id="1_3jelw"]
[ext_resource type="Script" path="res://src/UI/Dialogs/ModifySelection.gd" id="1_w6rs7"]
[node name="ModifySelection" type="ConfirmationDialog"]
title = "Expand selection"
position = Vector2i(0, 36)
size = Vector2i(260, 130)
script = ExtResource("1_w6rs7")
[node name="GridContainer" type="GridContainer" parent="."]
offset_left = 8.0
offset_top = 8.0
offset_right = 252.0
offset_bottom = 81.0
columns = 2
[node name="WidthLabel" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Width:"
[node name="WidthSlider" type="TextureProgressBar" parent="GridContainer"]
custom_minimum_size = Vector2(0, 24)
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 2
mouse_default_cursor_shape = 2
theme_type_variation = &"ValueSlider"
min_value = 1.0
max_value = 25.0
value = 1.0
allow_greater = true
nine_patch_stretch = true
stretch_margin_left = 3
stretch_margin_top = 3
stretch_margin_right = 3
stretch_margin_bottom = 3
script = ExtResource("1_3jelw")
[node name="BrushLabel" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "Brush:"
[node name="BrushOptionButton" type="OptionButton" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
selected = 0
item_count = 3
popup/item_0/text = "Diamond"
popup/item_1/text = "Circle"
popup/item_1/id = 1
popup/item_2/text = "Square"
popup/item_2/id = 2
[connection signal="confirmed" from="." to="." method="_on_confirmed"]
[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"]

View file

@ -1,11 +1,12 @@
extends ConfirmationDialog
@onready var x_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisX
@onready var x_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisY
@onready var y_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisX
@onready var y_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisY
@onready var x_basis_label: Label = $VBoxContainer/OptionsContainer/XBasisLabel
@onready var x_basis: ValueSliderV2 = $VBoxContainer/OptionsContainer/XBasis
@onready var y_basis_label: Label = $VBoxContainer/OptionsContainer/YBasisLabel
@onready var y_basis: ValueSliderV2 = $VBoxContainer/OptionsContainer/YBasis
@onready var preview_rect: Control = $VBoxContainer/AspectRatioContainer/Preview
@onready var tile_mode: Node2D = $VBoxContainer/AspectRatioContainer/Preview/TileMode
@onready var masking: CheckButton = $VBoxContainer/OptionsContainer/Masking
func _ready() -> void:
@ -37,35 +38,25 @@ func _on_TileModeOffsetsDialog_about_to_show() -> void:
tile_mode.tiles.mode = Global.current_project.tiles.mode
tile_mode.tiles.x_basis = Global.current_project.tiles.x_basis
tile_mode.tiles.y_basis = Global.current_project.tiles.y_basis
x_basis_x_spinbox.value = tile_mode.tiles.x_basis.x
x_basis_y_spinbox.value = tile_mode.tiles.x_basis.y
y_basis_x_spinbox.value = tile_mode.tiles.y_basis.x
y_basis_y_spinbox.value = tile_mode.tiles.y_basis.y
x_basis.value = tile_mode.tiles.x_basis
y_basis.value = tile_mode.tiles.y_basis
_show_options()
if Global.current_project.tiles.mode == Tiles.MODE.X_AXIS:
y_basis_x_spinbox.visible = false
y_basis_y_spinbox.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisYLabel.visible = false
y_basis.visible = false
y_basis_label.visible = false
elif Global.current_project.tiles.mode == Tiles.MODE.Y_AXIS:
x_basis_x_spinbox.visible = false
x_basis_y_spinbox.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisYLabel.visible = false
x_basis.visible = false
x_basis_label.visible = false
update_preview()
func _show_options() -> void:
x_basis_x_spinbox.visible = true
x_basis_y_spinbox.visible = true
y_basis_x_spinbox.visible = true
y_basis_y_spinbox.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisXLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisYLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisXLabel.visible = true
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisYLabel.visible = true
x_basis.visible = true
y_basis.visible = true
x_basis_label.visible = true
y_basis_label.visible = true
func _on_TileModeOffsetsDialog_confirmed() -> void:
@ -75,23 +66,13 @@ func _on_TileModeOffsetsDialog_confirmed() -> void:
Global.transparent_checker.update_rect()
func _on_XBasisX_value_changed(value: int) -> void:
tile_mode.tiles.x_basis.x = value
func _on_x_basis_value_changed(value: Vector2) -> void:
tile_mode.tiles.x_basis = value
update_preview()
func _on_XBasisY_value_changed(value: int) -> void:
tile_mode.tiles.x_basis.y = value
update_preview()
func _on_YBasisX_value_changed(value: int) -> void:
tile_mode.tiles.y_basis.x = value
update_preview()
func _on_YBasisY_value_changed(value: int) -> void:
tile_mode.tiles.y_basis.y = value
func _on_y_basis_value_changed(value: Vector2) -> void:
tile_mode.tiles.y_basis = value
update_preview()
@ -122,10 +103,17 @@ func _on_TileModeOffsetsDialog_size_changed() -> void:
func _on_Reset_pressed() -> void:
tile_mode.tiles.x_basis = Vector2i(Global.current_project.size.x, 0)
tile_mode.tiles.y_basis = Vector2i(0, Global.current_project.size.y)
x_basis_x_spinbox.value = Global.current_project.size.x
x_basis_y_spinbox.value = 0
y_basis_x_spinbox.value = 0
y_basis_y_spinbox.value = Global.current_project.size.y
x_basis.value = tile_mode.tiles.x_basis
y_basis.value = tile_mode.tiles.y_basis
update_preview()
func _on_isometric_pressed() -> void:
tile_mode.tiles.x_basis = Global.current_project.size / 2
tile_mode.tiles.x_basis.y *= -1
tile_mode.tiles.y_basis = Global.current_project.size / 2
x_basis.value = tile_mode.tiles.x_basis
y_basis.value = tile_mode.tiles.y_basis
update_preview()
@ -138,10 +126,7 @@ func change_mask() -> void:
var tiles_size := tiles.tile_size
var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8)
DrawingAlgos.blend_layers(image, current_frame)
if (
image.get_used_rect().size == Vector2i.ZERO
or not $VBoxContainer/HBoxContainer/Masking.button_pressed
):
if image.get_used_rect().size == Vector2i.ZERO or not masking.button_pressed:
tiles.reset_mask()
else:
load_mask(image)

View file

@ -1,7 +1,8 @@
[gd_scene load_steps=5 format=3 uid="uid://c0nuukjakmai2"]
[gd_scene load_steps=6 format=3 uid="uid://c0nuukjakmai2"]
[ext_resource type="PackedScene" uid="uid://3pmb60gpst7b" path="res://src/UI/Nodes/TransparentChecker.tscn" id="1"]
[ext_resource type="Script" path="res://src/UI/Canvas/TileMode.gd" id="2"]
[ext_resource type="PackedScene" path="res://src/UI/Nodes/ValueSliderV2.tscn" id="2_ul2eq"]
[ext_resource type="Script" path="res://src/UI/Dialogs/TileModeOffsetsDialog.gd" id="3"]
[sub_resource type="CanvasItemMaterial" id="1"]
@ -10,83 +11,72 @@ blend_mode = 4
[node name="TileModeOffsetsDialog" type="ConfirmationDialog"]
canvas_item_default_texture_filter = 0
title = "Tile Mode Offsets"
position = Vector2i(0, 36)
size = Vector2i(298, 536)
script = ExtResource("3")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 8.0
offset_top = 8.0
offset_right = 293.0
offset_bottom = 386.0
offset_right = 290.0
offset_bottom = 487.0
[node name="TileModeOffsets" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "Tile Mode Offsets"
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer"]
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/h_separation = 2
theme_override_constants/v_separation = 4
columns = 2
[node name="XBasisXLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
[node name="XBasisLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
text = "X-basis x:"
size_flags_horizontal = 3
text = "X-basis:"
[node name="XBasisX" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
[node name="XBasis" parent="VBoxContainer/OptionsContainer" instance=ExtResource("2_ul2eq")]
layout_mode = 2
mouse_default_cursor_shape = 2
min_value = -16384.0
max_value = 16384.0
suffix = "px"
size_flags_horizontal = 3
allow_greater = true
allow_lesser = true
suffix_x = "px"
suffix_y = "px"
[node name="XBasisYLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
[node name="YBasisLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
text = "X-basis y:"
size_flags_horizontal = 3
text = "Y-basis:"
[node name="XBasisY" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
[node name="YBasis" parent="VBoxContainer/OptionsContainer" instance=ExtResource("2_ul2eq")]
layout_mode = 2
mouse_default_cursor_shape = 2
min_value = -16384.0
max_value = 16384.0
suffix = "px"
size_flags_horizontal = 3
allow_greater = true
allow_lesser = true
suffix_x = "px"
suffix_y = "px"
[node name="YBasisXLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
[node name="MaskingLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
text = "Y-basis x:"
text = "Masking:"
[node name="YBasisX" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
layout_mode = 2
mouse_default_cursor_shape = 2
min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="YBasisYLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
layout_mode = 2
text = "Y-basis y:"
[node name="YBasisY" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
layout_mode = 2
mouse_default_cursor_shape = 2
min_value = -16384.0
max_value = 16384.0
suffix = "px"
[node name="Reset" type="Button" parent="VBoxContainer/HBoxContainer/OptionsContainer"]
layout_mode = 2
mouse_default_cursor_shape = 2
text = "Reset"
[node name="VSeparator" type="VSeparator" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
[node name="Masking" type="CheckButton" parent="VBoxContainer/HBoxContainer"]
[node name="Masking" type="CheckButton" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 0
text = "Masking"
[node name="Reset" type="Button" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Default"
[node name="Isometric" type="Button" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2
size_flags_horizontal = 3
mouse_default_cursor_shape = 2
text = "Isometric"
[node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
layout_mode = 2
@ -111,9 +101,8 @@ anchor_bottom = 1.0
[connection signal="confirmed" from="." to="." method="_on_TileModeOffsetsDialog_confirmed"]
[connection signal="size_changed" from="." to="." method="_on_TileModeOffsetsDialog_size_changed"]
[connection signal="visibility_changed" from="." to="." method="_on_TileModeOffsetsDialog_visibility_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/XBasisX" to="." method="_on_XBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/XBasisY" to="." method="_on_XBasisY_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisX" to="." method="_on_YBasisX_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisY" to="." method="_on_YBasisY_value_changed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"]
[connection signal="toggled" from="VBoxContainer/HBoxContainer/Masking" to="." method="_on_Masking_toggled"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/XBasis" to="." method="_on_x_basis_value_changed"]
[connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasis" to="." method="_on_y_basis_value_changed"]
[connection signal="toggled" from="VBoxContainer/OptionsContainer/Masking" to="." method="_on_Masking_toggled"]
[connection signal="pressed" from="VBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"]
[connection signal="pressed" from="VBoxContainer/OptionsContainer/Isometric" to="." method="_on_isometric_pressed"]

View file

@ -80,15 +80,21 @@ func _notification(what: int) -> void:
_reset_display(false)
func _input(event: InputEvent) -> void:
func _unhandled_input(event: InputEvent) -> void:
if not editable or not is_visible_in_tree():
return
if event.is_action_pressed(global_increment_action, true):
if (
not global_increment_action.is_empty()
and event.is_action_pressed(global_increment_action, true)
):
if snap_by_default:
value += step if event.ctrl_pressed else snap_step
else:
value += snap_step if event.ctrl_pressed else step
elif event.is_action_pressed(global_decrement_action, true):
elif (
not global_decrement_action.is_empty()
and event.is_action_pressed(global_decrement_action, true)
):
if snap_by_default:
value -= step if event.ctrl_pressed else snap_step
else:
@ -108,11 +114,13 @@ func _gui_input(event: InputEvent) -> void:
value += step if event.ctrl_pressed else snap_step
else:
value += snap_step if event.ctrl_pressed else step
get_viewport().set_input_as_handled()
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
if snap_by_default:
value -= step if event.ctrl_pressed else snap_step
else:
value -= snap_step if event.ctrl_pressed else step
get_viewport().set_input_as_handled()
elif state == HELD:
if event.is_action_released("left_mouse"):
state = TYPING

View file

@ -85,6 +85,9 @@ class Recorder:
func _ready() -> void:
if OS.get_name() == "Web":
ExtensionsApi.panel.remove_node_from_tab.call_deferred(self)
return
Global.project_switched.connect(_on_project_switched)
# Make a recordings folder if there isn't one
chosen_dir = Global.home_data_directory.path_join("Recordings")

View file

@ -141,6 +141,7 @@ func _input(event: InputEvent) -> void:
if timeline_rect.has_point(mouse_pos):
if Input.is_key_pressed(KEY_CTRL):
cel_size += (2 * int(event.is_action("zoom_in")) - 2 * int(event.is_action("zoom_out")))
get_viewport().set_input_as_handled()
func reset_settings() -> void:

View file

@ -17,6 +17,7 @@ var zen_mode := false
var new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn")
var project_properties_dialog := Dialog.new("res://src/UI/Dialogs/ProjectProperties.tscn")
var preferences_dialog := Dialog.new("res://src/Preferences/PreferencesDialog.tscn")
var modify_selection := Dialog.new("res://src/UI/Dialogs/ModifySelection.tscn")
var offset_image_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/OffsetImage.tscn")
var scale_image_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ScaleImage.tscn")
var resize_canvas_dialog := Dialog.new("res://src/UI/Dialogs/ImageEffects/ResizeCanvas.tscn")
@ -54,6 +55,7 @@ var about_dialog := Dialog.new("res://src/UI/Dialogs/AboutDialog.tscn")
@onready var greyscale_vision: ColorRect = main_ui.find_child("GreyscaleVision")
@onready var tile_mode_submenu := PopupMenu.new()
@onready var selection_modify_submenu := PopupMenu.new()
@onready var snap_to_submenu := PopupMenu.new()
@onready var panels_submenu := PopupMenu.new()
@onready var layouts_submenu := PopupMenu.new()
@ -440,17 +442,30 @@ func _setup_select_menu() -> void:
"All": "select_all",
"Clear": "clear_selection",
"Invert": "invert_selection",
"Tile Mode": ""
"Tile Mode": "",
"Modify": ""
}
for i in select_menu_items.size():
var item: String = select_menu_items.keys()[i]
if item == "Tile Mode":
select_menu.add_check_item(item, i)
elif item == "Modify":
_setup_selection_modify_submenu(item)
else:
_set_menu_shortcut(select_menu_items[item], select_menu, i, item)
select_menu.id_pressed.connect(select_menu_id_pressed)
func _setup_selection_modify_submenu(item: String) -> void:
selection_modify_submenu.set_name("selection_modify_submenu")
selection_modify_submenu.add_item("Expand")
selection_modify_submenu.add_item("Shrink")
selection_modify_submenu.add_item("Border")
selection_modify_submenu.id_pressed.connect(_selection_modify_submenu_id_pressed)
select_menu.add_child(selection_modify_submenu)
select_menu.add_submenu_item(item, selection_modify_submenu.get_name())
func _setup_help_menu() -> void:
# Order as in Global.HelpMenu enum
var help_menu_items := {
@ -667,6 +682,11 @@ func _tile_mode_submenu_id_pressed(id: Tiles.MODE) -> void:
get_tree().current_scene.tile_mode_offsets_dialog.change_mask()
func _selection_modify_submenu_id_pressed(id: int) -> void:
modify_selection.popup()
modify_selection.node.type = id
func _snap_to_submenu_id_pressed(id: int) -> void:
if id == 0:
Global.snap_to_rectangular_grid_boundary = !Global.snap_to_rectangular_grid_boundary