Merge branch 'color-picker' of https://github.com/Menotdan/FlaxEngine into Menotdan-color-picker

This commit is contained in:
Wojtek Figat
2023-05-28 13:38:37 +02:00
10 changed files with 430 additions and 3 deletions

View File

@@ -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;
/// <summary>
/// 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;
}
/// <inheritdoc />
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (_activeEyedropper)
{
UpdateEyedrop();
}
}
/// <inheritdoc />
public override void Draw()
{

View File

@@ -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\"");
/// <summary>
/// Platform-dependent screen utilties.
/// </summary>
API_CLASS(Static, Name = "ScreenUtilities", Tag = "NativeInvokeUseName")
class FLAXENGINE_API ScreenUtilities
{
public:
static struct FLAXENGINE_API ScriptingTypeInitializer TypeInitializer;
/// <summary>
/// Gets the pixel color at the specified coordinates.
/// </summary>
/// <param name="x">X Coordinate to read.</param>
/// <param name="y">Y Coordinate to read.</param>
/// <returns>Pixel color at the specified coordinates.</returns>
API_FUNCTION() static Color32 GetPixelAt(int32 x, int32 y);
/// <summary>
/// Gets the cursor position, in screen cooridnates.
/// </summary>
/// <returns>Cursor position, in screen coordinates.</returns>
API_FUNCTION() static Int2 GetScreenCursorPosition();
/// <summary>
/// Starts async color picking. Will return a color through ColorReturnCallback.
/// </summary
API_FUNCTION() static void PickColor();
/// <summary>
/// Called when PickColor() is finished.
/// </summary>
API_EVENT() static Delegate<Color32> PickColorDone;
};

View File

@@ -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<Color32> ScreenUtilities::PickColorDone;
void ScreenUtilities::PickColor()
{
ScreenUtilitiesLinux::BlockAndReadMouse();
}
#endif

View File

@@ -0,0 +1,46 @@
#if PLATFORM_MAC
#include <Cocoa/Cocoa.h>
#include <AppKit/AppKit.h>
#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<Color32> 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

View File

@@ -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 <Windows.h>
#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<Color32> ScreenUtilities::PickColorDone;
void ScreenUtilities::PickColor()
{
ScreenUtilitiesWindows::BlockAndReadMouse();
}
#endif

View File

@@ -1037,7 +1037,9 @@ namespace FlaxEditor.Utilities
/// <returns>The processed name path.</returns>
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);
}

View File

@@ -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 <sys/resource.h>
#include <sys/sysinfo.h>
#include <sys/time.h>
@@ -2217,6 +2217,8 @@ void LinuxPlatform::BeforeRun()
{
}
Delegate<void*> 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)
{

View File

@@ -33,6 +33,11 @@ public:
/// <returns>The user home directory.</returns>
static const String& GetHomeDirectory();
/// <summary>
/// An event that is fired when an XEvent is recieved by Flax.
/// </summary>
static Delegate<void*> xEventRecieved;
public:
// [UnixPlatform]

View File

@@ -0,0 +1,74 @@
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine.GUI
{
/// <summary>
/// Button with an icon.
/// </summary>
public class IconButton : Button
{
/// <summary>
/// The sprite rendered on the button.
/// </summary>
public SpriteHandle ButtonSprite { get; set; }
/// <summary>
/// Whether or not to hide the border of the button.
/// </summary>
public bool HideBorder = true;
/// <summary>
/// Initializes a new instance of the <see cref="IconButton"/> class.
/// </summary>
/// <param name="buttonSprite">The sprite used by the button.</param>
public IconButton(SpriteHandle buttonSprite)
: this(0, 0, buttonSprite)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IconButton"/> class.
/// </summary>
/// <param name="x">Position X coordinate</param>
/// <param name="y">Position Y coordinate</param>
/// <param name="buttonSprite">The sprite used by the button.</param>
/// <param name="width">Width</param>
/// <param name="height">Height</param>
/// <param name="hideBorder">Whether or not to hide the border.</param>
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;
}
/// <summary>
/// Initializes a new instance of the <see cref="IconButton"/> class.
/// </summary>
/// <param name="location">Position</param>
/// <param name="size">Size</param>
/// <param name="buttonSprite">The sprite used by the button.</param>
public IconButton(Float2 location, Float2 size, SpriteHandle buttonSprite)
: this(location.X, location.Y, buttonSprite, size.X, size.Y)
{
}
/// <summary>
/// Sets the colors of the button, taking into account the <see cref="HideBorder"/> field.>
/// </summary>
/// <param name="color">The color to use.</param>
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;
}
}
}

View File

@@ -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"