1
0
Fork 0
mirror of https://github.com/Orama-Interactive/Pixelorama.git synced 2025-02-13 01:03:07 +00:00

Compare commits

..

17 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
23 changed files with 422 additions and 160 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. 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> <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 ## [v1.0.4] - 2024-10-25
This update has been brought to you by the contributions of: 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)) 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" msgid "Invert"
msgstr "" 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" msgid "Grayscale View"
msgstr "" msgstr ""
@ -230,19 +267,16 @@ msgstr ""
msgid "Tile Mode Offsets" msgid "Tile Mode Offsets"
msgstr "" 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 "" 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 "" msgstr ""
msgid "Y-basis x:" #. 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.
msgstr "" msgid "Masking:"
msgid "Y-basis y:"
msgstr ""
msgid "Tile Mask"
msgstr "" msgstr ""
msgid "Reset" msgid "Reset"
@ -1865,6 +1899,10 @@ msgstr ""
msgid "Fills the drawn shape with color, instead of drawing a hollow shape" msgid "Fills the drawn shape with color, instead of drawing a hollow shape"
msgstr "" 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" msgid "Brush color from"
msgstr "" msgstr ""
@ -2808,6 +2846,10 @@ msgstr ""
msgid "Sort by value" msgid "Sort by value"
msgstr "" 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. #. 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" msgid "Sort by red"
msgstr "" msgstr ""

View file

@ -534,6 +534,10 @@ class SelectionAPI:
Global.canvas.selection.move_borders_start() Global.canvas.selection.move_borders_start()
else: else:
Global.canvas.selection.transform_content_start() 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.big_bounding_rectangle.size = new_size
Global.canvas.selection.resize_selection() Global.canvas.selection.resize_selection()
Global.canvas.selection.move_borders_end() Global.canvas.selection.move_borders_end()

View file

