Merge pull request #88970 from KoBeWi/ImageCursor2D

Improve `cursor_set_custom_image()` method
This commit is contained in:
Rémi Verschelde 2024-02-29 13:54:53 +01:00
commit 6d9a529c87
No known key found for this signature in database
GPG Key ID: C3336907360768E1
10 changed files with 63 additions and 171 deletions

View File

@ -78,6 +78,7 @@
<param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" /> <param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" />
<description> <description>
Sets a custom mouse cursor image for the defined [param shape]. This means the user's operating system and mouse cursor theme will no longer influence the mouse cursor's appearance. The image must be [code]256x256[/code] or smaller for correct appearance. [param hotspot] can optionally be set to define the area where the cursor will click. By default, [param hotspot] is set to [code]Vector2(0, 0)[/code], which is the top-left corner of the image. See also [method cursor_set_shape]. Sets a custom mouse cursor image for the defined [param shape]. This means the user's operating system and mouse cursor theme will no longer influence the mouse cursor's appearance. The image must be [code]256x256[/code] or smaller for correct appearance. [param hotspot] can optionally be set to define the area where the cursor will click. By default, [param hotspot] is set to [code]Vector2(0, 0)[/code], which is the top-left corner of the image. See also [method cursor_set_shape].
[param cursor] can be either [Texture2D] or [Image].
</description> </description>
</method> </method>
<method name="cursor_set_shape"> <method name="cursor_set_shape">

View File

@ -325,7 +325,7 @@
<param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" /> <param index="2" name="hotspot" type="Vector2" default="Vector2(0, 0)" />
<description> <description>
Sets a custom mouse cursor image, which is only visible inside the game window. The hotspot can also be specified. Passing [code]null[/code] to the image parameter resets to the system cursor. See [enum CursorShape] for the list of shapes. Sets a custom mouse cursor image, which is only visible inside the game window. The hotspot can also be specified. Passing [code]null[/code] to the image parameter resets to the system cursor. See [enum CursorShape] for the list of shapes.
[param image]'s size must be lower than or equal to 256×256. To avoid rendering issues, sizes lower than or equal to 128×128 are recommended. [param image] can be either [Texture2D] or [Image] and its size must be lower than or equal to 256×256. To avoid rendering issues, sizes lower than or equal to 128×128 are recommended.
[param hotspot] must be within [param image]'s size. [param hotspot] must be within [param image]'s size.
[b]Note:[/b] [AnimatedTexture]s aren't supported as custom mouse cursors. If using an [AnimatedTexture], only the first frame will be displayed. [b]Note:[/b] [AnimatedTexture]s aren't supported as custom mouse cursors. If using an [AnimatedTexture], only the first frame will be displayed.
[b]Note:[/b] The [b]Lossless[/b], [b]Lossy[/b] or [b]Uncompressed[/b] compression modes are recommended. The [b]Video RAM[/b] compression mode can be used, but it will be decompressed on the CPU, which means loading times are slowed down and no memory is saved compared to lossless modes. [b]Note:[/b] The [b]Lossless[/b], [b]Lossy[/b] or [b]Uncompressed[/b] compression modes are recommended. The [b]Video RAM[/b] compression mode can be used, but it will be decompressed on the CPU, which means loading times are slowed down and no memory is saved compared to lossless modes.

View File

