From 65423aa813303d2465b192320375c8079898ec4e Mon Sep 17 00:00:00 2001 From: Lars Brubaker Date: Fri, 17 Nov 2023 14:20:55 -0800 Subject: [PATCH] Adding clicking on color in windows for picker --- .../PartPreviewWindow/ItemColorButton.cs | 59 +++--- .../PartPreviewWindow/SelectedObjectPanel.cs | 6 + .../WindowsColorOnMouseDown.cs | 173 ++++++++++++++++++ 3 files changed, 212 insertions(+), 26 deletions(-) create mode 100644 MatterControlLib/PartPreviewWindow/WindowsColorOnMouseDown.cs diff --git a/MatterControlLib/PartPreviewWindow/ItemColorButton.cs b/MatterControlLib/PartPreviewWindow/ItemColorButton.cs index d14a3d1b8..94c3ef564 100644 --- a/MatterControlLib/PartPreviewWindow/ItemColorButton.cs +++ b/MatterControlLib/PartPreviewWindow/ItemColorButton.cs @@ -1,5 +1,5 @@ /* -Copyright (c) 2018, John Lewin +Copyright (c) 2023, John Lewin, Lars Brubaker All rights reserved. Redistribution and use in source and binary forms, with or without @@ -29,7 +29,6 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Linq; -using System.Reflection.Metadata.Ecma335; using MatterHackers.Agg; using MatterHackers.Agg.Image; using MatterHackers.Agg.Platform; @@ -42,7 +41,7 @@ using MatterHackers.MatterControl.SlicerConfiguration; namespace MatterHackers.MatterControl.PartPreviewWindow { - public class ItemColorButton : PopupButton + public class ItemColorButton : PopupButton { private ColorButton colorButton; private GuiWidget popupContent; @@ -259,19 +258,27 @@ namespace MatterHackers.MatterControl.PartPreviewWindow picker.SetColorWithoutChangeEvent(Color.White); }; - if (getPickedColor != null) - { - var selectButton = rightContent.AddChild(new ThemedTextIconButton("Select".Localize(), StaticData.Instance.LoadIcon("eye_dropper.png", 16, 16).GrayToColor(theme.TextColor), theme) - { - Margin = 0, - HAnchor = HAnchor.Fit | HAnchor.Left, - VAnchor = VAnchor.Absolute - }); + var selectButton = rightContent.AddChild(new ThemedTextIconButton("Select".Localize(), StaticData.Instance.LoadIcon("eye_dropper.png", 16, 16).GrayToColor(theme.TextColor), theme) + { + Margin = 0, + HAnchor = HAnchor.Fit | HAnchor.Left, + VAnchor = VAnchor.Absolute + }); - selectButton.Click += (s, e) => + // If there is a custom function to get a color, use it. + if (getPickedColor == null) + { + // get a color picker for all of windows + getPickedColor = new WindowsColorOnMouseDown().GetColor; + } + + selectButton.Click += (s, e) => + { + getPickedColor((color) => { - getPickedColor((color) => + if (color.Alpha0To255 > 0) { + // only run this code if we did get a color (the timeout will result in no color) update?.Invoke(color); if (transparent?.Checked == true) { @@ -281,20 +288,20 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { picker.SelectedColor = color; } - }); - }; - - if (selectButton.Width < resetButton.Width) - { - selectButton.HAnchor = HAnchor.Stretch; - } - else - { - resetButton.HAnchor = HAnchor.Stretch; - } - } + } + }); + }; - return content; + if (selectButton.Width < resetButton.Width) + { + selectButton.HAnchor = HAnchor.Stretch; + } + else + { + resetButton.HAnchor = HAnchor.Stretch; + } + + return content; } public override void OnLoad(EventArgs args) diff --git a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs index 04a194bb1..0bac26622 100644 --- a/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs +++ b/MatterControlLib/PartPreviewWindow/SelectedObjectPanel.cs @@ -289,6 +289,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow progress?.Invoke((UiThread.CurrentTimerMs - time) / 1000.0 / durationSeconds, null); } + // If the reason we exited the loop was due to a cancellation request, return no color + if (cancellationToken.IsCancellationRequested) + { + setColor?.Invoke(Color.Transparent); + } + scene.SelectionChanged -= SelectionChanged; return Task.CompletedTask; }); diff --git a/MatterControlLib/PartPreviewWindow/WindowsColorOnMouseDown.cs b/MatterControlLib/PartPreviewWindow/WindowsColorOnMouseDown.cs new file mode 100644 index 000000000..dc435f80d --- /dev/null +++ b/MatterControlLib/PartPreviewWindow/WindowsColorOnMouseDown.cs @@ -0,0 +1,173 @@ +/* +Copyright (c) 2023, Lars Brubaker +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using MatterHackers.Agg; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; + +namespace MatterHackers.MatterControl.PartPreviewWindow +{ + public class WindowsColorOnMouseDown + { + [DllImport("user32.dll")] + public static extern bool GetCursorPos(out POINT lpPoint); + + [DllImport("gdi32.dll")] + public static extern uint GetPixel(IntPtr hDC, int nXPos, int nYPos); + + [DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + } + + [DllImport("user32.dll")] + static extern IntPtr SetWindowsHookEx(int idHook, MouseProc callback, IntPtr hInstance, uint threadId); + + [DllImport("user32.dll")] + static extern bool UnhookWindowsHookEx(IntPtr hInstance); + + [DllImport("user32.dll")] + static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, int wParam, IntPtr lParam); + + [DllImport("kernel32.dll")] + static extern IntPtr LoadLibrary(string lpFileName); + + private delegate IntPtr MouseProc(int nCode, int wParam, IntPtr lParam); + + private const int WH_MOUSE_LL = 14; + private static MouseProc mouseProc; + private static IntPtr hookId = IntPtr.Zero; + + public WindowsColorOnMouseDown() + { + } + + private static IntPtr MouseHookCallback(int nCode, int wParam, IntPtr lParam) + { + if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam) + { + return (IntPtr)1; // Indicates that we handle the message + } + + return CallNextHookEx(hookId, nCode, wParam, lParam); + } + + private enum MouseMessages + { + WM_LBUTTONDOWN = 0x0201 + } + + public void GetColor(Action setColor) + { + setColor?.Invoke(WaitForClick()); + } + + public static void SetHook() + { + mouseProc = MouseHookCallback; + using (Process curProcess = Process.GetCurrentProcess()) + using (ProcessModule curModule = curProcess.MainModule) + { + hookId = SetWindowsHookEx(WH_MOUSE_LL, mouseProc, + LoadLibrary("user32"), 0); + } + } + + public static void ReleaseHook() + { + UnhookWindowsHookEx(hookId); + } + + public Agg.Color WaitForClick() + { + var clickWaitHandle = new AutoResetEvent(false); + Timer timeoutTimer = null; + + MouseProc clickProc = (nCode, wParam, lParam) => + { + if (nCode >= 0 && MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam) + { + timeoutTimer?.Dispose(); + timeoutTimer = null; + clickWaitHandle.Set(); + return (IntPtr)1; // Stop further processing + } + return CallNextHookEx(hookId, nCode, wParam, lParam); + }; + + using (Process curProcess = Process.GetCurrentProcess()) + using (ProcessModule curModule = curProcess.MainModule) + { + hookId = SetWindowsHookEx(WH_MOUSE_LL, clickProc, LoadLibrary("user32"), 0); + } + + // Set a timer to release the hook after 20 seconds + timeoutTimer = new Timer(_ => + { + ReleaseHook(); + clickWaitHandle.Set(); + }, null, 20000, Timeout.Infinite); + + clickWaitHandle.WaitOne(); + ReleaseHook(); + + // Check if the timer has been disposed, indicating a click occurred + if (timeoutTimer != null) + { + timeoutTimer.Dispose(); + return Agg.Color.Transparent; // or another default value to indicate timeout + } + + return GetColorAtCursor(); + } + + public Agg.Color GetColorAtCursor() + { + GetCursorPos(out POINT cursor); + IntPtr desktopDC = GetDC(IntPtr.Zero); + uint color = GetPixel(desktopDC, cursor.X, cursor.Y); + ReleaseDC(IntPtr.Zero, desktopDC); + + return new Agg.Color((int)(color & 0x000000FF), + (int)(color & 0x0000FF00) >> 8, + (int)(color & 0x00FF0000) >> 16); + } + } +}