@ -71,7 +71,7 @@ enum EffectsMenu {
SHADER SHADER
} }
## Enumeration of items present in the Select Menu. ## 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. ## Enumeration of items present in the Help Menu.
enum HelpMenu { enum HelpMenu {
VIEW_SPLASH_SCREEN, VIEW_SPLASH_SCREEN,

View file

@ -1,7 +1,8 @@
class_name SelectionMap class_name SelectionMap
extends Image 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: func is_pixel_selected(pixel: Vector2i, calculate_offset := true) -> bool:
@ -87,8 +88,7 @@ func clear() -> void:
func invert() -> void: func invert() -> void:
var params := {"red": true, "green": true, "blue": true, "alpha": true} var params := {"red": true, "green": true, "blue": true, "alpha": true}
var gen := ShaderImageEffect.new() var gen := ShaderImageEffect.new()
gen.generate_image(self, invert_shader, params, get_size()) gen.generate_image(self, INVERT_SHADER, params, get_size())
self.convert(Image.FORMAT_LA8)
## Returns a copy of itself that is cropped to [param 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: if new_bitmap_size != size:
crop(new_bitmap_size.x, new_bitmap_size.y) crop(new_bitmap_size.x, new_bitmap_size.y)
blit_rect(smaller_image, Rect2i(Vector2i.ZERO, new_bitmap_size), dst) 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(ci_rid)
RenderingServer.free_rid(mat_rid) RenderingServer.free_rid(mat_rid)
RenderingServer.free_rid(texture) RenderingServer.free_rid(texture)
viewport_texture.convert(Image.FORMAT_RGBA8) viewport_texture.convert(img.get_format())
img.copy_from(viewport_texture) img.copy_from(viewport_texture)
if resized_width: if resized_width:
img.crop(img.get_width() - 1, img.get_height()) 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 != "": if u_value != "":
slider.value = int(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): if params.has(u_name):
slider.value = params[u_name] slider.value = params[u_name]
else: else:
params[u_name] = slider.value 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.value_changed.connect(value_changed.bind(u_name))
slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND slider.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
hbox.add_child(slider) hbox.add_child(slider)

View file

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

View file

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

View file

@ -1,8 +1,11 @@
extends BaseTool extends BaseTool
const IMAGE_BRUSHES := [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM]
var _brush := Brushes.get_default_brush() var _brush := Brushes.get_default_brush()
var _brush_size := 1 var _brush_size := 1
var _brush_size_dynamics := 1 var _brush_size_dynamics := 1
var _brush_density := 100
var _brush_flip_x := false var _brush_flip_x := false
var _brush_flip_y := false var _brush_flip_y := false
var _brush_rotate_90 := false var _brush_rotate_90 := false
@ -89,6 +92,12 @@ func _reset_dynamics() -> void:
save_config() 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: func _on_InterpolateFactor_value_changed(value: float) -> void:
_brush_interpolate = int(value) _brush_interpolate = int(value)
update_config() update_config()
@ -111,6 +120,7 @@ func get_config() -> Dictionary:
"brush_type": _brush.type, "brush_type": _brush.type,
"brush_index": _brush.index, "brush_index": _brush.index,
"brush_size": _brush_size, "brush_size": _brush_size,
"brush_density": _brush_density,
"brush_interpolate": _brush_interpolate, "brush_interpolate": _brush_interpolate,
"brush_flip_x": _brush_flip_x, "brush_flip_x": _brush_flip_x,
"brush_flip_y": _brush_flip_y, "brush_flip_y": _brush_flip_y,
@ -128,6 +138,7 @@ func set_config(config: Dictionary) -> void:
_brush_size_dynamics = _brush_size _brush_size_dynamics = _brush_size
if Tools.dynamics_size != Tools.Dynamics.NONE: if Tools.dynamics_size != Tools.Dynamics.NONE:
_brush_size_dynamics = Tools.brush_size_min _brush_size_dynamics = Tools.brush_size_min
_brush_density = config.get("brush_density", _brush_density)
_brush_interpolate = config.get("brush_interpolate", _brush_interpolate) _brush_interpolate = config.get("brush_interpolate", _brush_interpolate)
_brush_flip_x = config.get("brush_flip_x", _brush_flip_x) _brush_flip_x = config.get("brush_flip_x", _brush_flip_x)
_brush_flip_y = config.get("brush_flip_y", _brush_flip_y) _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) _brush_texture = ImageTexture.create_from_image(_brush_image)
update_mirror_brush() update_mirror_brush()
_stroke_dimensions = _brush_image.get_size() _stroke_dimensions = _brush_image.get_size()
_circle_tool_shortcut = []
_indicator = _create_brush_indicator() _indicator = _create_brush_indicator()
_polylines = _create_polylines(_indicator) _polylines = _create_polylines(_indicator)
$Brush/Type/Texture.texture = _brush_texture $Brush/Type/Texture.texture = _brush_texture
$ColorInterpolation.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] $DensityValueSlider.visible = _brush.type not in IMAGE_BRUSHES
$RotationOptions.visible = _brush.type in [Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM] $ColorInterpolation.visible = _brush.type in IMAGE_BRUSHES
$RotationOptions.visible = _brush.type in IMAGE_BRUSHES
func update_random_image() -> void: func update_random_image() -> void:
@ -275,6 +288,7 @@ func draw_end(pos: Vector2i) -> void:
super.draw_end(pos) super.draw_end(pos)
_stroke_project = null _stroke_project = null
_stroke_images = [] _stroke_images = []
_circle_tool_shortcut = []
_brush_size_dynamics = _brush_size _brush_size_dynamics = _brush_size
if Tools.dynamics_size != Tools.Dynamics.NONE: if Tools.dynamics_size != Tools.Dynamics.NONE:
_brush_size_dynamics = Tools.brush_size_min _brush_size_dynamics = Tools.brush_size_min
@ -313,10 +327,6 @@ func _prepare_tool() -> void:
# This may prevent a few tests when setting pixels # This may prevent a few tests when setting pixels
_is_mask_size_zero = _mask.size() == 0 _is_mask_size_zero = _mask.size() == 0
match _brush.type: match _brush.type:
Brushes.CIRCLE:
_prepare_circle_tool(false)
Brushes.FILLED_CIRCLE:
_prepare_circle_tool(true)
Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM: Brushes.FILE, Brushes.RANDOM_FILE, Brushes.CUSTOM:
# save _brush_image for safe keeping # save _brush_image for safe keeping
_brush_image = _create_blended_brush_image(_orignal_brush_image) _brush_image = _create_blended_brush_image(_orignal_brush_image)
@ -326,19 +336,6 @@ func _prepare_tool() -> void:
_stroke_dimensions = _brush_image.get_size() _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 ## 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) ## drawn if it can (except for the generic brush, when it's actually drawing them)
func _draw_tool(pos: Vector2) -> PackedVector2Array: 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: func draw_indicator_at(pos: Vector2i, offset: Vector2i, color: Color) -> void:
var canvas: Node2D = Global.canvas.indicators 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 -= _brush_image.get_size() / 2
pos -= offset pos -= offset
canvas.draw_texture(_brush_texture, pos) 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: 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) pos = _stroke_project.tiles.get_canon_position(pos)
if Global.current_project.has_selection: if Global.current_project.has_selection:
pos = Global.current_project.selection_map.get_canon_position(pos) 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: 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 brush_size_v2 := Vector2i(brush_size, brush_size)
var diameter := brush_size_v2 * 2 + Vector2i.ONE var diameter_v2 := brush_size_v2 * 2 + Vector2i.ONE
return _fill_bitmap_with_points(_compute_draw_tool_circle(brush_size_v2, fill), diameter) 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: 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://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="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/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/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"] [sub_resource type="ButtonGroup" id="ButtonGroup_7u3x0"]
resource_name = "rotate" resource_name = "rotate"
@ -130,7 +131,25 @@ suffix = "px"
global_increment_action = "brush_size_increment" global_increment_action = "brush_size_increment"
global_decrement_action = "brush_size_decrement" 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 visible = false
layout_mode = 2 layout_mode = 2
tooltip_text = "0: Color from the brush itself, 100: the currently selected color" 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="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="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="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"] [connection signal="value_changed" from="ColorInterpolation" to="." method="_on_InterpolateFactor_value_changed"]