@ -990,36 +990,9 @@ void DisplayServerWayland::cursor_set_custom_image(const Ref<Resource> &p_cursor
wayland_thread.cursor_shape_clear_custom_image(p_shape); wayland_thread.cursor_shape_clear_custom_image(p_shape);
} }
Ref<Texture2D> texture = p_cursor; Rect2 atlas_rect;
ERR_FAIL_COND(!texture.is_valid()); Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
Size2i texture_size; ERR_FAIL_COND(image.is_null());
Ref<AtlasTexture> atlas_texture = texture;
if (atlas_texture.is_valid()) {
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
// NOTE: The Wayland protocol says nothing about cursor size limits, yet if
// the texture is larger than 256x256 it won't show at least on sway.
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
ERR_FAIL_COND(texture_size.height == 0 || texture_size.width == 0);
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
if (image->is_compressed()) {
image = image->duplicate(true);
Error err = image->decompress();
ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
CustomCursor &cursor = custom_cursors[p_shape]; CustomCursor &cursor = custom_cursors[p_shape];

View File

@ -59,8 +59,6 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/input/input.h" #include "core/input/input.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/texture.h"
#include "servers/display_server.h" #include "servers/display_server.h"
#include <limits.h> #include <limits.h>

View File

@ -41,7 +41,6 @@
#include "core/string/ustring.h" #include "core/string/ustring.h"
#include "drivers/png/png_driver_common.h" #include "drivers/png/png_driver_common.h"
#include "main/main.h" #include "main/main.h"
#include "scene/resources/atlas_texture.h"
#if defined(VULKAN_ENABLED) #if defined(VULKAN_ENABLED)
#include "servers/rendering/renderer_rd/renderer_compositor_rd.h" #include "servers/rendering/renderer_rd/renderer_compositor_rd.h"
@ -3064,39 +3063,10 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
cursors_cache.erase(p_shape); cursors_cache.erase(p_shape);
} }
Ref<Texture2D> texture = p_cursor; Rect2 atlas_rect;
ERR_FAIL_COND(!texture.is_valid()); Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
Ref<AtlasTexture> atlas_texture = p_cursor; ERR_FAIL_COND(image.is_null());
Size2i texture_size; Vector2i texture_size = image->get_size();
Rect2i atlas_rect;
if (atlas_texture.is_valid()) {
texture = atlas_texture->get_atlas();
atlas_rect.size.width = texture->get_width();
atlas_rect.size.height = texture->get_height();
atlas_rect.position.x = atlas_texture->get_region().position.x;
atlas_rect.position.y = atlas_texture->get_region().position.y;
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
if (image->is_compressed()) {
image = image->duplicate(true);
Error err = image->decompress();
ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
// Create the cursor structure // Create the cursor structure
XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height); XcursorImage *cursor_image = XcursorImageCreate(texture_size.width, texture_size.height);
@ -3115,7 +3085,7 @@ void DisplayServerX11::cursor_set_custom_image(const Ref<Resource> &p_cursor, Cu
int row_index = floor(index / texture_size.width) + atlas_rect.position.y; int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
if (atlas_texture.is_valid()) { if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1); column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1); row_index = MIN(row_index, atlas_rect.size.height - 1);
} }

View File

@ -48,7 +48,6 @@
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "drivers/png/png_driver_common.h" #include "drivers/png/png_driver_common.h"
#include "main/main.h" #include "main/main.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/image_texture.h" #include "scene/resources/image_texture.h"
#if defined(GLES3_ENABLED) #if defined(GLES3_ENABLED)
@ -4037,39 +4036,10 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
cursors_cache.erase(p_shape); cursors_cache.erase(p_shape);
} }
Ref<Texture2D> texture = p_cursor;
ERR_FAIL_COND(!texture.is_valid());
Ref<AtlasTexture> atlas_texture = p_cursor;
Size2 texture_size;
Rect2 atlas_rect; Rect2 atlas_rect;
Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
if (atlas_texture.is_valid()) { ERR_FAIL_COND(image.is_null());
texture = atlas_texture->get_atlas(); Vector2i texture_size = image->get_size();
atlas_rect.size.width = texture->get_width();
atlas_rect.size.height = texture->get_height();
atlas_rect.position.x = atlas_texture->get_region().position.x;
atlas_rect.position.y = atlas_texture->get_region().position.y;
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
if (image->is_compressed()) {
image = image->duplicate(true);
Error err = image->decompress();
ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc] NSBitmapImageRep *imgrep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:nullptr initWithBitmapDataPlanes:nullptr
@ -4092,7 +4062,7 @@ void DisplayServerMacOS::cursor_set_custom_image(const Ref<Resource> &p_cursor,
int row_index = floor(i / texture_size.width) + atlas_rect.position.y; int row_index = floor(i / texture_size.width) + atlas_rect.position.y;
int column_index = (i % int(texture_size.width)) + atlas_rect.position.x; int column_index = (i % int(texture_size.width)) + atlas_rect.position.x;
if (atlas_texture.is_valid()) { if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1); column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1); row_index = MIN(row_index, atlas_rect.size.height - 1);
} }

