diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 8209b63cf..0c81ecc51 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -1,8 +1,10 @@ // Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. using FlaxEditor.GUI.Input; +using FlaxEditor.Windows; using FlaxEngine; using FlaxEngine.GUI; +using System; namespace FlaxEditor.GUI.Dialogs { @@ -25,6 +27,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 +37,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 +52,7 @@ namespace FlaxEditor.GUI.Dialogs private TextBox _cHex; private Button _cCancel; private Button _cOK; + private IconButton _cEyedropper; /// /// Gets the selected color. @@ -104,6 +109,7 @@ namespace FlaxEditor.GUI.Dialogs { _initialValue = initialValue; _useDynamicEditing = useDynamicEditing; + _activeEyedropper = false; _value = Color.Transparent; _onChanged = colorChanged; _onClosed = pickerClosed; @@ -192,10 +198,49 @@ namespace FlaxEditor.GUI.Dialogs }; _cOK.Clicked += OnSubmit; + // Eyedropper button + _cEyedropper = new IconButton(_cOK.X - EyedropperMargin, _cHex.Bottom + PickerMargin, Editor.Instance.Icons.Add64, hideBorder: false) + { + Parent = this, + }; + _cEyedropper.Clicked += OnEyedropStart; + _cEyedropper.Height = (_cValue.Bottom - _cEyedropper.Y) * 0.5f; + _cEyedropper.Width = _cEyedropper.Height; + _cEyedropper.X -= _cEyedropper.Width; + //_cEyedropper.SetColors(_cEyedropper.BackgroundColor); + // Set initial color SelectedColor = initialValue; } + private Color32 GetEyedropColor() + { + Int2 mousePosition = ScreenUtilities.GetScreenCursorPosition(); + Color32 pixelColor = ScreenUtilities.GetPixelAt(mousePosition.X, mousePosition.Y); + + return pixelColor; + } + + private void ColorPicked(Color32 colorPicked) + { + _activeEyedropper = false; + SelectedColor = colorPicked; + ScreenUtilities.PickColorDone -= ColorPicked; + } + + private void OnEyedropStart() + { + _activeEyedropper = true; + ScreenUtilities.PickColor(); + ScreenUtilities.PickColorDone += ColorPicked; + } + + private void UpdateEyedrop() + { + Color32 pixelColor = GetEyedropColor(); + SelectedColor = pixelColor; + } + private void OnRGBAChanged() { if (_disableEvents) @@ -221,6 +266,18 @@ namespace FlaxEditor.GUI.Dialogs SelectedColor = color; } + + /// + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + if (_activeEyedropper) + { + UpdateEyedrop(); + } + } + /// public override void Draw() { diff --git a/Source/Editor/Utilities/ScreenUtilities/ScreenUtilities.h b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilities.h new file mode 100644 index 000000000..01380e25b --- /dev/null +++ b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilities.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Core/Delegate.h" + +API_INJECT_CODE(cpp, "#include \"Editor/Utilities/ScreenUtilities/ScreenUtilities.h\""); + +/// +/// Platform-dependent screen utilties. +/// +API_CLASS(Static, Name = "ScreenUtilities", Tag = "NativeInvokeUseName") +class FLAXENGINE_API ScreenUtilities +{ +public: + static struct FLAXENGINE_API ScriptingTypeInitializer TypeInitializer; + + /// + /// Gets the pixel color at the specified coordinates. + /// + /// X Coordinate to read. + /// Y Coordinate to read. + /// Pixel color at the specified coordinates. + API_FUNCTION() static Color32 GetPixelAt(int32 x, int32 y); + + /// + /// Gets the cursor position, in screen cooridnates. + /// + /// Cursor position, in screen coordinates. + API_FUNCTION() static Int2 GetScreenCursorPosition(); + + /// + /// Starts async color picking. Will return a color through ColorReturnCallback. + /// + /// Called when PickColor() is finished. + /// + API_EVENT() static Delegate PickColorDone; +}; diff --git a/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesLinux.cpp b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesLinux.cpp new file mode 100644 index 000000000..6dc2103d9 --- /dev/null +++ b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesLinux.cpp @@ -0,0 +1,104 @@ +#if PLATFORM_LINUX + +#include "ScreenUtilities.h" +#include "Engine/Core/Math/Color32.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Log.h" +#include "Engine/Platform/Linux/LinuxPlatform.h" + +#include "Engine/Platform/Linux/IncludeX11.h" + +Color32 ScreenUtilities::GetPixelAt(int32 x, int32 y) +{ + X11::XColor color; + Color32 outputColor; + + 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); + outputColor.R = color.red / 256; + outputColor.G = color.green / 256; + outputColor.B = color.blue / 256; + + return outputColor; +} + +Int2 ScreenUtilities::GetScreenCursorPosition() +{ + Int2 cursorPosition = { 0, 0 }; + X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay(); + X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display)); + + // Buffers (Some useful, some not.) + X11::Window rootWindowBuffer; + int rootX, rootY; + int winXBuffer, winYBuffer; + unsigned int maskBuffer; + + int gotPointer = X11::XQueryPointer(display, rootWindow, &rootWindowBuffer, &rootWindowBuffer, &rootX, &rootY, &winXBuffer, &winYBuffer, &maskBuffer); + if (!gotPointer) { + LOG(Error, "Failed to find the mouse pointer (Are you using multiple displays?)"); + return cursorPosition; + } + + cursorPosition.X = rootX; + cursorPosition.Y = rootY; + + return cursorPosition; +} + +class ScreenUtilitiesLinux +{ +public: + static void BlockAndReadMouse(); + static void xEventHandler(void* event); +}; + +void ScreenUtilitiesLinux::xEventHandler(void* eventPtr) { + X11::XEvent* event = (X11::XEvent*) eventPtr; + + X11::Display* display = (X11::Display*) LinuxPlatform::GetXDisplay(); + + if (event->type == ButtonPress) { + Int2 cursorPosition = ScreenUtilities::GetScreenCursorPosition(); + Color32 colorPicked = ScreenUtilities::GetPixelAt(cursorPosition.X, cursorPosition.Y); + + ScreenUtilities::PickColorDone(colorPicked); // Run the callback for picking colors being complete. + LinuxPlatform::xEventRecieved.Unbind(xEventHandler); // Unbind the event, we only want to handle one click event + X11::XUngrabPointer(display, CurrentTime); + } +} + +void ScreenUtilitiesLinux::BlockAndReadMouse() +{ + 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(xEventHandler); +} + +Delegate ScreenUtilities::PickColorDone; + +void ScreenUtilities::PickColor() +{ + ScreenUtilitiesLinux::BlockAndReadMouse(); +} + +#endif \ No newline at end of file diff --git a/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesMac.cpp b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesMac.cpp new file mode 100644 index 000000000..f774242ce --- /dev/null +++ b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesMac.cpp @@ -0,0 +1,46 @@ +#if PLATFORM_MAC +#include +#include + +#include "ScreenUtilities.h" +#include "Engine/Core/Math/Color32.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Log.h" + +Color32 ScreenUtilities::GetPixelAt(int32 x, int32 y) +{ + // Called from C# for live updates to the color. + + return { 0, 0, 0, 255 }; +} + +Int2 ScreenUtilities::GetScreenCursorPosition() +{ + // Called from C# for live updates to the color. + + return { 0, 0 }; +} + +class ScreenUtilitiesMac +{ +public: + static void BlockAndReadMouse(); +}; + +void ScreenUtilitiesMac::BlockAndReadMouse() +{ + // Maybe you don't need this if you go with NSColorSampler +} + +Delegate ScreenUtilities::PickColorDone; + +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. + // It also might just work to copy the Linux Impl since Mac uses X as well, right? +} + +#endif diff --git a/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesWindows.cpp b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesWindows.cpp new file mode 100644 index 000000000..5af695c4d --- /dev/null +++ b/Source/Editor/Utilities/ScreenUtilities/ScreenUtilitiesWindows.cpp @@ -0,0 +1,94 @@ +#if PLATFORM_WINDOWS + +#include "ScreenUtilities.h" +#include "Engine/Core/Math/Color32.h" +#include "Engine/Core/Math/Vector2.h" +#include "Engine/Core/Delegate.h" +#include "Engine/Core/Log.h" +#include "Engine/Scripting/ManagedCLR/MCore.h" + +#include + + +#pragma comment(lib, "Gdi32.lib") + + +Color32 ScreenUtilities::GetPixelAt(int32 x, int32 y) +{ + HDC deviceContext = GetDC(NULL); + COLORREF color = GetPixel(deviceContext, x, y); + ReleaseDC(NULL, deviceContext); + + Color32 returnColor = { GetRValue(color), GetGValue(color), GetBValue(color), 255 }; + return returnColor; +} + +Int2 ScreenUtilities::GetScreenCursorPosition() +{ + POINT cursorPos; + GetCursorPos(&cursorPos); + + Int2 returnCursorPos = { cursorPos.x, cursorPos.y }; + return returnCursorPos; +} + +class ScreenUtilitiesWindows +{ +public: + static void PickSelected(); + static void BlockAndReadMouse(); +}; + +void ScreenUtilitiesWindows::PickSelected() { + Int2 cursorPos = ScreenUtilities::GetScreenCursorPosition(); + Color32 colorPicked = ScreenUtilities::GetPixelAt(cursorPos.X, cursorPos.Y); + + // Push event with the picked color. + ScreenUtilities::PickColorDone(colorPicked); +} + +static HHOOK _mouseCallbackHook; +LRESULT CALLBACK ScreenUtilsMouseCallback( + _In_ int nCode, + _In_ WPARAM wParam, + _In_ LPARAM lParam +) +{ + if (wParam != WM_LBUTTONDOWN) { // Return as early as possible. + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + + if (nCode < 0) { + return CallNextHookEx(NULL, nCode, wParam, lParam); + } + + + if (nCode >= 0 && wParam == WM_LBUTTONDOWN) { // Now try to run our code. + UnhookWindowsHookEx(_mouseCallbackHook); + + ScreenUtilitiesWindows::PickSelected(); + return 1; + } + + return CallNextHookEx(NULL, nCode, wParam, lParam); +} + +void ScreenUtilitiesWindows::BlockAndReadMouse() +{ + _mouseCallbackHook = SetWindowsHookEx(WH_MOUSE_LL, ScreenUtilsMouseCallback, NULL, NULL); + if (_mouseCallbackHook == NULL) + { + LOG(Warning, "Failed to set mouse hook."); + LOG(Warning, "Error: {0}", GetLastError()); + } +} + +Delegate ScreenUtilities::PickColorDone; + +void ScreenUtilities::PickColor() +{ + ScreenUtilitiesWindows::BlockAndReadMouse(); +} + + +#endif diff --git a/Source/Editor/Utilities/Utils.cs b/Source/Editor/Utilities/Utils.cs index 5f836e069..acc0cf605 100644 --- a/Source/Editor/Utilities/Utils.cs +++ b/Source/Editor/Utilities/Utils.cs @@ -1037,7 +1037,9 @@ namespace FlaxEditor.Utilities /// The processed name path. public static string GetAssetNamePath(string path) { - path = GetAssetNamePathWithExt(path); + var projectFolder = Globals.ProjectFolder; + if (path.StartsWith(projectFolder)) + path = path.Substring(projectFolder.Length + 1); return StringUtils.GetPathWithoutExtension(path); } diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.cpp b/Source/Engine/Platform/Linux/LinuxPlatform.cpp index 8015162ec..631b8d017 100644 --- a/Source/Engine/Platform/Linux/LinuxPlatform.cpp +++ b/Source/Engine/Platform/Linux/LinuxPlatform.cpp @@ -5,6 +5,7 @@ #include "LinuxPlatform.h" #include "LinuxWindow.h" #include "LinuxInput.h" +#include "IncludeX11.h" #include "Engine/Core/Log.h" #include "Engine/Core/Types/Guid.h" #include "Engine/Core/Types/String.h" @@ -30,7 +31,6 @@ #include "Engine/Input/Input.h" #include "Engine/Input/Mouse.h" #include "Engine/Input/Keyboard.h" -#include "IncludeX11.h" #include #include #include @@ -2217,6 +2217,8 @@ void LinuxPlatform::BeforeRun() { } +Delegate LinuxPlatform::xEventRecieved; + void LinuxPlatform::Tick() { UnixPlatform::Tick(); @@ -2234,7 +2236,9 @@ void LinuxPlatform::Tick() if (X11::XFilterEvent(&event, 0)) continue; - + + xEventRecieved(&event); // Fire this event, since we recieved an event. + LinuxWindow* window; switch (event.type) { diff --git a/Source/Engine/Platform/Linux/LinuxPlatform.h b/Source/Engine/Platform/Linux/LinuxPlatform.h index 89afe9292..486674652 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 recieved by Flax. + /// + static Delegate xEventRecieved; + public: // [UnixPlatform] diff --git a/Source/Engine/UI/GUI/Common/IconButton.cs b/Source/Engine/UI/GUI/Common/IconButton.cs new file mode 100644 index 000000000..e2652cd9f --- /dev/null +++ b/Source/Engine/UI/GUI/Common/IconButton.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. + +using System; + +namespace FlaxEngine.GUI +{ + /// + /// Button with an icon. + /// + public class IconButton : Button + { + /// + /// The sprite rendered on the button. + /// + public SpriteHandle ButtonSprite { get; set; } + + /// + /// Whether or not to hide the border of the button. + /// + public bool HideBorder = true; + + /// + /// Initializes a new instance of the class. + /// + /// The sprite used by the button. + public IconButton(SpriteHandle buttonSprite) + : this(0, 0, buttonSprite) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Position X coordinate + /// Position Y coordinate + /// The sprite used by the button. + /// Width + /// Height + /// Whether or not to hide the border. + public IconButton(float x, float y, SpriteHandle buttonSprite, float width = 120, float height = DefaultHeight, bool hideBorder = true) + : base(x, y, width, height) + { + ButtonSprite = buttonSprite; + BackgroundBrush = new SpriteBrush(ButtonSprite); + HideBorder = hideBorder; + } + + /// + /// Initializes a new instance of the class. + /// + /// Position + /// Size + /// The sprite used by the button. + public IconButton(Float2 location, Float2 size, SpriteHandle buttonSprite) + : this(location.X, location.Y, buttonSprite, size.X, size.Y) + { + } + + /// + /// Sets the colors of the button, taking into account the field.> + /// + /// The color to use. + public override void SetColors(Color color) + { + BackgroundColor = color; + BackgroundColorSelected = color.RGBMultiplied(0.8f); + BackgroundColorHighlighted = color.RGBMultiplied(1.2f); + + BorderColor = HideBorder ? Color.Transparent : color.RGBMultiplied(0.5f); + BorderColorSelected = BorderColor; + BorderColorHighlighted = BorderColor; + } + } +} diff --git a/Source/Engine/Utilities/Screenshot.cpp b/Source/Engine/Utilities/Screenshot.cpp index d67f8d123..fbde8043b 100644 --- a/Source/Engine/Utilities/Screenshot.cpp +++ b/Source/Engine/Utilities/Screenshot.cpp @@ -3,6 +3,7 @@ #include "Screenshot.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Math.h" +#include "Engine/Core/Math/Color32.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Platform/FileSystem.h" #include "Engine/Graphics/Textures/TextureData.h"