diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
index 8209b63cf..161b3f4ae 100644
--- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
+++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs
@@ -25,6 +25,7 @@ namespace FlaxEditor.GUI.Dialogs
{
private const float ButtonsWidth = 60.0f;
private const float PickerMargin = 6.0f;
+ private const float EyedropperMargin = 8.0f;
private const float RGBAMargin = 12.0f;
private const float HSVMargin = 0.0f;
private const float ChannelsMargin = 4.0f;
@@ -34,6 +35,7 @@ namespace FlaxEditor.GUI.Dialogs
private Color _value;
private bool _disableEvents;
private bool _useDynamicEditing;
+ private bool _activeEyedropper;
private ColorValueBox.ColorPickerEvent _onChanged;
private ColorValueBox.ColorPickerClosedEvent _onClosed;
@@ -48,6 +50,7 @@ namespace FlaxEditor.GUI.Dialogs
private TextBox _cHex;
private Button _cCancel;
private Button _cOK;
+ private Button _cEyedropper;
///
/// Gets the selected color.
@@ -192,10 +195,44 @@ namespace FlaxEditor.GUI.Dialogs
};
_cOK.Clicked += OnSubmit;
+ // Eyedropper button
+ var style = Style.Current;
+ _cEyedropper = new Button(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin)
+ {
+ TooltipText = "Eyedropper tool to pick a color directly from the screen",
+ BackgroundBrush = new SpriteBrush(Editor.Instance.Icons.Search32),
+ BackgroundColor = style.Foreground,
+ BackgroundColorHighlighted = style.Foreground.RGBMultiplied(0.9f),
+ BorderColor = Color.Transparent,
+ BorderColorHighlighted = style.BorderSelected,
+ Parent = this,
+ };
+ _cEyedropper.Clicked += OnEyedropStart;
+ _cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f;
+ _cEyedropper.Width = _cEyedropper.Height;
+ _cEyedropper.X -= _cEyedropper.Width;
+
// Set initial color
SelectedColor = initialValue;
}
+ private void OnColorPicked(Color32 colorPicked)
+ {
+ if (_activeEyedropper)
+ {
+ _activeEyedropper = false;
+ SelectedColor = colorPicked;
+ ScreenUtilities.PickColorDone -= OnColorPicked;
+ }
+ }
+
+ private void OnEyedropStart()
+ {
+ _activeEyedropper = true;
+ ScreenUtilities.PickColor();
+ ScreenUtilities.PickColorDone += OnColorPicked;
+ }
+
private void OnRGBAChanged()
{
if (_disableEvents)
@@ -221,6 +258,19 @@ namespace FlaxEditor.GUI.Dialogs
SelectedColor = color;
}
+ ///
+ public override void Update(float deltaTime)
+ {
+ base.Update(deltaTime);
+
+ // Update eye dropper tool
+ if (_activeEyedropper)
+ {
+ Float2 mousePosition = Platform.MousePosition;
+ SelectedColor = ScreenUtilities.GetColorAt(mousePosition);
+ }
+ }
+
///
public override void Draw()
{
@@ -274,6 +324,20 @@ namespace FlaxEditor.GUI.Dialogs
base.OnShow();
}
+ ///
+ public override bool OnKeyDown(KeyboardKeys key)
+ {
+ if (_activeEyedropper && key == KeyboardKeys.Escape)
+ {
+ // Cancel eye dropping
+ _activeEyedropper = false;
+ ScreenUtilities.PickColorDone -= OnColorPicked;
+ return true;
+ }
+
+ return base.OnKeyDown(key);
+ }
+
///
public override void OnSubmit()
{
diff --git a/Source/Editor/GUI/Input/ColorValueBox.cs b/Source/Editor/GUI/Input/ColorValueBox.cs
index bafa27c87..167cc65bb 100644
--- a/Source/Editor/GUI/Input/ColorValueBox.cs
+++ b/Source/Editor/GUI/Input/ColorValueBox.cs
@@ -57,6 +57,11 @@ namespace FlaxEditor.GUI.Input
///
protected Color _value;
+ ///
+ /// Enables live preview of the selected value from the picker. Otherwise will update the value only when user confirms it on dialog closing.
+ ///
+ public bool UseDynamicEditing = true;
+
///
/// Occurs when value gets changed.
///
@@ -143,7 +148,7 @@ namespace FlaxEditor.GUI.Input
base.OnSubmit();
// Show color picker dialog
- _currentDialog = ShowPickColorDialog?.Invoke(this, _value, OnColorChanged, OnPickerClosed);
+ _currentDialog = ShowPickColorDialog?.Invoke(this, _value, OnColorChanged, OnPickerClosed, UseDynamicEditing);
}
private void OnColorChanged(Color color, bool sliding)
diff --git a/Source/Editor/Surface/Elements/ColorValue.cs b/Source/Editor/Surface/Elements/ColorValue.cs
index 96069cfeb..19934581d 100644
--- a/Source/Editor/Surface/Elements/ColorValue.cs
+++ b/Source/Editor/Surface/Elements/ColorValue.cs
@@ -30,7 +30,7 @@ namespace FlaxEditor.Surface.Elements
{
ParentNode = parentNode;
Archetype = archetype;
-
+ UseDynamicEditing = false;
ParentNode.ValuesChanged += OnNodeValuesChanged;
}
diff --git a/Source/Editor/Utilities/ScreenUtilities.cpp b/Source/Editor/Utilities/ScreenUtilities.cpp
new file mode 100644
index 000000000..cff41f7bf
--- /dev/null
+++ b/Source/Editor/Utilities/ScreenUtilities.cpp
@@ -0,0 +1,130 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#include "ScreenUtilities.h"
+#include "Engine/Core/Math/Vector2.h"
+#include "Engine/Core/Delegate.h"
+#include "Engine/Core/Log.h"
+#include "Engine/Profiler/ProfilerCPU.h"
+
+Delegate ScreenUtilities::PickColorDone;
+
+#if PLATFORM_WINDOWS
+
+#include
+
+#pragma comment(lib, "Gdi32.lib")
+
+static HHOOK MouseCallbackHook;
+
+LRESULT CALLBACK OnScreenUtilsMouseCallback(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam)
+{
+ if (nCode >= 0 && wParam == WM_LBUTTONDOWN)
+ {
+ UnhookWindowsHookEx(MouseCallbackHook);
+
+ // Push event with the picked color
+ const Float2 cursorPos = Platform::GetMousePosition();
+ const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
+ ScreenUtilities::PickColorDone(colorPicked);
+ return 1;
+ }
+ return CallNextHookEx(NULL, nCode, wParam, lParam);
+}
+
+Color32 ScreenUtilities::GetColorAt(const Float2& pos)
+{
+ PROFILE_CPU();
+ HDC deviceContext = GetDC(NULL);
+ COLORREF color = GetPixel(deviceContext, (int)pos.X, (int)pos.Y);
+ ReleaseDC(NULL, deviceContext);
+ return Color32(GetRValue(color), GetGValue(color), GetBValue(color), 255);
+}
+
+void ScreenUtilities::PickColor()
+{
+ MouseCallbackHook = SetWindowsHookEx(WH_MOUSE_LL, OnScreenUtilsMouseCallback, NULL, NULL);
+ if (MouseCallbackHook == NULL)
+ {
+ LOG(Warning, "Failed to set mouse hook.");
+ LOG(Warning, "Error: {0}", GetLastError());
+ }
+}
+
+#elif PLATFORM_LINUX
+
+#include "Engine/Platform/Linux/LinuxPlatform.h"
+#include "Engine/Platform/Linux/IncludeX11.h"
+
+Color32 ScreenUtilities::GetColorAt(const Float2& pos)
+{
+ X11::XColor color;
+
+ X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay();
+ int defaultScreen = X11::XDefaultScreen(display);
+
+ X11::XImage* image;
+ image = X11::XGetImage(display, X11::XRootWindow(display, defaultScreen), x, y, 1, 1, AllPlanes, XYPixmap);
+ color.pixel = XGetPixel(image, 0, 0);
+ X11::XFree(image);
+
+ X11::XQueryColor(display, X11::XDefaultColormap(display, defaultScreen), &color);
+
+ Color32 outputColor;
+ outputColor.R = color.red / 256;
+ outputColor.G = color.green / 256;
+ outputColor.B = color.blue / 256;
+ return outputColor;
+}
+
+void OnScreenUtilsXEventCallback(void* eventPtr)
+{
+ X11::XEvent* event = (X11::XEvent*) eventPtr;
+ X11::Display* display = (X11::Display*)LinuxPlatform::GetXDisplay();
+ if (event->type == ButtonPress)
+ {
+ const Float2 cursorPos = Platform::GetMousePosition();
+ const Color32 colorPicked = ScreenUtilities::GetColorAt(cursorPos);
+ X11::XUngrabPointer(display, CurrentTime);
+ ScreenUtilities::PickColorDone(colorPicked);
+ LinuxPlatform::xEventRecieved.Unbind(OnScreenUtilsXEventCallback);
+ }
+}
+
+void ScreenUtilities::PickColor()
+{
+ PROFILE_CPU();
+ X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay();
+ X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display));
+
+ X11::Cursor cursor = XCreateFontCursor(display, 130);
+ int grabbedPointer = X11::XGrabPointer(display, rootWindow, 0, ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);
+ if (grabbedPointer != GrabSuccess)
+ {
+ LOG(Error, "Failed to grab cursor for events.");
+ X11::XFreeCursor(display, cursor);
+ return;
+ }
+
+ X11::XFreeCursor(display, cursor);
+ LinuxPlatform::xEventRecieved.Bind(OnScreenUtilsXEventCallback);
+}
+
+#elif PLATFORM_MAC
+
+#include
+#include
+
+Color32 ScreenUtilities::GetColorAt(const Float2& pos)
+{
+ // TODO: implement ScreenUtilities for macOS
+ return { 0, 0, 0, 255 };
+}
+
+void ScreenUtilities::PickColor()
+{
+ // This is what C# calls to start the color picking sequence
+ // This should stop mouse clicks from working for one click, and that click is on the selected color
+ // There is a class called NSColorSample that might implement that for you, but maybe not.
+}
+
+#endif
diff --git a/Source/Editor/Utilities/ScreenUtilities.h b/Source/Editor/Utilities/ScreenUtilities.h
new file mode 100644
index 000000000..506dc8634
--- /dev/null
+++ b/Source/Editor/Utilities/ScreenUtilities.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
+
+#pragma once
+
+#include "Engine/Core/Types/BaseTypes.h"
+#include "Engine/Core/Math/Color32.h"
+#include "Engine/Core/Math/Vector2.h"
+#include "Engine/Core/Delegate.h"
+
+///
+/// Platform-dependent screen utilities.
+///
+API_CLASS(Static) class FLAXENGINE_API ScreenUtilities
+{
+ DECLARE_SCRIPTING_TYPE_MINIMAL(ScreenUtilities);
+
+ ///
+ /// Gets the pixel color at the specified coordinates.
+ ///
+ /// Screen-space coordinate to read.
+ /// Pixel color at the specified coordinates.
+ API_FUNCTION() static Color32 GetColorAt(const Float2& pos);
+
+ ///
+ /// Starts async color picking. Color will be returned through PickColorDone event when the actions ends (user selected the final color with a mouse). When action is active, GetColorAt can be used to read the current value.
+ ///
+ API_FUNCTION() static void PickColor();
+
+ ///
+ /// Called when PickColor action is finished.
+ ///
+ API_EVENT() static Delegate PickColorDone;
+};
diff --git a/Source/Engine/Input/Keyboard.h b/Source/Engine/Input/Keyboard.h
index 45f8e295e..7e238bcf5 100644
--- a/Source/Engine/Input/Keyboard.h
+++ b/Source/Engine/Input/Keyboard.h
@@ -26,9 +26,8 @@ protected:
public:
///
- /// Gets the text entered during the current frame.
+ /// Gets the text entered during the current frame (Unicode format).
///
- /// The input text (Unicode).
API_PROPERTY() StringView GetInputText() const
{
return StringView(_state.InputText, _state.InputTextLength);
diff --git a/Source/Engine/Input/Mouse.h b/Source/Engine/Input/Mouse.h
index 7fc2be75a..0acd31238 100644
--- a/Source/Engine/Input/Mouse.h
+++ b/Source/Engine/Input/Mouse.h
@@ -58,7 +58,6 @@ public:
///
/// Gets the position of the mouse in the screen-space coordinates.
///
- /// The mouse position
API_PROPERTY() FORCE_INLINE Float2 GetPosition() const
{
return _state.MousePosition;
@@ -72,7 +71,6 @@ public:
///
/// Gets the delta position of the mouse in the screen-space coordinates.
///
- /// The mouse position delta
API_PROPERTY() FORCE_INLINE Float2 GetPositionDelta() const
{
return _state.MousePosition - _prevState.MousePosition;
@@ -81,7 +79,6 @@ public:
///
/// Gets the mouse wheel change during the last frame.
///
- /// Mouse wheel value delta
API_PROPERTY() FORCE_INLINE float GetScrollDelta() const
{
return _state.MouseWheelDelta;
diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp
index eb02c1ce9..89fcc1011 100644
--- a/Source/Engine/Platform/Base/PlatformBase.cpp
+++ b/Source/Engine/Platform/Base/PlatformBase.cpp
@@ -5,6 +5,7 @@
#include "Engine/Platform/MemoryStats.h"
#include "Engine/Platform/MessageBox.h"
#include "Engine/Platform/FileSystem.h"
+#include "Engine/Platform/Window.h"
#include "Engine/Platform/User.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Types/DateTime.h"
@@ -520,6 +521,21 @@ void PlatformBase::CreateGuid(Guid& result)
result = Guid(dateThingHigh, randomThing | (sequentialThing << 16), cyclesThing, dateThingLow);
}
+Float2 PlatformBase::GetMousePosition()
+{
+ const Window* win = Engine::MainWindow;
+ if (win)
+ return win->ClientToScreen(win->GetMousePosition());
+ return Float2::Minimum;
+}
+
+void PlatformBase::SetMousePosition(const Float2& position)
+{
+ const Window* win = Engine::MainWindow;
+ if (win)
+ win->SetMousePosition(win->ScreenToClient(position));
+}
+
Float2 PlatformBase::GetVirtualDesktopSize()
{
return Platform::GetVirtualDesktopBounds().Size;
diff --git a/Source/Engine/Platform/Base/PlatformBase.h b/Source/Engine/Platform/Base/PlatformBase.h
index e7800c94d..7b06fa3d0 100644
--- a/Source/Engine/Platform/Base/PlatformBase.h
+++ b/Source/Engine/Platform/Base/PlatformBase.h
@@ -660,6 +660,18 @@ public:
public:
+ ///
+ /// Gets the mouse cursor position in screen-space coordinates.
+ ///
+ /// Mouse cursor coordinates.
+ API_PROPERTY() static Float2 GetMousePosition();
+
+ ///
+ /// Sets the mouse cursor position in screen-space coordinates.
+ ///
+ /// Cursor position to set.
+ API_PROPERTY() static void SetMousePosition(const Float2& position);
+
///
/// Gets the origin position and size of the monitor at the given screen-space location.
///
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
index 8015162ec..92a26032d 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp
@@ -88,6 +88,7 @@ X11::Cursor Cursors[(int32)CursorType::MAX];
X11::XcursorImage* CursorsImg[(int32)CursorType::MAX];
Dictionary KeyNameMap;
Array KeyCodeMap;
+Delegate LinuxPlatform::xEventRecieved;
// Message boxes configuration
#define LINUX_DIALOG_MIN_BUTTON_WIDTH 64
@@ -2231,10 +2232,12 @@ void LinuxPlatform::Tick()
{
X11::XEvent event;
X11::XNextEvent(xDisplay, &event);
-
if (X11::XFilterEvent(&event, 0))
continue;
+ // External event handling
+ xEventRecieved(&event);
+
LinuxWindow* window;
switch (event.type)
{
@@ -2639,10 +2642,8 @@ Float2 LinuxPlatform::GetMousePosition()
{
if (!xDisplay)
return Float2::Zero;
-
- int32 x, y;
+ int32 x = 0, y = 0;
uint32 screenCount = (uint32)X11::XScreenCount(xDisplay);
-
for (uint32 i = 0; i < screenCount; i++)
{
X11::Window outRoot, outChild;
@@ -2651,7 +2652,6 @@ Float2 LinuxPlatform::GetMousePosition()
if (X11::XQueryPointer(xDisplay, X11::XRootWindow(xDisplay, i), &outRoot, &outChild, &x, &y, &childX, &childY, &mask))
break;
}
-
return Float2((float)x, (float)y);
}
diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h
index 89afe9292..e995ab42d 100644
--- a/Source/Engine/Platform/Linux/LinuxPlatform.h
+++ b/Source/Engine/Platform/Linux/LinuxPlatform.h
@@ -33,6 +33,11 @@ public:
/// The user home directory.
static const String& GetHomeDirectory();
+ ///
+ /// An event that is fired when an XEvent is received during platform tick.
+ ///
+ static Delegate xEventRecieved;
+
public:
// [UnixPlatform]
diff --git a/Source/Engine/Platform/UWP/UWPPlatform.cpp b/Source/Engine/Platform/UWP/UWPPlatform.cpp
index f7c883878..06a0d964d 100644
--- a/Source/Engine/Platform/UWP/UWPPlatform.cpp
+++ b/Source/Engine/Platform/UWP/UWPPlatform.cpp
@@ -158,27 +158,6 @@ void UWPPlatform::OpenUrl(const StringView& url)
// TODO: add support for OpenUrl on UWP
}
-Float2 UWPPlatform::GetMousePosition()
-{
- // Use the main window
- auto win = Engine::MainWindow;
- if (win)
- {
- return win->ClientToScreen(win->GetMousePosition());
- }
- return Float2::Minimum;
-}
-
-void UWPPlatform::SetMousePosition(const Float2& pos)
-{
- // Use the main window
- auto win = Engine::MainWindow;
- if (win)
- {
- win->SetMousePosition(win->ScreenToClient(pos));
- }
-}
-
Float2 UWPPlatform::GetDesktopSize()
{
Float2 result;
diff --git a/Source/Engine/Platform/UWP/UWPPlatform.h b/Source/Engine/Platform/UWP/UWPPlatform.h
index be114f419..9bc0d7afd 100644
--- a/Source/Engine/Platform/UWP/UWPPlatform.h
+++ b/Source/Engine/Platform/UWP/UWPPlatform.h
@@ -37,8 +37,6 @@ public:
static bool GetHasFocus();
static bool CanOpenUrl(const StringView& url);
static void OpenUrl(const StringView& url);
- static Float2 GetMousePosition();
- static void SetMousePosition(const Float2& pos);
static Rectangle GetMonitorBounds(const Float2& screenPos);
static Float2 GetDesktopSize();
static Rectangle GetVirtualDesktopBounds();
diff --git a/Source/Engine/Platform/Win32/Win32Platform.h b/Source/Engine/Platform/Win32/Win32Platform.h
index b50d31616..d3f9a5cdc 100644
--- a/Source/Engine/Platform/Win32/Win32Platform.h
+++ b/Source/Engine/Platform/Win32/Win32Platform.h
@@ -109,7 +109,7 @@ public:
static void CreateGuid(Guid& result);
static String GetMainDirectory();
static String GetExecutableFilePath();
- static struct Guid GetUniqueDeviceId();
+ static Guid GetUniqueDeviceId();
static String GetWorkingDirectory();
static bool SetWorkingDirectory(const String& path);
static void FreeLibrary(void* handle);
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
index e215cbaf0..7c8a5b060 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp
@@ -825,6 +825,18 @@ void WindowsPlatform::OpenUrl(const StringView& url)
::ShellExecuteW(nullptr, TEXT("open"), *url, nullptr, nullptr, SW_SHOWNORMAL);
}
+Float2 WindowsPlatform::GetMousePosition()
+{
+ POINT cursorPos;
+ GetCursorPos(&cursorPos);
+ return Float2((float)cursorPos.x, (float)cursorPos.y);
+}
+
+void WindowsPlatform::SetMousePosition(const Float2& pos)
+{
+ ::SetCursorPos((int)pos.X, (int)pos.Y);
+}
+
struct GetMonitorBoundsData
{
Float2 Pos;
diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.h b/Source/Engine/Platform/Windows/WindowsPlatform.h
index 7441d0812..a144e09e5 100644
--- a/Source/Engine/Platform/Windows/WindowsPlatform.h
+++ b/Source/Engine/Platform/Windows/WindowsPlatform.h
@@ -71,6 +71,8 @@ public:
static bool GetHasFocus();
static bool CanOpenUrl(const StringView& url);
static void OpenUrl(const StringView& url);
+ static Float2 GetMousePosition();
+ static void SetMousePosition(const Float2& pos);
static Rectangle GetMonitorBounds(const Float2& screenPos);
static Float2 GetDesktopSize();
static Rectangle GetVirtualDesktopBounds();
diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp
index d67f8d123..7e1d3d902 100644
--- a/Source/Engine/Utilities/Screenshot.cpp
+++ b/Source/Engine/Utilities/Screenshot.cpp
@@ -10,7 +10,6 @@
#include "Engine/Graphics/GPUResourceProperty.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/GPUSwapChain.h"
-#include "Engine/Engine/Engine.h"
#include "Engine/Threading/ThreadPoolTask.h"
#include "Engine/Engine/Globals.h"
#if COMPILE_WITH_TEXTURE_TOOL