View File

@ -36,7 +36,6 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/object/callable_method_pointer.h" #include "core/object/callable_method_pointer.h"
#include "scene/resources/atlas_texture.h"
#include "servers/rendering/dummy/rasterizer_dummy.h" #include "servers/rendering/dummy/rasterizer_dummy.h"
#ifdef GLES3_ENABLED #ifdef GLES3_ENABLED
@ -511,43 +510,12 @@ DisplayServer::CursorShape DisplayServerWeb::cursor_get_shape() const {
void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { void DisplayServerWeb::cursor_set_custom_image(const Ref<Resource> &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) {
ERR_FAIL_INDEX(p_shape, CURSOR_MAX); ERR_FAIL_INDEX(p_shape, CURSOR_MAX);
if (p_cursor.is_valid()) { if (p_cursor.is_valid()) {
Ref<Texture2D> texture = p_cursor;
ERR_FAIL_COND(!texture.is_valid());
Ref<AtlasTexture> atlas_texture = p_cursor;
Size2 texture_size;
Rect2 atlas_rect; Rect2 atlas_rect;
Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
ERR_FAIL_COND(image.is_null());
Vector2i texture_size = image->get_size();
if (atlas_texture.is_valid()) { if (atlas_rect.has_area()) {
texture = atlas_texture->get_atlas();
atlas_rect.size.width = texture->get_width();
atlas_rect.size.height = texture->get_height();
atlas_rect.position.x = atlas_texture->get_region().position.x;
atlas_rect.position.y = atlas_texture->get_region().position.y;
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
image = image->duplicate(true);
if (image->is_compressed()) {
Error err = image->decompress();
ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
if (atlas_texture.is_valid()) {
image->crop_from_point( image->crop_from_point(
atlas_rect.position.x, atlas_rect.position.x,
atlas_rect.position.y, atlas_rect.position.y,

View File

@ -38,7 +38,6 @@
#include "core/version.h" #include "core/version.h"
#include "drivers/png/png_driver_common.h" #include "drivers/png/png_driver_common.h"
#include "main/main.h" #include "main/main.h"
#include "scene/resources/atlas_texture.h"
#if defined(VULKAN_ENABLED) #if defined(VULKAN_ENABLED)
#include "rendering_context_driver_vulkan_windows.h" #include "rendering_context_driver_vulkan_windows.h"
@ -2393,38 +2392,10 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor
cursors_cache.erase(p_shape); cursors_cache.erase(p_shape);
} }
Ref<Texture2D> texture = p_cursor;
ERR_FAIL_COND(!texture.is_valid());
Ref<AtlasTexture> atlas_texture = p_cursor;
Size2 texture_size;
Rect2 atlas_rect; Rect2 atlas_rect;
Ref<Image> image = _get_cursor_image_from_resource(p_cursor, p_hotspot, atlas_rect);
if (atlas_texture.is_valid()) { ERR_FAIL_COND(image.is_null());
texture = atlas_texture->get_atlas(); Vector2i texture_size = image->get_size();
atlas_rect.size.width = texture->get_width();
atlas_rect.size.height = texture->get_height();
atlas_rect.position.x = atlas_texture->get_region().position.x;
atlas_rect.position.y = atlas_texture->get_region().position.y;
texture_size.width = atlas_texture->get_region().size.x;
texture_size.height = atlas_texture->get_region().size.y;
} else {
texture_size.width = texture->get_width();
texture_size.height = texture->get_height();
}
ERR_FAIL_COND(p_hotspot.x < 0 || p_hotspot.y < 0);
ERR_FAIL_COND(texture_size.width > 256 || texture_size.height > 256);
ERR_FAIL_COND(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height);
Ref<Image> image = texture->get_image();
ERR_FAIL_COND(!image.is_valid());
if (image->is_compressed()) {
image = image->duplicate(true);
Error err = image->decompress();
ERR_FAIL_COND_MSG(err != OK, "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
UINT image_size = texture_size.width * texture_size.height; UINT image_size = texture_size.width * texture_size.height;
@ -2453,7 +2424,7 @@ void DisplayServerWindows::cursor_set_custom_image(const Ref<Resource> &p_cursor
int row_index = floor(index / texture_size.width) + atlas_rect.position.y; int row_index = floor(index / texture_size.width) + atlas_rect.position.y;
int column_index = (index % int(texture_size.width)) + atlas_rect.position.x; int column_index = (index % int(texture_size.width)) + atlas_rect.position.x;
if (atlas_texture.is_valid()) { if (atlas_rect.has_area()) {
column_index = MIN(column_index, atlas_rect.size.width - 1); column_index = MIN(column_index, atlas_rect.size.width - 1);
row_index = MIN(row_index, atlas_rect.size.height - 1); row_index = MIN(row_index, atlas_rect.size.height - 1);
} }

View File

@ -31,6 +31,7 @@
#include "display_server.h" #include "display_server.h"
#include "core/input/input.h" #include "core/input/input.h"
#include "scene/resources/atlas_texture.h"
#include "scene/resources/texture.h" #include "scene/resources/texture.h"
#include "servers/display_server_headless.h" #include "servers/display_server_headless.h"
@ -987,6 +988,43 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(TTS_UTTERANCE_BOUNDARY); BIND_ENUM_CONSTANT(TTS_UTTERANCE_BOUNDARY);
} }
Ref<Image> DisplayServer::_get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect) {
Ref<Image> image;
ERR_FAIL_COND_V_MSG(p_hotspot.x < 0 || p_hotspot.y < 0, image, "Hotspot outside cursor image.");
Size2 texture_size;
Ref<Texture2D> texture = p_cursor;
if (texture.is_valid()) {
Ref<AtlasTexture> atlas_texture = p_cursor;
if (atlas_texture.is_valid()) {
texture = atlas_texture->get_atlas();
r_atlas_rect.size = texture->get_size();
r_atlas_rect.position = atlas_texture->get_region().position;
texture_size = atlas_texture->get_region().size;
} else {
texture_size = texture->get_size();
}
image = texture->get_image();
} else {
image = p_cursor;
ERR_FAIL_COND_V(image.is_null(), image);
texture_size = image->get_size();
}
ERR_FAIL_COND_V_MSG(p_hotspot.x > texture_size.width || p_hotspot.y > texture_size.height, image, "Hotspot outside cursor image.");
ERR_FAIL_COND_V_MSG(texture_size.width > 256 || texture_size.height > 256, image, "Cursor image too big. Max supported size is 256x256.");
ERR_FAIL_COND_V(image.is_null(), image);
if (image->is_compressed()) {
image = image->duplicate(true);
Error err = image->decompress();
ERR_FAIL_COND_V_MSG(err != OK, Ref<Image>(), "Couldn't decompress VRAM-compressed custom mouse cursor image. Switch to a lossless compression mode in the Import dock.");
}
return image;
}
void DisplayServer::register_create_function(const char *p_name, CreateFunction p_function, GetRenderingDriversFunction p_get_drivers) { void DisplayServer::register_create_function(const char *p_name, CreateFunction p_function, GetRenderingDriversFunction p_get_drivers) {
ERR_FAIL_COND(server_create_count == MAX_SERVERS); ERR_FAIL_COND(server_create_count == MAX_SERVERS);
// Headless display server is always last // Headless display server is always last

View File

@ -37,6 +37,7 @@
#include "core/variant/callable.h" #include "core/variant/callable.h"
class Texture2D; class Texture2D;
class Image;
class DisplayServer : public Object { class DisplayServer : public Object {
GDCLASS(DisplayServer, Object) GDCLASS(DisplayServer, Object)
@ -86,6 +87,8 @@ private:
protected: protected:
static void _bind_methods(); static void _bind_methods();
static Ref<Image> _get_cursor_image_from_resource(const Ref<Resource> &p_cursor, const Vector2 &p_hotspot, Rect2 &r_atlas_rect);
enum { enum {
MAX_SERVERS = 64 MAX_SERVERS = 64
}; };