diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index fea38aad55c..e84143e47b0 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -1079,6 +1079,15 @@ [b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color. + + + + + Returns screenshot of the screen [param rect]. + [b]Note:[/b] This method is implemented on macOS and Windows. + [b]Note:[/b] On macOS, this method requires "Screen Recording" permission, if permission is not granted it will return desktop wallpaper color. + + @@ -1913,6 +1922,9 @@ The display server supports initiating window drag operation on demand. See [method window_start_drag]. + + Display server supports [constant WINDOW_FLAG_EXCLUDE_FROM_CAPTURE] window flag. + Makes the mouse cursor visible if it is hidden. @@ -2128,7 +2140,12 @@ Window style is overridden, forcing sharp corners. [b]Note:[/b] This flag is implemented only on Windows (11). - + + Windows is excluded from screenshots taken by [method screen_get_image], [method screen_get_image_rect], and [method screen_get_pixel]. + [b]Note:[/b] This flag is implemented on macOS and Windows. + [b]Note:[/b] Setting this flag will [b]NOT[/b] prevent other apps from capturing an image, it should not be used as a security measure. + + Max value of the [enum WindowFlags]. diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml index 6d3294cc275..f779da8af9d 100644 --- a/doc/classes/Window.xml +++ b/doc/classes/Window.xml @@ -586,6 +586,9 @@ The screen the window is currently on. + + Windows is excluded from screenshots taken by [method DisplayServer.screen_get_image], [method DisplayServer.screen_get_image_rect], and [method DisplayServer.screen_get_pixel]. + If [code]true[/code], the [Window] will be in exclusive mode. Exclusive windows are always on top of their parent and will block all input going to the parent [Window]. Needs [member transient] enabled to work. @@ -853,7 +856,12 @@ [b]Note:[/b] This flag has no effect in embedded windows. [b]Note:[/b] This flag is implemented only on Windows (11). - + + Windows is excluded from screenshots taken by [method DisplayServer.screen_get_image], [method DisplayServer.screen_get_image_rect], and [method DisplayServer.screen_get_pixel]. + [b]Note:[/b] This flag is implemented on macOS and Windows. + [b]Note:[/b] Setting this flag will [b]NOT[/b] prevent other apps from capturing an image, it should not be used as a security measure. + + Max value of the [enum Flags]. diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 1cb4abc2052..9f1f7d21e22 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -129,6 +129,7 @@ public: bool focused = false; bool is_visible = true; bool extend_to_title = false; + bool hide_from_capture = false; Rect2i parent_safe_rect; }; @@ -326,6 +327,7 @@ public: virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Color screen_get_pixel(const Point2i &p_position) const override; virtual Ref screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Ref screen_get_image_rect(const Rect2i &p_rect) const override; virtual void screen_set_keep_on(bool p_enable) override; virtual bool screen_is_kept_on() const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 476e508a554..472d54ae62e 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -50,6 +50,8 @@ #include "main/main.h" #include "scene/resources/image_texture.h" +#include + #if defined(GLES3_ENABLED) #include "drivers/gles3/rasterizer_gles3.h" #endif @@ -1609,35 +1611,41 @@ Rect2i DisplayServerMacOS::screen_get_usable_rect(int p_screen) const { } Color DisplayServerMacOS::screen_get_pixel(const Point2i &p_position) const { - Point2i position = p_position; - // macOS native y-coordinate relative to _get_screens_origin() is negative, - // Godot passes a positive value. - position.y *= -1; - position += _get_screens_origin(); + HashSet exclude_windows; + for (HashMap::ConstIterator E = windows.begin(); E; ++E) { + if (E->value.hide_from_capture) { + exclude_windows.insert([E->value.window_object windowNumber]); + } + } + + CFArrayRef on_screen_windows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + CFMutableArrayRef capture_windows = CFArrayCreateMutableCopy(nullptr, 0, on_screen_windows); + for (long i = CFArrayGetCount(on_screen_windows) - 1; i >= 0; i--) { + CGWindowID window = (CGWindowID)(uintptr_t)CFArrayGetValueAtIndex(capture_windows, i); + if (exclude_windows.has(window)) { + CFArrayRemoveValueAtIndex(capture_windows, i); + } + } + + Point2i position = p_position - Vector2i(1, 1); + position -= screen_get_position(0); // Note: coordinates where the screen origin is in the upper-left corner of the main display and y-axis values increase downward. position /= screen_get_max_scale(); Color color; - for (NSScreen *screen in [NSScreen screens]) { - NSRect frame = [screen frame]; - if (NSMouseInRect(NSMakePoint(position.x, position.y), frame, NO)) { - NSDictionary *screenDescription = [screen deviceDescription]; - CGDirectDisplayID display_id = [[screenDescription objectForKey:@"NSScreenNumber"] unsignedIntValue]; - CGImageRef image = CGDisplayCreateImageForRect(display_id, CGRectMake(position.x - frame.origin.x, frame.size.height - (position.y - frame.origin.y), 1, 1)); - if (image) { - CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); - if (color_space) { - uint8_t img_data[4]; - CGContextRef context = CGBitmapContextCreate(img_data, 1, 1, 8, 4, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - if (context) { - CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image); - color = Color(img_data[0] / 255.0f, img_data[1] / 255.0f, img_data[2] / 255.0f, img_data[3] / 255.0f); - CGContextRelease(context); - } - CGColorSpaceRelease(color_space); - } - CGImageRelease(image); + CGImageRef image = CGWindowListCreateImageFromArray(CGRectMake(position.x, position.y, 1, 1), capture_windows, kCGWindowListOptionAll); + if (image) { + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); + if (color_space) { + uint8_t img_data[4]; + CGContextRef context = CGBitmapContextCreate(img_data, 1, 1, 8, 4, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context) { + CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image); + color = Color(img_data[0] / 255.0f, img_data[1] / 255.0f, img_data[2] / 255.0f, img_data[3] / 255.0f); + CGContextRelease(context); } + CGColorSpaceRelease(color_space); } + CGImageRelease(image); } return color; } @@ -1645,43 +1653,98 @@ Color DisplayServerMacOS::screen_get_pixel(const Point2i &p_position) const { Ref DisplayServerMacOS::screen_get_image(int p_screen) const { ERR_FAIL_INDEX_V(p_screen, get_screen_count(), Ref()); - switch (p_screen) { - case SCREEN_PRIMARY: { - p_screen = get_primary_screen(); - } break; - case SCREEN_OF_MAIN_WINDOW: { - p_screen = window_get_current_screen(MAIN_WINDOW_ID); - } break; - default: - break; + HashSet exclude_windows; + for (HashMap::ConstIterator E = windows.begin(); E; ++E) { + if (E->value.hide_from_capture) { + exclude_windows.insert([E->value.window_object windowNumber]); + } } - Ref img; - NSArray *screenArray = [NSScreen screens]; - if ((NSUInteger)p_screen < [screenArray count]) { - NSRect nsrect = [[screenArray objectAtIndex:p_screen] frame]; - NSDictionary *screenDescription = [[screenArray objectAtIndex:p_screen] deviceDescription]; - CGDirectDisplayID display_id = [[screenDescription objectForKey:@"NSScreenNumber"] unsignedIntValue]; - CGImageRef image = CGDisplayCreateImageForRect(display_id, CGRectMake(0, 0, nsrect.size.width, nsrect.size.height)); - if (image) { - CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); - if (color_space) { - NSUInteger width = CGImageGetWidth(image); - NSUInteger height = CGImageGetHeight(image); - - Vector img_data; - img_data.resize(height * width * 4); - CGContextRef context = CGBitmapContextCreate(img_data.ptrw(), width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); - if (context) { - CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); - img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); - CGContextRelease(context); - } - CGColorSpaceRelease(color_space); - } - CGImageRelease(image); + CFArrayRef on_screen_windows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + CFMutableArrayRef capture_windows = CFArrayCreateMutableCopy(nullptr, 0, on_screen_windows); + for (long i = CFArrayGetCount(on_screen_windows) - 1; i >= 0; i--) { + CGWindowID window = (CGWindowID)(uintptr_t)CFArrayGetValueAtIndex(capture_windows, i); + if (exclude_windows.has(window)) { + CFArrayRemoveValueAtIndex(capture_windows, i); } } + + Point2i position = screen_get_position(p_screen); + position -= screen_get_position(0); // Note: coordinates where the screen origin is in the upper-left corner of the main display and y-axis values increase downward. + position /= screen_get_max_scale(); + + Size2i size = screen_get_size(p_screen); + size /= screen_get_max_scale(); + + Ref img; + CGImageRef image = CGWindowListCreateImageFromArray(CGRectMake(position.x, position.y, size.width, size.height), capture_windows, kCGWindowListOptionAll); + if (image) { + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); + if (color_space) { + NSUInteger width = CGImageGetWidth(image); + NSUInteger height = CGImageGetHeight(image); + + Vector img_data; + img_data.resize(height * width * 4); + CGContextRef context = CGBitmapContextCreate(img_data.ptrw(), width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context) { + CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); + img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); + CGContextRelease(context); + img->resize(screen_get_size(p_screen).x, screen_get_size(p_screen).y, Image::INTERPOLATE_NEAREST); + } + CGColorSpaceRelease(color_space); + } + CGImageRelease(image); + } + return img; +} + +Ref DisplayServerMacOS::screen_get_image_rect(const Rect2i &p_rect) const { + HashSet exclude_windows; + for (HashMap::ConstIterator E = windows.begin(); E; ++E) { + if (E->value.hide_from_capture) { + exclude_windows.insert([E->value.window_object windowNumber]); + } + } + + CFArrayRef on_screen_windows = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID); + CFMutableArrayRef capture_windows = CFArrayCreateMutableCopy(nullptr, 0, on_screen_windows); + for (long i = CFArrayGetCount(on_screen_windows) - 1; i >= 0; i--) { + CGWindowID window = (CGWindowID)(uintptr_t)CFArrayGetValueAtIndex(capture_windows, i); + if (exclude_windows.has(window)) { + CFArrayRemoveValueAtIndex(capture_windows, i); + } + } + + Point2i position = p_rect.position; + position -= screen_get_position(0); // Note: coordinates where the screen origin is in the upper-left corner of the main display and y-axis values increase downward. + position /= screen_get_max_scale(); + + Size2i size = p_rect.size; + size /= screen_get_max_scale(); + + Ref img; + CGImageRef image = CGWindowListCreateImageFromArray(CGRectMake(position.x, position.y, size.width, size.height), capture_windows, kCGWindowListOptionAll); + if (image) { + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); + if (color_space) { + NSUInteger width = CGImageGetWidth(image); + NSUInteger height = CGImageGetHeight(image); + + Vector img_data; + img_data.resize_zeroed(height * width * 4); + CGContextRef context = CGBitmapContextCreate(img_data.ptrw(), width, height, 8, 4 * width, color_space, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); + if (context) { + CGContextDrawImage(context, CGRectMake(0, 0, width, height), image); + img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); + CGContextRelease(context); + img->resize(p_rect.size.x, p_rect.size.y, Image::INTERPOLATE_NEAREST); + } + CGColorSpaceRelease(color_space); + } + CGImageRelease(image); + } return img; } @@ -2528,6 +2591,14 @@ void DisplayServerMacOS::window_set_flag(WindowFlags p_flag, bool p_enabled, Win NSWindow *w = wd.window_object; w.excludedFromWindowsMenu = wd.is_popup || wd.no_focus; } break; + case WINDOW_FLAG_EXCLUDE_FROM_CAPTURE: { + if (p_enabled) { + [wd.window_object setSharingType:NSWindowSharingNone]; + } else { + [wd.window_object setSharingType:NSWindowSharingReadWrite]; + } + wd.hide_from_capture = p_enabled; + } break; case WINDOW_FLAG_MOUSE_PASSTHROUGH: { wd.mpass = p_enabled; } break; @@ -2573,6 +2644,9 @@ bool DisplayServerMacOS::window_get_flag(WindowFlags p_flag, WindowID p_window) case WINDOW_FLAG_NO_FOCUS: { return wd.no_focus; } break; + case WINDOW_FLAG_EXCLUDE_FROM_CAPTURE: { + return wd.hide_from_capture; + } break; case WINDOW_FLAG_MOUSE_PASSTHROUGH: { return wd.mpass; } break; diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 271a93b122f..0769603d8eb 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -1395,6 +1395,62 @@ Ref DisplayServerWindows::screen_get_image(int p_screen) const { return img; } +Ref DisplayServerWindows::screen_get_image_rect(const Rect2i &p_rect) const { + Point2i pos = p_rect.position + _get_screens_origin(); + Size2i size = p_rect.size; + + POINT p1; + p1.x = pos.x; + p1.y = pos.y; + + POINT p2; + p2.x = pos.x + size.x; + p2.y = pos.y + size.y; + if (win81p_LogicalToPhysicalPointForPerMonitorDPI) { + win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p1); + win81p_LogicalToPhysicalPointForPerMonitorDPI(0, &p2); + } + + Ref img; + HDC dc = GetDC(0); + if (dc) { + HDC hdc = CreateCompatibleDC(dc); + int width = p2.x - p1.x; + int height = p2.y - p1.y; + if (hdc) { + HBITMAP hbm = CreateCompatibleBitmap(dc, width, height); + if (hbm) { + SelectObject(hdc, hbm); + BitBlt(hdc, 0, 0, width, height, dc, p1.x, p1.y, SRCCOPY); + + BITMAPINFO bmp_info = {}; + bmp_info.bmiHeader.biSize = sizeof(bmp_info.bmiHeader); + bmp_info.bmiHeader.biWidth = width; + bmp_info.bmiHeader.biHeight = -height; + bmp_info.bmiHeader.biPlanes = 1; + bmp_info.bmiHeader.biBitCount = 32; + bmp_info.bmiHeader.biCompression = BI_RGB; + + Vector img_data; + img_data.resize(width * height * 4); + GetDIBits(hdc, hbm, 0, height, img_data.ptrw(), &bmp_info, DIB_RGB_COLORS); + + uint8_t *wr = (uint8_t *)img_data.ptrw(); + for (int i = 0; i < width * height; i++) { + SWAP(wr[i * 4 + 0], wr[i * 4 + 2]); // Swap B and R. + } + img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, img_data); + + DeleteObject(hbm); + } + DeleteDC(hdc); + } + ReleaseDC(NULL, dc); + } + + return img; +} + float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ @@ -1507,6 +1563,14 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod if (p_flags & WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT) { wd.mpass = true; } + if (p_flags & WINDOW_FLAG_EXCLUDE_FROM_CAPTURE_BIT) { + wd.hide_from_capture = true; + if (os_ver.dwBuildNumber >= 19041) { + SetWindowDisplayAffinity(wd.hWnd, WDA_EXCLUDEFROMCAPTURE); + } else { + SetWindowDisplayAffinity(wd.hWnd, WDA_MONITOR); + } + } if (p_flags & WINDOW_FLAG_POPUP_BIT) { wd.is_popup = true; } @@ -2395,6 +2459,18 @@ void DisplayServerWindows::window_set_flag(WindowFlags p_flag, bool p_enabled, W wd.mpass = p_enabled; _update_window_mouse_passthrough(p_window); } break; + case WINDOW_FLAG_EXCLUDE_FROM_CAPTURE: { + wd.hide_from_capture = p_enabled; + if (p_enabled) { + if (os_ver.dwBuildNumber >= 19041) { + SetWindowDisplayAffinity(wd.hWnd, WDA_EXCLUDEFROMCAPTURE); + } else { + SetWindowDisplayAffinity(wd.hWnd, WDA_MONITOR); + } + } else { + SetWindowDisplayAffinity(wd.hWnd, WDA_NONE); + } + } break; case WINDOW_FLAG_POPUP: { ERR_FAIL_COND_MSG(p_window == MAIN_WINDOW_ID, "Main window can't be popup."); ERR_FAIL_COND_MSG(IsWindowVisible(wd.hWnd) && (wd.is_popup != p_enabled), "Popup flag can't changed while window is opened."); @@ -2432,6 +2508,9 @@ bool DisplayServerWindows::window_get_flag(WindowFlags p_flag, WindowID p_window case WINDOW_FLAG_MOUSE_PASSTHROUGH: { return wd.mpass; } break; + case WINDOW_FLAG_EXCLUDE_FROM_CAPTURE: { + return wd.hide_from_capture; + } break; case WINDOW_FLAG_POPUP: { return wd.is_popup; } break; @@ -6083,7 +6162,6 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); // Load Windows version info. - OSVERSIONINFOW os_ver; ZeroMemory(&os_ver, sizeof(OSVERSIONINFOW)); os_ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOW); diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index c6b8693a9d5..af5243a1495 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -362,6 +362,10 @@ typedef enum _SHC_PROCESS_DPI_AWARENESS { class DropTargetWindows; +#ifndef WDA_EXCLUDEFROMCAPTURE +#define WDA_EXCLUDEFROMCAPTURE 0x00000011 +#endif + class DisplayServerWindows : public DisplayServer { // No need to register with GDCLASS, it's platform-specific and nothing is added. @@ -415,6 +419,8 @@ class DisplayServerWindows : public DisplayServer { TIMER_ID_WINDOW_ACTIVATION = 2, }; + OSVERSIONINFOW os_ver; + enum { KEY_EVENT_BUFFER_SIZE = 512 }; @@ -483,6 +489,7 @@ class DisplayServerWindows : public DisplayServer { bool context_created = false; bool mpass = false; bool sharp_corners = false; + bool hide_from_capture = false; // Used to transfer data between events using timer. WPARAM saved_wparam; @@ -714,6 +721,7 @@ public: virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; virtual Color screen_get_pixel(const Point2i &p_position) const override; virtual Ref screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const override; + virtual Ref screen_get_image_rect(const Rect2i &p_rect) const override; virtual void screen_set_keep_on(bool p_enable) override; //disable screensaver virtual bool screen_is_kept_on() const override; diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index d12caad2f64..e04ad55fd2f 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -40,10 +40,12 @@ #include "scene/gui/margin_container.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" +#include "scene/gui/panel.h" #include "scene/gui/popup_menu.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_rect.h" +#include "scene/resources/atlas_texture.h" #include "scene/resources/color_palette.h" #include "scene/resources/image_texture.h" #include "scene/resources/style_box_flat.h" @@ -155,7 +157,19 @@ void ColorPicker::_notification(int p_what) { if (!is_picking_color) { return; } - set_pick_color(DisplayServer::get_singleton()->screen_get_pixel(DisplayServer::get_singleton()->mouse_get_position())); + DisplayServer *ds = DisplayServer::get_singleton(); + Vector2 ofs = ds->mouse_get_position(); + picker_window->set_position(ofs - Vector2(28, 28)); + + Color c = DisplayServer::get_singleton()->screen_get_pixel(DisplayServer::get_singleton()->mouse_get_position()); + + picker_preview_style_box_color->set_bg_color(c); + picker_preview_style_box->set_bg_color(c.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f)); + + Ref zoom_preview_img = ds->screen_get_image_rect(Rect2i(ofs.x - 8, ofs.y - 8, 17, 17)); + picker_texture_zoom->set_texture(ImageTexture::create_from_image(zoom_preview_img)); + + set_pick_color(c); } } } @@ -1685,17 +1699,54 @@ void ColorPicker::_add_preset_pressed() { void ColorPicker::_pick_button_pressed() { is_picking_color = true; - set_process_internal(true); if (!picker_window) { picker_window = memnew(Popup); - picker_window->set_size(Vector2i(1, 1)); + picker_window->set_size(Vector2i(55, 72)); picker_window->connect(SceneStringName(visibility_changed), callable_mp(this, &ColorPicker::_pick_finished)); + picker_window->connect(SceneStringName(window_input), callable_mp(this, &ColorPicker::_target_gui_input)); + + picker_window->set_flag(Window::FLAG_EXCLUDE_FROM_CAPTURE, true); + + picker_preview = memnew(Panel); + picker_preview->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_preview->set_size(Vector2i(55, 72)); + picker_window->add_child(picker_preview); + + picker_preview_color = memnew(Panel); + picker_preview_color->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_preview_color->set_size(Vector2i(51, 15)); + picker_preview_color->set_position(Vector2i(2, 55)); + picker_preview->add_child(picker_preview_color); + + picker_texture_zoom = memnew(TextureRect); + picker_texture_zoom->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_texture_zoom->set_custom_minimum_size(Vector2i(51, 51)); + picker_texture_zoom->set_position(Vector2i(2, 2)); + picker_texture_zoom->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + picker_preview->add_child(picker_texture_zoom); + + picker_preview_style_box.instantiate(); + picker_preview->add_theme_style_override(SceneStringName(panel), picker_preview_style_box); + + picker_preview_style_box_color.instantiate(); + picker_preview_color->add_theme_style_override(SceneStringName(panel), picker_preview_style_box_color); + add_child(picker_window, false, INTERNAL_MODE_FRONT); } + set_process_internal(true); + picker_window->popup(); } +void ColorPicker::_target_gui_input(const Ref &p_event) { + const Ref mouse_event = p_event; + if (mouse_event.is_valid() && mouse_event->is_pressed()) { + picker_window->hide(); + _pick_finished(); + } +} + void ColorPicker::_pick_finished() { if (picker_window->is_visible()) { return; @@ -1825,27 +1876,46 @@ void ColorPicker::_pick_button_pressed_legacy() { picker_texture_rect = memnew(TextureRect); picker_texture_rect->set_anchors_preset(Control::PRESET_FULL_RECT); picker_texture_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE); + picker_texture_rect->set_default_cursor_shape(Control::CURSOR_CROSS); picker_window->add_child(picker_texture_rect); - picker_texture_rect->set_default_cursor_shape(CURSOR_POINTING_HAND); picker_texture_rect->connect(SceneStringName(gui_input), callable_mp(this, &ColorPicker::_picker_texture_input)); - picker_preview_label = memnew(Label); - picker_preview_label->set_anchors_preset(Control::PRESET_CENTER_TOP); - picker_preview_label->set_text(ETR("Color Picking active")); + picker_preview = memnew(Panel); + picker_preview->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_preview->set_size(Vector2i(55, 72)); + picker_window->add_child(picker_preview); + + picker_preview_color = memnew(Panel); + picker_preview_color->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_preview_color->set_size(Vector2i(51, 15)); + picker_preview_color->set_position(Vector2i(2, 55)); + picker_preview->add_child(picker_preview_color); + + picker_texture_zoom = memnew(TextureRect); + picker_texture_zoom->set_mouse_filter(MOUSE_FILTER_IGNORE); + picker_texture_zoom->set_custom_minimum_size(Vector2i(51, 51)); + picker_texture_zoom->set_position(Vector2i(2, 2)); + picker_texture_zoom->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + picker_preview->add_child(picker_texture_zoom); picker_preview_style_box.instantiate(); - picker_preview_style_box->set_bg_color(Color(1.0, 1.0, 1.0)); - picker_preview_style_box->set_content_margin_all(4.0); - picker_preview_label->add_theme_style_override(CoreStringName(normal), picker_preview_style_box); + picker_preview->add_theme_style_override(SceneStringName(panel), picker_preview_style_box); - picker_window->add_child(picker_preview_label); + picker_preview_style_box_color.instantiate(); + picker_preview_color->add_theme_style_override(SceneStringName(panel), picker_preview_style_box_color); } Rect2i screen_rect; if (picker_window->is_embedded()) { + Ref tx = ImageTexture::create_from_image(picker_window->get_embedder()->get_texture()->get_image()); screen_rect = picker_window->get_embedder()->get_visible_rect(); picker_window->set_position(Point2i()); - picker_texture_rect->set_texture(ImageTexture::create_from_image(picker_window->get_embedder()->get_texture()->get_image())); + picker_texture_rect->set_texture(tx); + + Ref atlas; + atlas.instantiate(); + atlas->set_atlas(tx); + picker_texture_zoom->set_texture(atlas); } else { screen_rect = picker_window->get_parent_rect(); picker_window->set_position(screen_rect.position); @@ -1874,7 +1944,6 @@ void ColorPicker::_pick_button_pressed_legacy() { } picker_window->set_size(screen_rect.size); - picker_preview_label->set_custom_minimum_size(screen_rect.size / 10); // 10% of size in each axis. picker_window->popup(); } @@ -1895,9 +1964,17 @@ void ColorPicker::_picker_texture_input(const Ref &p_event) { Ref img = picker_texture_rect->get_texture()->get_image(); if (img.is_valid() && !img->is_empty()) { Vector2 ofs = mev->get_position(); + picker_preview->set_position(ofs - Vector2(28, 28)); + Vector2 scale = picker_texture_rect->get_size() / img->get_size(); + ofs /= scale; picker_color = img->get_pixel(ofs.x, ofs.y); - picker_preview_style_box->set_bg_color(picker_color); - picker_preview_label->add_theme_color_override(SceneStringName(font_color), picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f)); + picker_preview_style_box_color->set_bg_color(picker_color); + picker_preview_style_box->set_bg_color(picker_color.get_luminance() < 0.5 ? Color(1.0f, 1.0f, 1.0f) : Color(0.0f, 0.0f, 0.0f)); + + Ref atlas = picker_texture_zoom->get_texture(); + if (atlas.is_valid()) { + atlas->set_region(Rect2i(ofs.x - 8, ofs.y - 8, 17, 17)); + } } } } diff --git a/scene/gui/color_picker.h b/scene/gui/color_picker.h index 183bbae072f..829263fbf55 100644 --- a/scene/gui/color_picker.h +++ b/scene/gui/color_picker.h @@ -133,10 +133,14 @@ private: Vector modes; Popup *picker_window = nullptr; + TextureRect *picker_texture_zoom = nullptr; + Panel *picker_preview = nullptr; + Panel *picker_preview_color = nullptr; + Ref picker_preview_style_box; + Ref picker_preview_style_box_color; + // Legacy color picking. TextureRect *picker_texture_rect = nullptr; - Label *picker_preview_label = nullptr; - Ref picker_preview_style_box; Color picker_color; FileDialog *file_dialog = nullptr; Button *menu_btn = nullptr; @@ -297,6 +301,7 @@ private: void _add_preset_pressed(); void _html_focus_exit(); void _pick_button_pressed(); + void _target_gui_input(const Ref &p_event); void _pick_finished(); void _update_menu_items(); void _update_menu(); diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 33e8b81991e..71fac3df991 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -3008,6 +3008,7 @@ void Window::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "extend_to_title"), "set_flag", "get_flag", FLAG_EXTEND_TO_TITLE); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "mouse_passthrough"), "set_flag", "get_flag", FLAG_MOUSE_PASSTHROUGH); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "sharp_corners"), "set_flag", "get_flag", FLAG_SHARP_CORNERS); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "exclude_from_capture"), "set_flag", "get_flag", FLAG_EXCLUDE_FROM_CAPTURE); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_native"), "set_force_native", "get_force_native"); ADD_GROUP("Limits", ""); @@ -3062,6 +3063,7 @@ void Window::_bind_methods() { BIND_ENUM_CONSTANT(FLAG_EXTEND_TO_TITLE); BIND_ENUM_CONSTANT(FLAG_MOUSE_PASSTHROUGH); BIND_ENUM_CONSTANT(FLAG_SHARP_CORNERS); + BIND_ENUM_CONSTANT(FLAG_EXCLUDE_FROM_CAPTURE); BIND_ENUM_CONSTANT(FLAG_MAX); BIND_ENUM_CONSTANT(CONTENT_SCALE_MODE_DISABLED); diff --git a/scene/main/window.h b/scene/main/window.h index 6c82efcc3f9..86b1d7c71ac 100644 --- a/scene/main/window.h +++ b/scene/main/window.h @@ -33,6 +33,7 @@ #include "scene/main/viewport.h" #include "scene/resources/theme.h" +#include "servers/display_server.h" class Font; class Shortcut; @@ -63,6 +64,7 @@ public: FLAG_EXTEND_TO_TITLE = DisplayServer::WINDOW_FLAG_EXTEND_TO_TITLE, FLAG_MOUSE_PASSTHROUGH = DisplayServer::WINDOW_FLAG_MOUSE_PASSTHROUGH, FLAG_SHARP_CORNERS = DisplayServer::WINDOW_FLAG_SHARP_CORNERS, + FLAG_EXCLUDE_FROM_CAPTURE = DisplayServer::WINDOW_FLAG_EXCLUDE_FROM_CAPTURE, FLAG_MAX = DisplayServer::WINDOW_FLAG_MAX, }; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index ff9a07f3970..ff274a2f69e 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -903,6 +903,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_pixel", "position"), &DisplayServer::screen_get_pixel); ClassDB::bind_method(D_METHOD("screen_get_image", "screen"), &DisplayServer::screen_get_image, DEFVAL(SCREEN_OF_MAIN_WINDOW)); + ClassDB::bind_method(D_METHOD("screen_get_image_rect", "rect"), &DisplayServer::screen_get_image_rect); ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW)); ClassDB::bind_method(D_METHOD("screen_get_orientation", "screen"), &DisplayServer::screen_get_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW)); @@ -1065,6 +1066,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE); BIND_ENUM_CONSTANT(FEATURE_NATIVE_DIALOG_FILE_EXTRA); BIND_ENUM_CONSTANT(FEATURE_WINDOW_DRAG); + BIND_ENUM_CONSTANT(FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -1138,6 +1140,7 @@ void DisplayServer::_bind_methods() { BIND_ENUM_CONSTANT(WINDOW_FLAG_EXTEND_TO_TITLE); BIND_ENUM_CONSTANT(WINDOW_FLAG_MOUSE_PASSTHROUGH); BIND_ENUM_CONSTANT(WINDOW_FLAG_SHARP_CORNERS); + BIND_ENUM_CONSTANT(WINDOW_FLAG_EXCLUDE_FROM_CAPTURE); BIND_ENUM_CONSTANT(WINDOW_FLAG_MAX); BIND_ENUM_CONSTANT(WINDOW_EVENT_MOUSE_ENTER); diff --git a/servers/display_server.h b/servers/display_server.h index 805d2374ab0..93d03f5554f 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -154,6 +154,7 @@ public: FEATURE_NATIVE_DIALOG_FILE, FEATURE_NATIVE_DIALOG_FILE_EXTRA, FEATURE_WINDOW_DRAG, + FEATURE_SCREEN_EXCLUDE_FROM_CAPTURE, }; virtual bool has_feature(Feature p_feature) const = 0; @@ -346,6 +347,7 @@ public: virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0; virtual Color screen_get_pixel(const Point2i &p_position) const { return Color(); } virtual Ref screen_get_image(int p_screen = SCREEN_OF_MAIN_WINDOW) const { return Ref(); } + virtual Ref screen_get_image_rect(const Rect2i &p_rect) const { return Ref(); } virtual bool is_touchscreen_available() const; // Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation` @@ -387,6 +389,7 @@ public: WINDOW_FLAG_EXTEND_TO_TITLE, WINDOW_FLAG_MOUSE_PASSTHROUGH, WINDOW_FLAG_SHARP_CORNERS, + WINDOW_FLAG_EXCLUDE_FROM_CAPTURE, WINDOW_FLAG_MAX, }; @@ -401,6 +404,7 @@ public: WINDOW_FLAG_EXTEND_TO_TITLE_BIT = (1 << WINDOW_FLAG_EXTEND_TO_TITLE), WINDOW_FLAG_MOUSE_PASSTHROUGH_BIT = (1 << WINDOW_FLAG_MOUSE_PASSTHROUGH), WINDOW_FLAG_SHARP_CORNERS_BIT = (1 << WINDOW_FLAG_SHARP_CORNERS), + WINDOW_FLAG_EXCLUDE_FROM_CAPTURE_BIT = (1 << WINDOW_FLAG_EXCLUDE_FROM_CAPTURE), }; virtual WindowID create_sub_window(WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i(), bool p_exclusive = false, WindowID p_transient_parent = INVALID_WINDOW_ID);