View file

@ -106,6 +106,11 @@ func _input(event: InputEvent) -> void:
image_current_pixel = canvas.current_pixel image_current_pixel = canvas.current_pixel
if Global.mirror_view: if Global.mirror_view:
image_current_pixel.x = Global.current_project.size.x - image_current_pixel.x 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(): if not project.layers[project.current_layer].can_layer_get_drawn():
return return
if event is InputEventKey: if event is InputEventKey:

View file

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

View file

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

View file

@ -51,16 +51,15 @@ size_flags_horizontal = 3
[node name="PatternLabel" type="Label" parent="VBoxContainer/OutlineOptions" index="4"] [node name="PatternLabel" type="Label" parent="VBoxContainer/OutlineOptions" index="4"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
text = "Pattern:" text = "Brush:"
[node name="PatternOptionButton" type="OptionButton" parent="VBoxContainer/OutlineOptions" index="5"] [node name="PatternOptionButton" type="OptionButton" parent="VBoxContainer/OutlineOptions" index="5"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
mouse_default_cursor_shape = 2 mouse_default_cursor_shape = 2
item_count = 3
selected = 0 selected = 0
item_count = 3
popup/item_0/text = "Diamond" popup/item_0/text = "Diamond"
popup/item_0/id = 0
popup/item_1/text = "Circle" popup/item_1/text = "Circle"
popup/item_1/id = 1 popup/item_1/id = 1
popup/item_2/text = "Square" 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 extends ConfirmationDialog
@onready var x_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisX @onready var x_basis_label: Label = $VBoxContainer/OptionsContainer/XBasisLabel
@onready var x_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/XBasisY @onready var x_basis: ValueSliderV2 = $VBoxContainer/OptionsContainer/XBasis
@onready var y_basis_x_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisX @onready var y_basis_label: Label = $VBoxContainer/OptionsContainer/YBasisLabel
@onready var y_basis_y_spinbox: SpinBox = $VBoxContainer/HBoxContainer/OptionsContainer/YBasisY @onready var y_basis: ValueSliderV2 = $VBoxContainer/OptionsContainer/YBasis
@onready var preview_rect: Control = $VBoxContainer/AspectRatioContainer/Preview @onready var preview_rect: Control = $VBoxContainer/AspectRatioContainer/Preview
@onready var tile_mode: Node2D = $VBoxContainer/AspectRatioContainer/Preview/TileMode @onready var tile_mode: Node2D = $VBoxContainer/AspectRatioContainer/Preview/TileMode
@onready var masking: CheckButton = $VBoxContainer/OptionsContainer/Masking
func _ready() -> void: 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.mode = Global.current_project.tiles.mode
tile_mode.tiles.x_basis = Global.current_project.tiles.x_basis tile_mode.tiles.x_basis = Global.current_project.tiles.x_basis
tile_mode.tiles.y_basis = Global.current_project.tiles.y_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.value = tile_mode.tiles.x_basis
x_basis_y_spinbox.value = tile_mode.tiles.x_basis.y y_basis.value = tile_mode.tiles.y_basis
y_basis_x_spinbox.value = tile_mode.tiles.y_basis.x
y_basis_y_spinbox.value = tile_mode.tiles.y_basis.y
_show_options() _show_options()
if Global.current_project.tiles.mode == Tiles.MODE.X_AXIS: if Global.current_project.tiles.mode == Tiles.MODE.X_AXIS:
y_basis_x_spinbox.visible = false y_basis.visible = false
y_basis_y_spinbox.visible = false y_basis_label.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/YBasisYLabel.visible = false
elif Global.current_project.tiles.mode == Tiles.MODE.Y_AXIS: elif Global.current_project.tiles.mode == Tiles.MODE.Y_AXIS:
x_basis_x_spinbox.visible = false x_basis.visible = false
x_basis_y_spinbox.visible = false x_basis_label.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisXLabel.visible = false
$VBoxContainer/HBoxContainer/OptionsContainer/XBasisYLabel.visible = false
update_preview() update_preview()
func _show_options() -> void: func _show_options() -> void:
x_basis_x_spinbox.visible = true x_basis.visible = true
x_basis_y_spinbox.visible = true y_basis.visible = true
y_basis_x_spinbox.visible = true x_basis_label.visible = true
y_basis_y_spinbox.visible = true y_basis_label.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
func _on_TileModeOffsetsDialog_confirmed() -> void: func _on_TileModeOffsetsDialog_confirmed() -> void:
@ -75,23 +66,13 @@ func _on_TileModeOffsetsDialog_confirmed() -> void:
Global.transparent_checker.update_rect() Global.transparent_checker.update_rect()
func _on_XBasisX_value_changed(value: int) -> void: func _on_x_basis_value_changed(value: Vector2) -> void:
tile_mode.tiles.x_basis.x = value tile_mode.tiles.x_basis = value
update_preview() update_preview()
func _on_XBasisY_value_changed(value: int) -> void: func _on_y_basis_value_changed(value: Vector2) -> void:
tile_mode.tiles.x_basis.y = value tile_mode.tiles.y_basis = 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
update_preview() update_preview()
@ -122,10 +103,17 @@ func _on_TileModeOffsetsDialog_size_changed() -> void:
func _on_Reset_pressed() -> void: func _on_Reset_pressed() -> void:
tile_mode.tiles.x_basis = Vector2i(Global.current_project.size.x, 0) tile_mode.tiles.x_basis = Vector2i(Global.current_project.size.x, 0)
tile_mode.tiles.y_basis = Vector2i(0, Global.current_project.size.y) tile_mode.tiles.y_basis = Vector2i(0, Global.current_project.size.y)
x_basis_x_spinbox.value = Global.current_project.size.x x_basis.value = tile_mode.tiles.x_basis
x_basis_y_spinbox.value = 0 y_basis.value = tile_mode.tiles.y_basis
y_basis_x_spinbox.value = 0 update_preview()
y_basis_y_spinbox.value = Global.current_project.size.y
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() update_preview()
@ -138,10 +126,7 @@ func change_mask() -> void:
var tiles_size := tiles.tile_size var tiles_size := tiles.tile_size
var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8) var image := Image.create(tiles_size.x, tiles_size.y, false, Image.FORMAT_RGBA8)
DrawingAlgos.blend_layers(image, current_frame) DrawingAlgos.blend_layers(image, current_frame)
if ( if image.get_used_rect().size == Vector2i.ZERO or not masking.button_pressed:
image.get_used_rect().size == Vector2i.ZERO
or not $VBoxContainer/HBoxContainer/Masking.button_pressed
):
tiles.reset_mask() tiles.reset_mask()
else: else:
load_mask(image) 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="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="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"] [ext_resource type="Script" path="res://src/UI/Dialogs/TileModeOffsetsDialog.gd" id="3"]
[sub_resource type="CanvasItemMaterial" id="1"] [sub_resource type="CanvasItemMaterial" id="1"]
@ -10,83 +11,72 @@ blend_mode = 4
[node name="TileModeOffsetsDialog" type="ConfirmationDialog"] [node name="TileModeOffsetsDialog" type="ConfirmationDialog"]
canvas_item_default_texture_filter = 0 canvas_item_default_texture_filter = 0
title = "Tile Mode Offsets" title = "Tile Mode Offsets"
position = Vector2i(0, 36)
size = Vector2i(298, 536)
script = ExtResource("3") script = ExtResource("3")
[node name="VBoxContainer" type="VBoxContainer" parent="."] [node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 8.0 offset_left = 8.0
offset_top = 8.0 offset_top = 8.0
offset_right = 293.0 offset_right = 290.0
offset_bottom = 386.0 offset_bottom = 487.0
[node name="TileModeOffsets" type="Label" parent="VBoxContainer"] [node name="TileModeOffsets" type="Label" parent="VBoxContainer"]
layout_mode = 2 layout_mode = 2
text = "Tile Mode Offsets" text = "Tile Mode Offsets"
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] [node name="OptionsContainer" type="GridContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="OptionsContainer" type="GridContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2 layout_mode = 2
theme_override_constants/h_separation = 2 theme_override_constants/h_separation = 2
theme_override_constants/v_separation = 4 theme_override_constants/v_separation = 4
columns = 2 columns = 2
[node name="XBasisXLabel" type="Label" parent="VBoxContainer/HBoxContainer/OptionsContainer"] [node name="XBasisLabel" type="Label" parent="VBoxContainer/OptionsContainer"]
layout_mode = 2 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 layout_mode = 2
mouse_default_cursor_shape = 2 size_flags_horizontal = 3
min_value = -16384.0 allow_greater = true
max_value = 16384.0 allow_lesser = true
suffix = "px" 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 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 layout_mode = 2
mouse_default_cursor_shape = 2 size_flags_horizontal = 3
min_value = -16384.0 allow_greater = true
max_value = 16384.0 allow_lesser = true
suffix = "px" 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 layout_mode = 2
text = "Y-basis x:" text = "Masking:"
[node name="YBasisX" type="SpinBox" parent="VBoxContainer/HBoxContainer/OptionsContainer"] [node name="Masking" type="CheckButton" parent="VBoxContainer/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"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 0 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"] [node name="AspectRatioContainer" type="AspectRatioContainer" parent="VBoxContainer"]
layout_mode = 2 layout_mode = 2
@ -111,9 +101,8 @@ anchor_bottom = 1.0
[connection signal="confirmed" from="." to="." method="_on_TileModeOffsetsDialog_confirmed"] [connection signal="confirmed" from="." to="." method="_on_TileModeOffsetsDialog_confirmed"]
[connection signal="size_changed" from="." to="." method="_on_TileModeOffsetsDialog_size_changed"] [connection signal="size_changed" from="." to="." method="_on_TileModeOffsetsDialog_size_changed"]
[connection signal="visibility_changed" from="." to="." method="_on_TileModeOffsetsDialog_visibility_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/OptionsContainer/XBasis" to="." method="_on_x_basis_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/XBasisY" to="." method="_on_XBasisY_value_changed"] [connection signal="value_changed" from="VBoxContainer/OptionsContainer/YBasis" to="." method="_on_y_basis_value_changed"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisX" to="." method="_on_YBasisX_value_changed"] [connection signal="toggled" from="VBoxContainer/OptionsContainer/Masking" to="." method="_on_Masking_toggled"]
[connection signal="value_changed" from="VBoxContainer/HBoxContainer/OptionsContainer/YBasisY" to="." method="_on_YBasisY_value_changed"] [connection signal="pressed" from="VBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/OptionsContainer/Reset" to="." method="_on_Reset_pressed"] [connection signal="pressed" from="VBoxContainer/OptionsContainer/Isometric" to="." method="_on_isometric_pressed"]
[connection signal="toggled" from="VBoxContainer/HBoxContainer/Masking" to="." method="_on_Masking_toggled"]

View file

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

View file

@ -85,6 +85,9 @@ class Recorder:
func _ready() -> void: 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) Global.project_switched.connect(_on_project_switched)
# Make a recordings folder if there isn't one # Make a recordings folder if there isn't one
chosen_dir = Global.home_data_directory.path_join("Recordings") 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 timeline_rect.has_point(mouse_pos):
if Input.is_key_pressed(KEY_CTRL): if Input.is_key_pressed(KEY_CTRL):
cel_size += (2 * int(event.is_action("zoom_in")) - 2 * int(event.is_action("zoom_out"))) 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: 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 new_image_dialog := Dialog.new("res://src/UI/Dialogs/CreateNewImage.tscn")
var project_properties_dialog := Dialog.new("res://src/UI/Dialogs/ProjectProperties.tscn") var project_properties_dialog := Dialog.new("res://src/UI/Dialogs/ProjectProperties.tscn")
var preferences_dialog := Dialog.new("res://src/Preferences/PreferencesDialog.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 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 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") 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 greyscale_vision: ColorRect = main_ui.find_child("GreyscaleVision")
@onready var tile_mode_submenu := PopupMenu.new() @onready var tile_mode_submenu := PopupMenu.new()
@onready var selection_modify_submenu := PopupMenu.new()
@onready var snap_to_submenu := PopupMenu.new() @onready var snap_to_submenu := PopupMenu.new()
@onready var panels_submenu := PopupMenu.new() @onready var panels_submenu := PopupMenu.new()
@onready var layouts_submenu := PopupMenu.new() @onready var layouts_submenu := PopupMenu.new()
@ -440,17 +442,30 @@ func _setup_select_menu() -> void:
"All": "select_all", "All": "select_all",
"Clear": "clear_selection", "Clear": "clear_selection",
"Invert": "invert_selection", "Invert": "invert_selection",
"Tile Mode": "" "Tile Mode": "",
"Modify": ""
} }
for i in select_menu_items.size(): for i in select_menu_items.size():
var item: String = select_menu_items.keys()[i] var item: String = select_menu_items.keys()[i]
if item == "Tile Mode": if item == "Tile Mode":
select_menu.add_check_item(item, i) select_menu.add_check_item(item, i)
elif item == "Modify":
_setup_selection_modify_submenu(item)
else: else:
_set_menu_shortcut(select_menu_items[item], select_menu, i, item) _set_menu_shortcut(select_menu_items[item], select_menu, i, item)
select_menu.id_pressed.connect(select_menu_id_pressed) 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: func _setup_help_menu() -> void:
# Order as in Global.HelpMenu enum # Order as in Global.HelpMenu enum
var help_menu_items := { 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() 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: func _snap_to_submenu_id_pressed(id: int) -> void:
if id == 0: if id == 0:
Global.snap_to_rectangular_grid_boundary = !Global.snap_to_rectangular_grid_boundary Global.snap_to_rectangular_grid_boundary = !Global.snap_to_rectangular_grid_boundary