├── Examples - Background image.ahk ├── Examples.ahk ├── Icons.dll ├── LICENSE ├── Lib ├── Color.ahk ├── ColorPicker.ahk ├── GuiButtonIcon.ahk ├── GuiCtrlTips.ahk └── LV_GridColor.ahk ├── Licenses ├── ColorPicker - LICENSE (YACS).txt ├── ColorPicker_ahk2 - LICENSE.txt ├── FontPicker_ahk2 - LICENSE.txt ├── GuiCtrlTips - LICENSE.txt └── JSON - LICENSE (ahk2_lib).txt ├── Notify Creator.ahk ├── Notify.ahk └── README.md /Examples - Background image.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | #SingleInstance 3 | 4 | #include Notify.ahk 5 | 6 | ;============================================================================================ 7 | ; Show(title, message, image, sound, callback, options) 8 | ; 9 | ; For more details on the BGIMG and BGIMGPOS settings, refer to the documentation in Notify.ahk and the tooltips in Notify Creator. 10 | ;============================================================================================ 11 | 12 | Notify.Show('DLL Icon as Background', 'This notification uses an icon from a DLL as its background.',,,, 13 | 'theme=Frost style=edge bgImg=' A_WinDir '\System32\msctf.dll|Icon13') 14 | 15 | ;============================================================================================ 16 | ; Using a Pixel Color as the Background Image Setting to Create a Sidebar 17 | ;============================================================================================ 18 | 19 | Notify.Show('Error', 'Something has gone wrong!',,,, 'theme=xdark gmb=25 bgImg=0xC61111 bgImgPos=bl wStretch h8') 20 | Notify.Show('Info', 'Some information to show.',,,, 'theme=ilight style=edge pad=,,5,5,10,5,10,0 bdr=0x41A5EE,3 bgImg=0x41A5EE bgImgPos=w50 hStretch') 21 | 22 | ;============================================================================================ 23 | ; Creating a Gradient Bitmap to Use as a Background Image 24 | ;============================================================================================ 25 | 26 | bgImgGradTitle := 'Gradient Background' 27 | bgImgGradMsg := 'This notification uses a gradient bitmap as its background.' 28 | 29 | Notify.Show(bgImgGradTitle, bgImgGradMsg,,,, 'theme=Cyberpunk maxW=400 bgImg=hBITMAP:*' CreateGradient(['0x383836', '0x000000']*)) 30 | Notify.Show(bgImgGradTitle, bgImgGradMsg,,,, 'theme=monokai style=edge maxW=325 bdr=0xA6E22E bgImg=hBITMAP:*' CreateGradient(['0x3f590b', '0x000000']*)) 31 | 32 | /********************************************** 33 | * @credits jNizM, just me, SKAN 34 | * @see {@link https://github.com/jNizM/ahk-scripts-v2/blob/main/src/Gui/CreateGradient.ahk GitHub} 35 | */ 36 | CreateGradient(Colors*) { 37 | static IMAGE_BITMAP := 0 38 | static LR_COPYDELETEORG := 0x00000008 39 | static LR_CREATEDIBSECTION := 0x00002000 40 | size := 500 41 | Bits := Buffer(Colors.Length * 2 * 4) 42 | Addr := Bits 43 | 44 | for each, Color in Colors 45 | Addr := NumPut("UInt", Color, "UInt", Color, Addr) 46 | 47 | hBITMAP := DllCall("CreateBitmap", "Int", 2, "Int", Colors.Length, "UInt", 1, "UInt", 32, "Ptr", 0, "Ptr") 48 | hBITMAP := DllCall("CopyImage", "Ptr", hBITMAP, "UInt", IMAGE_BITMAP, "Int", 0, "Int", 0, "UInt", LR_COPYDELETEORG | LR_CREATEDIBSECTION, "Ptr") 49 | DllCall("SetBitmapBits", "Ptr", hBITMAP, "UInt", Bits.Size, "Ptr", Bits) 50 | hBITMAP := DllCall("CopyImage", "Ptr", hBITMAP, "UInt", 0, "Int", size, "Int", size, "UInt", LR_COPYDELETEORG | LR_CREATEDIBSECTION, "Ptr") 51 | return hBITMAP 52 | } 53 | 54 | ;============================================================================================ 55 | ; Setting Click Callbacks for GUI and Images 56 | ; 57 | ; For more details on the callback parameter, refer to the documentation in Notify.ahk 58 | ;============================================================================================ 59 | 60 | strOpts := 'theme=matrix dg=5 tag=cbGUI' 61 | 62 | mapObjGUI := Notify.Show( 63 | 'GUI and Images with Callbacks.', 64 | 'This notification uses a background image as a close button. Clicking on it triggers a callback that destroys the GUI.', 65 | 'iconi',, 66 | [(*) => Notify.Show('GUI clicked',,,,, strOpts), 67 | (*) => Notify.Show('Image clicked',,,,, strOpts), 68 | (*) => BgImg_Click()], 69 | 'theme=Monaspace dur=0 maxW=400 bgImg=iconx bgImgPos=tr w20 h-1 ofstx-15 ofsty15' 70 | ) 71 | 72 | BgImg_Click(*) { 73 | Notify.Destroy(mapObjGUI['hwnd']) 74 | Notify.Show('Background image clicked',,,,, strOpts) 75 | } -------------------------------------------------------------------------------- /Examples.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | #SingleInstance 3 | 4 | #include Notify.ahk 5 | 6 | ;============================================================================================ 7 | ; Show(title, message, image, sound, callback, options) 8 | ;============================================================================================ 9 | 10 | Notify.Show('The quick brown fox jumps over the lazy dog.') 11 | Notify.Show('Alert!', 'You are being warned.',,,, 'theme=!') 12 | Notify.Show('Error', 'Something has gone wrong!',,,, 'theme=x dur=6 pos=bl') 13 | Notify.Show('Info', 'Some information to show.',, 'soundi',, 'theme=idark style=edge show=slideNorth hide=slideSouth@250') 14 | 15 | ;============================================================================================ 16 | ; 5 Ways to Destroy/Dismiss GUI 17 | ;============================================================================================ 18 | 19 | Notify.Show('Destroy the GUI by clicking on it before the set duration ends.', 'Click on GUI',,,, 'theme=matrix style=edge dur=15 pos=bl maxw=325') 20 | 21 | mNotifyGUI := Notify.Show('Destroy the GUI using a Handle.', 'Press Ctrl+F1', 'none',,, 'theme=!Dark dur=0 pos=tc mali=center') 22 | ^F1::Notify.Destroy(mNotifyGUI['hwnd']) 23 | 24 | Notify.Show('Destroy the GUI with the TAG option. This destroys every GUI with the specified tag across all scripts.', 'Press Ctrl+F2',,,, 'theme=synthwave dur=0 pos=ct style=edge tali=center mali=center maxw=375 tag=myTAG') 25 | ^F2::Notify.Destroy('myTAG') 26 | 27 | Notify.Show(strTitleDGTAG := 'Destroy the GUI with DG and TAG options. This destroys all GUIs with the tag before creating a new one.', 'Press Ctrl+F3',,,, 'theme=iDark dur=0 mali=center maxw=325 tag=thisTag') 28 | ^F3::Notify.Show(strTitleDGTAG, 'Press Ctrl+F3',,,, 'theme=iDark mali=center maxw=325 dg=5 tag=thisTag') 29 | 30 | ; Assign a hotkey to destroy GUIs one by one, starting with the oldest. 31 | HotIfWinExist('NotifyGUI_0 ahk_class AutoHotkeyGUI') 32 | Hotkey('Esc', (*) => Notify.Destroy()) 33 | HotIfWinExist() 34 | 35 | ;============================================================================================ 36 | ; Change the icon and text upon left-clicking the GUI using a callback. 37 | ; Limitation: The GUI doesn’t automatically resize when text is updated. 38 | ;============================================================================================ 39 | 40 | mNotifyGUI_CB := Notify.Show('Title', 'Click to change the icon and text using a callback.', 41 | A_WinDir '\system32\imageres.dll|Icon5',, NotifyGUICallback, 'theme=Dracula pos=tl dgb=1 dur=0 dgc=0') 42 | 43 | NotifyGUICallback(*) 44 | { 45 | mNotifyGUI_CB['pic'].Value := A_WinDir '\system32\user32.dll' 46 | mNotifyGUI_CB['title'].Value := 'Title changed' 47 | SetTimer((*) => _UpdateNotifyGUI('msg', 'Message changed'), -2000) 48 | SetTimer((*) => Notify.Destroy(mNotifyGUI_CB['hwnd'], 1), -4000) 49 | } 50 | 51 | _UpdateNotifyGUI(ctrlName, value) { 52 | try mNotifyGUI_CB[ctrlName].Value := value 53 | } 54 | 55 | ;============================================================================================ 56 | ; Progress Bar 57 | ;============================================================================================ 58 | 59 | mNotifyGUI_Prog := Notify.Show('Progress Bar Example', '0%',,,, 'theme=Solarized Dark style=edge prog=w325 mali=right dgb=1 dur=0 dgc=0') 60 | 61 | SetTimer((*) => UpdateNotifyGUI('prog', 50), -2000) 62 | SetTimer((*) => UpdateNotifyGUI('msg', '50%'), -2000) 63 | SetTimer((*) => UpdateNotifyGUI('prog', 100), -4000) 64 | SetTimer((*) => UpdateNotifyGUI('msg', 'Finished!'), -4000) 65 | SetTimer((*) => Notify.Destroy(mNotifyGUI_Prog['hwnd'], 1), -6000) 66 | 67 | UpdateNotifyGUI(ctrlName, value) { 68 | try mNotifyGUI_Prog[ctrlName].Value := value 69 | } 70 | 71 | ;============================================================================================ 72 | ; Set default theme 73 | ;============================================================================================ 74 | 75 | Notify.SetDefaultTheme('Cyberpunk') 76 | Notify.Show('Notify Title', 'Notify message with Cyberpunk theme.',,,, 'pos=ct style=edge bdr=default tali=center') 77 | Notify.Show('Cyberpunk', 78 | 'Cyberpunk is a subgenre of science fiction in a dystopian futuristic setting that tends to focus on a combination of low-life and high tech.',,,, 79 | 'pos=ct mali=center tali=center maxw=325' 80 | ) 81 | 82 | Notify.SetDefaultTheme('Monokai') 83 | Notify.Show('Notify Title', 'Notify message with Monokai theme.',,,, 'pos=tl style=edge bdr=default') 84 | Notify.Show('Monokai', 85 | 'Monokai is a popular theme for coding environments, featuring a dark background with vibrant, neon colors for enhanced readability.',,,, 86 | 'pos=tl maxw=325' 87 | ) 88 | 89 | ;============================================================================================ 90 | ; Lock keys indicators 91 | ;============================================================================================ 92 | 93 | ~*NumLock:: 94 | ~*ScrollLock:: 95 | ~*Insert:: 96 | { 97 | Sleep(10) 98 | thisHotkey := SubStr(A_ThisHotkey, 3) 99 | Notify.Show(thisHotkey ' ' (GetKeyState(thisHotkey, 'T') ? 'ON' : 'OFF'),,,,, 'theme=synthwave style=edge pos=bl dur=3 ts=35 show=none hide=none dg=5 tag=' thisHotkey) 100 | } 101 | 102 | ~*CapsLock:: 103 | { 104 | Sleep(10) 105 | thisHotkey := SubStr(A_ThisHotkey, 3) 106 | Notify.Destroy(thisHotkey, 1) 107 | 108 | if GetKeyState(thisHotkey, 'T') 109 | Notify.Show(thisHotkey ' ON',,,,, 'theme=matrix pos=bl ts=35 dgb=1 dur=0 dgc=0 tag=' thisHotkey) 110 | } 111 | -------------------------------------------------------------------------------- /Icons.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XMCQCX/NotifyClass-NotifyCreator/a66df2f5b163569cd5e97c651a6bbc6dee13bd18/Icons.dll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 XMCQCX 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Lib/Color.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotKey v2.0 2 | 3 | /** 4 | * Color.ahk 5 | * 6 | * @version 1.0 7 | * @author Komrad Toast (komrad.toast@hotmail.com) 8 | * @see https://www.autohotkey.com/boards/viewtopic.php?f=83&t=132433 9 | * @license 10 | * Copyright (c) 2024 Tyler J. Colby (Komrad Toast) 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 13 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 14 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 15 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | **/ 24 | 25 | /** 26 | * Color class. Stores a decimal RGB color representation. 27 | * 28 | * Has methods to convert to / from other formats (Hex, HSL, HWB, CMYK, NCol). 29 | * ```ahk2 30 | * Color("#27D") ; #RGB 31 | * Color("#27DF") ; #RGBA 32 | * Color("#2277DD") ; #RRGGBB 33 | * Color("#2277DDFF") ; #RRGGBBAA 34 | * Color(22, 77, 221) ; RGB 35 | * Color(22, 77, 221, 255) ; RGBA 36 | * ``` 37 | */ 38 | class Color 39 | { 40 | /** @property {String} HexFormat The hexadecimal color code format for `Color.ToHex().Full` (e.g. `#{R}{G}{B}{A}`). */ 41 | HexFormat 42 | { 43 | get 44 | { 45 | hex := this._hexFormat 46 | hex := RegExReplace(hex, "{4:02X}", "{A}") 47 | hex := RegExReplace(hex, "{1:02X}", "{R}") 48 | hex := RegExReplace(hex, "{2:02X}", "{G}") 49 | hex := RegExReplace(hex, "{3:02X}", "{B}") 50 | return hex 51 | } 52 | 53 | set 54 | { 55 | value := RegExReplace(value, "{A}", "{4:02X}") 56 | value := RegExReplace(value, "{R}", "{1:02X}") 57 | value := RegExReplace(value, "{G}", "{2:02X}") 58 | value := RegExReplace(value, "{B}", "{3:02X}") 59 | this._hexFormat := value 60 | } 61 | } 62 | 63 | /** @property {String} RGBFormat The RGB color code format for `Color.ToRGB().Full` (e.g. `RGBA({R},{G},{B},{A})`). */ 64 | RGBFormat 65 | { 66 | get 67 | { 68 | rgb := this._rgbFormat 69 | rgb := RegExReplace(rgb, "{1:d}", "{R}") 70 | rgb := RegExReplace(rgb, "{2:d}", "{G}") 71 | rgb := RegExReplace(rgb, "{3:d}", "{B}") 72 | rgb := RegExReplace(rgb, "{4:d}", "{A}") 73 | return rgb 74 | } 75 | set 76 | { 77 | value := RegExReplace(value, "{R}", "{1:d}") 78 | value := RegExReplace(value, "{G}", "{2:d}") 79 | value := RegExReplace(value, "{B}", "{3:d}") 80 | value := RegExReplace(value, "{A}", "{4:d}") 81 | this._rgbFormat := value 82 | this.Full := Format(this._rgbFormat, this.R, this.G, this.B, this.A) 83 | } 84 | } 85 | 86 | /** @property {String} HSLFormat The HSL color code format for `Color.ToHSL().Full` (e.g. `hsl({H},{S}%,{L}%)`). */ 87 | HSLFormat 88 | { 89 | get 90 | { 91 | hsl := this._hslFormat 92 | hsl := RegExReplace(hsl, "{1:d}", "{H}") 93 | hsl := RegExReplace(hsl, "{2:d}", "{S}") 94 | hsl := RegExReplace(hsl, "{3:d}", "{L}") 95 | hsl := RegExReplace(hsl, "{4:d}", "{A}") 96 | return hsl 97 | } 98 | 99 | set 100 | { 101 | value := RegExReplace(value, "{H}", "{1:d}") 102 | value := RegExReplace(value, "{S}", "{2:d}") 103 | value := RegExReplace(value, "{L}", "{3:d}") 104 | value := RegExReplace(value, "{A}", "{4:d}") 105 | this._hslFormat := value 106 | } 107 | } 108 | 109 | /** @property {String} HWBFormat The HWB color code format for `Color.ToHWB().Full` (e.g. `hwb({H},{W}%,{B}%)`). */ 110 | HWBFormat 111 | { 112 | get 113 | { 114 | hwb := this._hwbFormat 115 | hwb := RegExReplace(hwb, "{1:d}", "{H}") 116 | hwb := RegExReplace(hwb, "{2:d}", "{W}") 117 | hwb := RegExReplace(hwb, "{3:d}", "{B}") 118 | return hwb 119 | } 120 | 121 | set 122 | { 123 | value := RegExReplace(value, "{H}", "{1:d}") 124 | value := RegExReplace(value, "{W}", "{2:d}") 125 | value := RegExReplace(value, "{B}", "{3:d}") 126 | this._hwbFormat := value 127 | } 128 | } 129 | 130 | /** @property {String} CMYKFormat The CMYK color code format for `Color.ToCMYK().Full` (e.g. `cmyk({C}%,{M}%,{Y}%,{K}%)`). */ 131 | CMYKFormat 132 | { 133 | get 134 | { 135 | cmyk := this._cmykFormat 136 | cmyk := RegExReplace(cmyk, "{1:d}", "{C}") 137 | cmyk := RegExReplace(cmyk, "{2:d}", "{M}") 138 | cmyk := RegExReplace(cmyk, "{3:d}", "{Y}") 139 | cmyk := RegExReplace(cmyk, "{4:d}", "{K}") 140 | return cmyk 141 | } 142 | 143 | set 144 | { 145 | value := RegExReplace(value, "{C}", "{1:d}") 146 | value := RegExReplace(value, "{M}", "{2:d}") 147 | value := RegExReplace(value, "{Y}", "{3:d}") 148 | value := RegExReplace(value, "{K}", "{4:d}") 149 | this._cmykFormat := value 150 | } 151 | } 152 | 153 | /** @property {String} NColFormat The NCol color code format for `Color.ToNCol().Full` (e.g. `ncol({H},{W}%,{B}%)`). */ 154 | NColFormat 155 | { 156 | get 157 | { 158 | ncol := this._nColFormat 159 | ncol := RegExReplace(ncol, "{1:d}", "{H}") 160 | ncol := RegExReplace(ncol, "{2:d}", "{W}") 161 | ncol := RegExReplace(ncol, "{3:d}", "{B}") 162 | return ncol 163 | } 164 | 165 | set 166 | { 167 | value := RegExReplace(value, "{H}", "{1:s}") 168 | value := RegExReplace(value, "{W}", "{2:d}") 169 | value := RegExReplace(value, "{B}", "{3:d}") 170 | this._nColFormat := value 171 | } 172 | } 173 | 174 | ; Default Format Strings 175 | _hexFormat := "0x{1:02X}{2:02X}{3:02X}" 176 | _rgbFormat := "rgba({1:d}, {2:d}, {3:d}, {4:d})" 177 | _hslFormat := "hsl({1:d}, {2:d}%, {3:d}%)" 178 | _hwbFormat := "hwb({1:d} {2:d}% {3:d}%)" 179 | _cmykFormat := "cmyk({1:d}%, {2:d}%, {3:d}%, {4:d}%)" 180 | _nColFormat := "ncol({1:s}, {2:d}%, {3:d}%)" 181 | 182 | static Black => Color("Black") 183 | static Silver => Color("Silver") 184 | static Gray => Color("Gray") 185 | static White => Color("White") 186 | static Maroon => Color("Maroon") 187 | static Red => Color("Red") 188 | static Purple => Color("Purple") 189 | static Fuchsia => Color("Fuchsia") 190 | static Green => Color("Green") 191 | static Lime => Color("Lime") 192 | static Olive => Color("Olive") 193 | static Yellow => Color("Yellow") 194 | static Navy => Color("Navy") 195 | static Blue => Color("Blue") 196 | static Teal => Color("Teal") 197 | static Aqua => Color("Aqua") 198 | 199 | /** 200 | * Color constructor 201 | * @param colorArgs - Color arguments to initialize the color from. Can be RGB, RGBA, Hex (3, 4, 6, or 8 character), or Name. 202 | */ 203 | __New(colorArgs*) 204 | { 205 | this.A := 255 206 | 207 | colorNames := Map( 208 | "Black" , "000000", "Silver", "C0C0C0", "Gray" , "808080", "White" , "FFFFFF", 209 | "Maroon", "800000", "Red" , "FF0000", "Purple", "800080", "Fuchsia", "FF00FF", 210 | "Green" , "008000", "Lime" , "00FF00", "Olive" , "808000", "Yellow" , "FFFF00", 211 | "Navy" , "000080", "Blue" , "0000FF", "Teal" , "008080", "Aqua" , "00FFFF" 212 | ) 213 | 214 | 215 | if (colorArgs.Length == 0) 216 | { 217 | this.R := 0 218 | this.G := 0 219 | this.B := 0 220 | } 221 | else if (colorArgs.Length == 1) 222 | { 223 | if (colorNames.Has(colorArgs[1])) 224 | hex := colorNames[colorArgs[1]] 225 | else if (StrLen(colorArgs[1]) >= 3) 226 | hex := RegExReplace(colorArgs[1], "^#|^0x", "") 227 | else 228 | throw Error("Invalid Hex Color argument") 229 | 230 | if StrLen(hex) == 3 231 | { 232 | this.R := Integer("0x" . SubStr(hex, 1, 1)) 233 | this.G := Integer("0x" . SubStr(hex, 2, 1)) 234 | this.B := Integer("0x" . SubStr(hex, 3, 1)) 235 | } 236 | else if StrLen(hex) == 4 237 | { 238 | this.R := Integer("0x" . SubStr(hex, 2, 1)) 239 | this.G := Integer("0x" . SubStr(hex, 3, 1)) 240 | this.B := Integer("0x" . SubStr(hex, 4, 1)) 241 | this.A := Integer("0x" . SubStr(hex, 1, 1)) 242 | } 243 | else if StrLen(hex) == 6 244 | { 245 | this.R := Integer("0x" . SubStr(hex, 1, 2)) 246 | this.G := Integer("0x" . SubStr(hex, 3, 2)) 247 | this.B := Integer("0x" . SubStr(hex, 5, 2)) 248 | } 249 | else if StrLen(hex) == 8 250 | { 251 | this.R := Integer("0x" . SubStr(hex, 3, 2)) 252 | this.G := Integer("0x" . SubStr(hex, 5, 2)) 253 | this.B := Integer("0x" . SubStr(hex, 7, 2)) 254 | this.A := Integer("0x" . SubStr(hex, 1, 2)) 255 | } 256 | } 257 | else if (colorArgs.Length == 3) 258 | { 259 | this.R := Clamp(colorArgs[1], 0, 255) 260 | this.G := Clamp(colorArgs[2], 0, 255) 261 | this.B := Clamp(colorArgs[3], 0, 255) 262 | } 263 | else if (colorArgs.Length == 4) 264 | { 265 | this.R := Clamp(colorArgs[1], 0, 255) 266 | this.G := Clamp(colorArgs[2], 0, 255) 267 | this.B := Clamp(colorArgs[3], 0, 255) 268 | this.A := Clamp(colorArgs[4], 0, 255) 269 | } 270 | else 271 | { 272 | throw Error("Invalid color arguments") 273 | } 274 | 275 | this.Full := Format(this._rgbFormat, this.R, this.G, this.B, this.A) 276 | 277 | Clamp(val, low, high) => Min(Max(val, low), high) 278 | } 279 | 280 | /** 281 | * Converts the stored color to Hexadecimal representation. 282 | * @param {String} formatString The string used to format the output. 283 | * @returns {Object} `{R:(00-FF), G:(00-FF), B:(00-FF), A:(00-FF), Full:string}` 284 | */ 285 | ToHex(formatString := "") 286 | { 287 | if formatString 288 | { 289 | oldFormat := this.HexFormat 290 | this.HexFormat := formatString 291 | } 292 | 293 | full := Format(this._hexFormat, this.R, this.G, this.B, this.A) 294 | 295 | if formatString 296 | this.HexFormat := oldFormat 297 | 298 | return { 299 | R: Format("{:02X}", this.R), 300 | G: Format("{:02X}", this.G), 301 | B: Format("{:02X}", this.B), 302 | A: Format("{:02X}", this.A), 303 | Full: full 304 | } 305 | } 306 | 307 | /** 308 | * Converts the stored color to HSLA representation. 309 | * @param {String} formatString The string used to format the output. 310 | * @returns {Object} `{H:(0-360), S:(0-100), L:(0-100), A:(0-1), Full:string}` 311 | */ 312 | ToHSL(formatString := "") 313 | { 314 | if formatString 315 | { 316 | oldFormat := this.HSLFormat 317 | this.HSLFormat := formatString 318 | } 319 | 320 | r := this.R / 255 321 | g := this.G / 255 322 | b := this.B / 255 323 | a := this.A / 255 324 | 325 | cmax := Max(r, g, b) 326 | cmin := Min(r, g, b) 327 | delta := cmax - cmin 328 | 329 | l := (cmax + cmin) / 2 330 | 331 | if (delta == 0) 332 | { 333 | h := 0 334 | s := 0 335 | } 336 | else 337 | { 338 | s := delta / (1 - Abs(2 * l - 1)) 339 | 340 | if (cmax == r) 341 | h := 60 * Mod((g - b) / delta, 6) 342 | else if (cmax == g) 343 | h := 60 * ((b - r) / delta + 2) 344 | else 345 | h := 60 * ((r - g) / delta + 4) 346 | 347 | if (h < 0) 348 | h += 360 349 | } 350 | 351 | full := Format(this._hslFormat, Round(h), Round(s * 100), Round(l * 100)) 352 | 353 | if formatString 354 | this.HSLFormat := oldFormat 355 | 356 | return { 357 | H: Round(h), 358 | S: Round(s * 100), 359 | L: Round(l * 100), 360 | A: Round(a, 1), 361 | Full: full 362 | } 363 | } 364 | 365 | /** 366 | * Converts the stored color to HWB representation. 367 | * ```ahk2 368 | * color.ToHWB("HWB: {H}, {W}%, {B}%") 369 | * ``` 370 | * @param {String} formatString The string used to format the `Full` output. 371 | * @returns {Object} `{H:(0-360), W:(0-100), B:(0-100), Full:string}` 372 | */ 373 | ToHWB(formatString := "") 374 | { 375 | if formatString 376 | { 377 | oldFormat := this.HWBFormat 378 | this.HWBFormat := formatString 379 | } 380 | 381 | r := this.R / 255 382 | g := this.G / 255 383 | b := this.B / 255 384 | 385 | cmax := Max(r, g, b) 386 | cmin := Min(r, g, b) 387 | delta := cmax - cmin 388 | 389 | if (delta == 0) 390 | h := 0 391 | else if (cmax == r) 392 | h := 60 * Mod((g - b) / delta, 6) 393 | else if (cmax == g) 394 | h := 60 * ((b - r) / delta + 2) 395 | else 396 | h := 60 * ((r - g) / delta + 4) 397 | 398 | if (h < 0) 399 | h += 360 400 | 401 | w := cmin 402 | bl := 1 - cmax 403 | 404 | full := Format(this._hwbFormat, Round(h), Round(w * 100), Round(bl * 100)) 405 | 406 | if formatString 407 | this.HWBFormat := oldFormat 408 | 409 | return { 410 | H: Round(h), 411 | W: Round(w * 100), 412 | B: Round(bl * 100), 413 | Full: full 414 | } 415 | } 416 | 417 | /** 418 | * Converts the stored color to CMYK representation. 419 | * @param {String} formatString The string used to format the output. 420 | * @returns {Object} `{C:(0-100), M:(0-100), Y:(0-100), K:(0-100), Full:string}` 421 | */ 422 | ToCMYK(formatString := "") 423 | { 424 | if formatString 425 | { 426 | oldFormat := this.CMYKFormat 427 | this.CMYKFormat := formatString 428 | } 429 | 430 | r := this.R / 255 431 | g := this.G / 255 432 | b := this.B / 255 433 | 434 | k := 1 - Max(r, g, b) 435 | 436 | if (k == 1) 437 | { 438 | c := 0 439 | m := 0 440 | y := 0 441 | } 442 | else 443 | { 444 | c := (1 - r - k) / (1 - k) 445 | m := (1 - g - k) / (1 - k) 446 | y := (1 - b - k) / (1 - k) 447 | } 448 | 449 | full := Format(this._cmykFormat, Round(c * 100), Round(m * 100), Round(y * 100), Round(k * 100)) 450 | 451 | if formatString 452 | this.CMYKFormat := oldFormat 453 | 454 | return { 455 | C: Round(c * 100), 456 | M: Round(m * 100), 457 | Y: Round(y * 100), 458 | K: Round(k * 100), 459 | Full: full 460 | } 461 | } 462 | 463 | /** 464 | * Converts the stored color to NCol representation. 465 | * @param {String} formatString The string used to format the output. 466 | * @returns {Object} `{"H":string, "W":(0-100), "B":(0-100), "Full":string}` 467 | */ 468 | ToNCol(formatString := "") 469 | { 470 | if formatString 471 | { 472 | oldFormat := this.NColFormat 473 | this.NColFormat := formatString 474 | } 475 | 476 | hwb := this.ToHWB() 477 | h := hwb.H 478 | w := hwb.W 479 | b := hwb.B 480 | 481 | hueNames := ["R", "Y", "G", "C", "B", "M"] 482 | hueIndex := Floor(h / 60) 483 | huePercent := Round(Mod(h, 60) / 60 * 100) 484 | 485 | ncolHue := hueNames[Mod(hueIndex, 6) + 1] . huePercent 486 | 487 | full := Format(this._nColFormat, ncolHue, w, b) 488 | 489 | if formatString 490 | this.NColFormat := oldFormat 491 | 492 | return { 493 | H: ncolHue, 494 | W: w, 495 | B: b, 496 | Full: full 497 | } 498 | } 499 | 500 | /** 501 | * Generates a random color. 502 | * @returns {Color} A new, random color 503 | */ 504 | static Random() => Color(Random(255), Random(255), Random(255)) 505 | 506 | /** 507 | * Syntactic Sugar for the Color Constructor. Makes a new color using RGB or RGBA representation. 508 | * @returns {Color} 509 | */ 510 | static FromRGB(colorArgs*) ; Syntactic Sugar 511 | { 512 | if (colorArgs.Length == 3) 513 | return Color(colorArgs[1], colorArgs[2], colorArgs[3]) 514 | else if (colorArgs.Length == 4) 515 | return Color(colorArgs[1], colorArgs[2], colorArgs[3], colorArgs[4]) 516 | else 517 | throw Error("Invalid number of arguments passed to Color.FromRGB") 518 | } 519 | 520 | /** 521 | * Syntactic Sugar for the Color Constructor. Makes a new color using Hex RGB or RGBA representation. 522 | * @returns {Color} 523 | */ 524 | static FromHex(hex) ; Syntactic Sugar 525 | { 526 | if (StrLen(hex) >= 3) 527 | hex := RegExReplace(hex, "^#|^0x", "") 528 | else 529 | throw Error("Invalid Hex Color argument") 530 | 531 | if StrLen(hex) == 3 532 | { 533 | R := Integer("0x" . SubStr(hex, 1, 1)) 534 | G := Integer("0x" . SubStr(hex, 2, 1)) 535 | B := Integer("0x" . SubStr(hex, 3, 1)) 536 | A := 255 537 | } 538 | else if StrLen(hex) == 4 539 | { 540 | R := Integer("0x" . SubStr(hex, 1, 1)) 541 | G := Integer("0x" . SubStr(hex, 2, 1)) 542 | B := Integer("0x" . SubStr(hex, 3, 1)) 543 | A := Integer("0x" . SubStr(hex, 4, 1)) 544 | } 545 | else if StrLen(hex) == 6 546 | { 547 | R := Integer("0x" . SubStr(hex, 1, 2)) 548 | G := Integer("0x" . SubStr(hex, 3, 2)) 549 | B := Integer("0x" . SubStr(hex, 5, 2)) 550 | A := 255 551 | } 552 | else if StrLen(hex) == 8 553 | { 554 | R := Integer("0x" . SubStr(hex, 1, 2)) 555 | G := Integer("0x" . SubStr(hex, 3, 2)) 556 | B := Integer("0x" . SubStr(hex, 5, 2)) 557 | A := Integer("0x" . SubStr(hex, 7, 2)) 558 | } 559 | 560 | return Color(R, G, B, A) 561 | } 562 | 563 | /** 564 | * Creates a `Color` instance from HSL format. 565 | * @param {Integer} h Hue - `0-360` 566 | * @param {Integer} s Saturation - `0-100` 567 | * @param {Integer} l Lightness - `0-100` 568 | * @returns {Color} 569 | */ 570 | static FromHSL(h, s, l) 571 | { 572 | h := Mod(h, 360) / 360 573 | s := Clamp(s, 0, 100) / 100 574 | l := Clamp(l, 0, 100) / 100 575 | 576 | if (s == 0) 577 | { 578 | r := g := b := l 579 | } 580 | else 581 | { 582 | q := l < 0.5 ? l * (1 + s) : l + s - l * s 583 | p := 2 * l - q 584 | r := HueToRGB(p, q, h + 1/3) 585 | g := HueToRGB(p, q, h) 586 | b := HueToRGB(p, q, h - 1/3) 587 | } 588 | 589 | return Color(Round(r * 255), Round(g * 255), Round(b * 255)) 590 | 591 | Clamp(val, low, high) => Min(Max(val, low), high) 592 | 593 | HueToRGB(p, q, t) 594 | { 595 | if (t < 0) 596 | t += 1 597 | if (t > 1) 598 | t -= 1 599 | if (t < 1/6) 600 | return p + (q - p) * 6 * t 601 | if (t < 1/2) 602 | return q 603 | if (t < 2/3) 604 | return p + (q - p) * (2/3 - t) * 6 605 | return p 606 | } 607 | } 608 | 609 | /** 610 | * Creates a `Color` instance from HWB format. 611 | * @param {Integer} h Hue - `0-360` 612 | * @param {Integer} w Whiteness - `0-100` 613 | * @param {Integer} b Blackness - `0-100` 614 | * @returns {Color} 615 | */ 616 | static FromHWB(h, w, b) 617 | { 618 | h := Mod(h, 360) / 360 619 | w := Clamp(w, 0, 100) / 100 620 | b := Clamp(b, 0, 100) / 100 621 | 622 | if (w + b >= 1) 623 | { 624 | g := w / (w + b) 625 | return Color(Round(g * 255), Round(g * 255), Round(g * 255)) 626 | } 627 | 628 | f := 1 - w - b 629 | rgb := HueToRGB(h) 630 | 631 | r := Round((rgb.R * f + w) * 255) 632 | g := Round((rgb.G * f + w) * 255) 633 | b := Round((rgb.B * f + w) * 255) 634 | 635 | return Color(r, g, b) 636 | 637 | Clamp(val, low, high) => Min(Max(val, low), high) 638 | 639 | HueToRGB(h) 640 | { 641 | h *= 6 642 | x := 1 - Abs(Mod(h, 2) - 1) 643 | switch Floor(h) 644 | { 645 | case 0: return {R: 1, G: x, B: 0} 646 | case 1: return {R: x, G: 1, B: 0} 647 | case 2: return {R: 0, G: 1, B: x} 648 | case 3: return {R: 0, G: x, B: 1} 649 | case 4: return {R: x, G: 0, B: 1} 650 | case 5: return {R: 1, G: 0, B: x} 651 | } 652 | } 653 | } 654 | 655 | /** 656 | * Creates a `Color` instance from CMYK format. 657 | * @param {Integer} c Cyan - `0-100` 658 | * @param {Integer} m Magenta - `0-100` 659 | * @param {Integer} y Yellow - `0-100` 660 | * @param {Integer} k Key (Black) - `0-100` 661 | * @returns {Color} 662 | */ 663 | static FromCMYK(c, m, y, k) 664 | { 665 | c := Clamp(c, 0, 100) / 100 666 | m := Clamp(m, 0, 100) / 100 667 | y := Clamp(y, 0, 100) / 100 668 | k := Clamp(k, 0, 100) / 100 669 | 670 | r := Round((1 - c) * (1 - k) * 255) 671 | g := Round((1 - m) * (1 - k) * 255) 672 | b := Round((1 - y) * (1 - k) * 255) 673 | 674 | return Color(r, g, b) 675 | 676 | Clamp(val, low, high) => Min(Max(val, low), high) 677 | } 678 | 679 | /** 680 | * Creates a `Color` instance from NCol format. 681 | * @param {Integer} h Hue - `(R|Y|G|C|B|M)0-100` 682 | * @param {Integer} w Whiteness - `0-100` 683 | * @param {Integer} b Blackness - `0-100` 684 | * @returns {Color} 685 | */ 686 | static FromNCol(h, w, b) 687 | { 688 | hueNames := "RYGCBM" 689 | hueIndex := InStr(hueNames, SubStr(h, 1, 1)) - 1 690 | huePercent := Integer(SubStr(h, 2)) 691 | 692 | h := Mod(hueIndex * 60 + huePercent * 0.6, 360) 693 | w := Clamp(w, 0, 100) 694 | b := Clamp(b, 0, 100) 695 | 696 | return Color.FromHWB(h, w, b) 697 | 698 | Clamp(val, low, high) => Min(Max(val, low), high) 699 | } 700 | 701 | /** 702 | * Creates a new `Color` by calculating the average of two or more colors. 703 | * @param {Color[]} colors The colors to calculate the average of. 704 | * @returns {Color} 705 | */ 706 | static Average(colors*) 707 | { 708 | r := 0 709 | g := 0 710 | b := 0 711 | 712 | for _color in colors 713 | { 714 | r += _color.R 715 | g += _color.G 716 | b += _color.B 717 | } 718 | 719 | count := colors.Length 720 | return Color(Round(r / count), Round(g / count), Round(b / count)) 721 | } 722 | 723 | /** 724 | * Creates a new `Color` by multiplying two or more colors. 725 | * @param colors The colors to multiply. 726 | * @returns {Color} 727 | */ 728 | static Multiply(colors*) 729 | { 730 | r := 1 731 | g := 1 732 | b := 1 733 | 734 | for _color in colors 735 | { 736 | r *= _color.R / 255 737 | g *= _color.G / 255 738 | b *= _color.B / 255 739 | } 740 | 741 | return Color(Round(r * 255), Round(g * 255), Round(b * 255)) 742 | } 743 | 744 | /** 745 | * Inverts the current color and returns it as a new `Color` instance. 746 | * @returns {Color} 747 | */ 748 | Invert() => Color(255 - this.R, 255 - this.G, 255 - this.B, this.A) 749 | 750 | /** 751 | * Returns the grayscale representation of the current color. 752 | * @returns {Color} 753 | */ 754 | Grayscale() 755 | { 756 | luminance := 0.299 * this.R + 0.587 * this.G + 0.114 * this.B 757 | grayValue := Round(luminance) 758 | return Color(grayValue, grayValue, grayValue) 759 | } 760 | 761 | /** 762 | * Shifts the current color's hue by the specified amount of degrees. 763 | * @param {Integer} degrees The amount to shift the hue by - `0-360`. 764 | * @returns {Color} 765 | */ 766 | ShiftHue(degrees) 767 | { 768 | hsl := this.ToHSL() 769 | newHue := Mod(hsl.H + degrees, 360) 770 | return Color.FromHSL(newHue, hsl.S, hsl.L) 771 | } 772 | 773 | /** 774 | * Shifts the current color's saturation by the specified amount. 775 | * @param {Integer} degrees The amount to shift the saturation by - `0-100`. 776 | * @returns {Color} 777 | */ 778 | ShiftSaturation(amount) 779 | { 780 | hsl := this.ToHSL() 781 | newSaturation := Max(0, Min(100, hsl.S + amount)) 782 | return Color.FromHSL(hsl.H, newSaturation, hsl.L) 783 | } 784 | 785 | /** 786 | * Increases the current color's saturation by the specified amount. Negative values are made positive. 787 | * @param {Integer} percentage The amount to increase the saturation by - `0-100`. 788 | * @returns {Color} 789 | */ 790 | Saturate(percentage) => this.ShiftSaturation( Abs(percentage)) 791 | 792 | /** 793 | * Decreases the current color's saturation by the specified amount. Positive values are made negative. 794 | * @param {Integer} percentage The amount to decrease the saturation by - `0-100`. 795 | * @returns {Color} 796 | */ 797 | Desaturate(percentage) => this.ShiftSaturation(-Abs(percentage)) 798 | 799 | /** 800 | * Shifts the current color's lightness by the specified amount. 801 | * @param {Integer} degrees The amount to shift the lightness by - `0-100`. 802 | * @returns {Color} 803 | */ 804 | ShiftLightness(amount) 805 | { 806 | hsl := this.ToHSL() 807 | newLightness := Max(0, Min(100, hsl.L + amount)) 808 | return Color.FromHSL(hsl.H, hsl.S, newLightness) 809 | } 810 | 811 | /** 812 | * Increases the current color's lightness by the specified amount. Negative values are made positive. 813 | * @param {Integer} percentage The amount to increase the lightness by - `0-100`. 814 | * @returns {Color} 815 | */ 816 | Lighten(percentage) => this.ShiftLightness( Abs(percentage)) 817 | 818 | /** 819 | * Decreases the current color's lightness by the specified amount. Positive values are made negative. 820 | * @param {Integer} percentage The amount to decrease the lightness by - `0-100`. 821 | * @returns {Color} 822 | */ 823 | Darken(percentage) => this.ShiftLightness(-Abs(percentage)) 824 | 825 | /** 826 | * Shifts the current color's whiteness by the specified amount. 827 | * @param {Integer} degrees The amount to shift the whiteness by - `0-100`. 828 | * @returns {Color} 829 | */ 830 | ShiftWhiteness(amount) 831 | { 832 | hwb := this.ToHWB() 833 | newWhiteness := Max(0, Min(100, hwb.W + amount)) 834 | return Color.FromHWB(hwb.H, newWhiteness, hwb.B) 835 | } 836 | 837 | /** 838 | * Shifts the current color's blackness by the specified amount. 839 | * @param {Integer} degrees The amount to shift the blackness by - `0-100`. 840 | * @returns {Color} 841 | */ 842 | ShiftBlackness(amount) 843 | { 844 | hwb := this.ToHWB() 845 | newBlackness := Max(0, Min(100, hwb.B + amount)) 846 | return Color.FromHWB(hwb.H, hwb.W, newBlackness) 847 | } 848 | 849 | /** 850 | * Returns the complementary color to the current `Color` instance. 851 | * @returns {Color} 852 | */ 853 | Complement() => this.ShiftHue(180) 854 | 855 | /** 856 | * Returns the luminance (`0-1`) of the current `Color` instance. 857 | * @returns {Float} 858 | */ 859 | GetLuminance() 860 | { 861 | r := this.R / 255 862 | g := this.G / 255 863 | b := this.B / 255 864 | return 0.2126 * r + 0.7152 * g + 0.0722 * b 865 | } 866 | 867 | /** 868 | * Returns `True` if the current `Color` instance's Luminance is above `0.5`. 869 | * @returns {Boolean} 870 | */ 871 | IsLight() => this.GetLuminance() > 0.5 872 | 873 | /** 874 | * Returns `True` the current `Color` instance's Luminance is equal to or below `0.5`. 875 | * @returns {Boolean} 876 | */ 877 | IsDark() => this.GetLuminance() <= 0.5 878 | 879 | /** 880 | * Gets the contrast ratio between the current `Color` instance and another. 881 | * @param {Color} _color The `Color` instance to compare to. 882 | * @returns {Float} 883 | */ 884 | GetContrast(_color) 885 | { 886 | l1 := this.GetLuminance() 887 | l2 := _color.GetLuminance() 888 | 889 | if (l1 > l2) 890 | return (l1 + 0.05) / (l2 + 0.05) 891 | else 892 | return (l2 + 0.05) / (l1 + 0.05) 893 | } 894 | 895 | /** 896 | * Mixes the current `Color` instance with another and returns a new `Color`. 897 | * @param {Color} _color The color to mix with. 898 | * @param {Integer} weight The weight used to mix the two colors. 899 | * @returns {Color} 900 | */ 901 | Mix(_color, weight := 50) 902 | { 903 | w := weight / 100 904 | r := Round(this.R * (1 - w) + _color.R * w) 905 | g := Round(this.G * (1 - w) + _color.G * w) 906 | b := Round(this.B * (1 - w) + _color.B * w) 907 | 908 | return Color.FromRGB(r, g, b) 909 | } 910 | 911 | /** 912 | * Generates `count` colors that are analogous with (next to) the current color by `angle` degrees. 913 | * @param {Integer} angle The angle between the analogous colors. 914 | * @param {Integer} count Total colors to return (includes original). 915 | * @returns {Color[]} 916 | */ 917 | Analogous(angle := 30, count := 3) 918 | { 919 | hsl := this.ToHSL() 920 | colors := [] 921 | colors.Push(this) 922 | 923 | Loop count - 1 924 | { 925 | newH := Mod(hsl.H + angle * A_Index, 360) 926 | colors.Push(Color.FromHSL(newH, hsl.S, hsl.L)) 927 | } 928 | 929 | return colors 930 | } 931 | 932 | /** 933 | * Generates a Triadic color scheme from the current color. Traidic colors are offset from the current by `120°` and `240°`. 934 | * @returns {Color[3]} 935 | */ 936 | Triadic() 937 | { 938 | hsl := this.ToHSL() 939 | color2 := Color.FromHSL(Mod(hsl.H + 120, 360), hsl.S, hsl.L) 940 | color3 := Color.FromHSL(Mod(hsl.H + 240, 360), hsl.S, hsl.L) 941 | 942 | return [this, color2, color3] 943 | } 944 | 945 | /** 946 | * Produces a gradient from the current `Color` instance to another `Color` instance. 947 | * @param endColor The other color in the gradient. 948 | * @param {Integer} steps How many colors in-between should be made? 949 | * @returns {Color[]} 950 | */ 951 | Gradient(endColor, steps := 10) 952 | { 953 | gradient := [] 954 | gradient.Push(this) 955 | 956 | Loop steps - 1 957 | { 958 | weight := A_Index / steps 959 | gradient.Push(this.Mix(endColor, weight * 100)) 960 | } 961 | 962 | gradient.Push(endColor) 963 | 964 | return gradient 965 | } 966 | } 967 | 968 | /** 969 | #Requires AutoHotKey v2.0 970 | #Include ColorPicker.ahk 971 | #Include Color.ahk 972 | 973 | TestGui := Gui() 974 | TestGui.Title := "Color Class Test" 975 | TestGui.Opt("+Resize") 976 | 977 | startColor := Color.Random() 978 | endColor := Color.Random() 979 | 980 | CreateControls() 981 | UpdateControls() 982 | TestGui.Show() 983 | 984 | MsgBox(" 985 | ( 986 | This demonstration shows some of the Capabilities of the Color class. 987 | Every individual box is displaying an instance of the Color class. 988 | 989 | There are 149 boxes in total: 990 | Single color operations, 70 boxes arranged in a grid (5x14). 991 | There are also 20 boxes for Analogous colors 992 | 15 Boxes for Triadic colors 993 | And 54 boxes for the gradient. 994 | )") 995 | 996 | LaunchStartColorPicker(*) 997 | { 998 | picker := ColorPicker(False,, UpdateStartColor) 999 | picker.DefaultCaptureSize := 5 1000 | picker.DefaultZoomFactor := 12 1001 | picker.ViewMode := "crosshair" 1002 | picker.OnExit := ExitStartColor 1003 | picker.Start() 1004 | } 1005 | 1006 | LaunchEndColorPicker(*) 1007 | { 1008 | picker := ColorPicker(False,, UpdateEndColor) 1009 | picker.DefaultCaptureSize := 5 1010 | picker.DefaultZoomFactor := 12 1011 | picker.ViewMode := "crosshair" 1012 | picker.OnExit := ExitEndColor 1013 | picker.Start() 1014 | } 1015 | 1016 | RandomizeColors(*) 1017 | { 1018 | global startColor := Color.Random() 1019 | global endColor := Color.Random() 1020 | UpdateControls() 1021 | } 1022 | 1023 | UpdateStartColor(_color) 1024 | { 1025 | global startColor := _color 1026 | UpdateTopRow() 1027 | } 1028 | 1029 | UpdateEndColor(_color) 1030 | { 1031 | global endColor := _color 1032 | UpdateTopRow() 1033 | } 1034 | 1035 | ExitStartColor(_color) 1036 | { 1037 | global startColor := _color 1038 | UpdateControls() 1039 | } 1040 | 1041 | ExitEndColor(_color) 1042 | { 1043 | global endColor := _color 1044 | UpdateControls() 1045 | } 1046 | 1047 | CreateControls() 1048 | { 1049 | global controls := Map() 1050 | 1051 | columnLabels := ["Start Color", "End Color", "Mixed Color", "Average Color", "Multiplied Color"] 1052 | columnX := [120, 240, 360, 480, 600] 1053 | 1054 | TestGui.Add("Button", "x" columnX[1] " y10 w100", "Pick Start Color").OnEvent("Click", LaunchStartColorPicker) 1055 | TestGui.Add("Button", "x" columnX[2] " y10 w100", "Pick End Color").OnEvent("Click", LaunchEndColorPicker) 1056 | TestGui.Add("Button", "x" columnX[3] " y10 w100", "Randomize").OnEvent("Click", RandomizeColors) 1057 | 1058 | for i, label in columnLabels 1059 | { 1060 | TestGui.Add("Text", "x" columnX[i] " y50 w100 Center", label) 1061 | controls[label] := TestGui.Add("Progress", "x" columnX[i]+10 " y70 w20 h20") 1062 | controls[label "Text"] := TestGui.Add("Text", "x" columnX[i]+35 " y70 w80 h20 Left", "") 1063 | } 1064 | 1065 | colorProperties := ["Hex → RGB", "RGB → RGB", "HSL → RGB", "HWB → RGB", "CMYK → RGB", "NCol → RGB", "Invert", "Lighten", "Darken", "Saturate", "Desaturate", "Grayscale", "Complement"] 1066 | 1067 | for i, prop in colorProperties 1068 | { 1069 | y := 100 + (i - 1) * 30 1070 | TestGui.Add("Text", "x10 y" y " w100 Right", prop) 1071 | 1072 | for j, label in columnLabels 1073 | { 1074 | controls[label prop] := TestGui.Add("Progress", "x" columnX[j]+10 " y" y-2 " w20 h20") 1075 | controls[label prop "Text"] := TestGui.Add("Text", "x" columnX[j]+35 " y" y " w80 h20 Left", "") 1076 | } 1077 | } 1078 | 1079 | ; Add Analogous and Triadic displays 1080 | y := 495 1081 | TestGui.Add("Text", "x10 y" y " w100 Right", "Analogous") 1082 | for j, label in columnLabels 1083 | { 1084 | controls[label "Analogous1"] := TestGui.Add("Progress", "x" columnX[j]+10 " y" y-5 " w20 h20") 1085 | controls[label "Analogous2"] := TestGui.Add("Progress", "x" columnX[j]+30 " y" y-5 " w20 h20") 1086 | controls[label "Analogous3"] := TestGui.Add("Progress", "x" columnX[j]+10 " y" y+15 " w20 h20") 1087 | controls[label "Analogous4"] := TestGui.Add("Progress", "x" columnX[j]+30 " y" y+15 " w20 h20") 1088 | controls[label "AnalogousText"] := TestGui.Add("Text" , "x" columnX[j]+5 " y" y+40 " w80 h20 Center") 1089 | } 1090 | 1091 | y += 50 1092 | TestGui.Add("Text", "x10 y" y " w100 Right", "Triadic") 1093 | for j, label in columnLabels 1094 | { 1095 | controls[label "Triadic1"] := TestGui.Add("Progress", "x" columnX[j]+10 " y" y-5 " w20 h20") 1096 | controls[label "Triadic2"] := TestGui.Add("Progress", "x" columnX[j]+30 " y" y-5 " w20 h20") 1097 | controls[label "Triadic3"] := TestGui.Add("Progress", "x" columnX[j]+20 " y" y+15 " w20 h20") 1098 | controls[label "TriadicText"] := TestGui.Add("Text" , "x" columnX[j]+5 " y" y+40 " w80 h20 Center") 1099 | } 1100 | 1101 | ; Gradient display 1102 | y += 50 1103 | TestGui.Add("Text", "x10 y" y " w100 Right", "Gradient") 1104 | Loop 54 1105 | { 1106 | controls["Gradient" A_Index] := TestGui.Add("Progress", "x" 120+(A_Index-1)*10 " y" y-5 " w10 h20") 1107 | } 1108 | } 1109 | 1110 | UpdateTopRow() 1111 | { 1112 | columnLabels := ["Start Color", "End Color", "Mixed Color", "Average Color", "Multiplied Color"] 1113 | colorColumns := [startColor, endColor, startColor.Mix(endColor), Color.Average(startColor, endColor), Color.Multiply(startColor, endColor)] 1114 | 1115 | for i, _color in colorColumns 1116 | { 1117 | UpdateColorDisplay(columnLabels[i], _color) 1118 | } 1119 | } 1120 | 1121 | UpdateControls() 1122 | { 1123 | mixedColor := startColor.Mix(endColor) 1124 | averageColor := Color.Average(startColor, endColor) 1125 | multipliedColor := Color.Multiply(startColor, endColor) 1126 | 1127 | colorColumns := [startColor, endColor, mixedColor, averageColor, multipliedColor] 1128 | columnLabels := ["Start Color", "End Color", "Mixed Color", "Average Color", "Multiplied Color"] 1129 | 1130 | for i, _color in colorColumns 1131 | { 1132 | UpdateColorDisplay(columnLabels[i], _color) 1133 | UpdateColorDisplay(columnLabels[i] "Hex → RGB", _color) 1134 | UpdateColorDisplay(columnLabels[i] "RGB → RGB", Color.FromRGB(_color.R, _color.G, _color.B)) 1135 | 1136 | hsl := _color.ToHSL() 1137 | UpdateColorDisplay(columnLabels[i] "HSL → RGB", Color.FromHSL(hsl.H, hsl.S, hsl.L)) 1138 | 1139 | hwb := _color.ToHWB() 1140 | UpdateColorDisplay(columnLabels[i] "HWB → RGB", Color.FromHWB(hwb.H, hwb.W, hwb.B)) 1141 | 1142 | cmyk := _color.ToCMYK() 1143 | UpdateColorDisplay(columnLabels[i] "CMYK → RGB", Color.FromCMYK(cmyk.C, cmyk.M, cmyk.Y, cmyk.K)) 1144 | 1145 | ncol := _color.ToNCol() 1146 | UpdateColorDisplay(columnLabels[i] "NCol → RGB", Color.FromNCol(ncol.H, ncol.W, ncol.B)) 1147 | UpdateColorDisplay(columnLabels[i] "Invert", _color.Invert()) 1148 | UpdateColorDisplay(columnLabels[i] "Lighten", _color.Lighten(20)) 1149 | UpdateColorDisplay(columnLabels[i] "Darken", _color.Darken(20)) 1150 | UpdateColorDisplay(columnLabels[i] "Saturate", _color.Saturate(20)) 1151 | UpdateColorDisplay(columnLabels[i] "Desaturate", _color.Desaturate(20)) 1152 | UpdateColorDisplay(columnLabels[i] "Grayscale", _color.Grayscale()) 1153 | UpdateColorDisplay(columnLabels[i] "Complement", _color.Complement()) 1154 | } 1155 | 1156 | ; Update Analogous and Triadic displays 1157 | for i, _color in colorColumns 1158 | { 1159 | analogous := _color.Analogous(30, 4) 1160 | triadic := _color.Triadic() 1161 | 1162 | UpdateColorDisplay(columnLabels[i] "Analogous1", analogous[1]) 1163 | UpdateColorDisplay(columnLabels[i] "Analogous2", analogous[2]) 1164 | UpdateColorDisplay(columnLabels[i] "Analogous3", analogous[3]) 1165 | UpdateColorDisplay(columnLabels[i] "Analogous4", analogous[4]) 1166 | 1167 | UpdateColorDisplay(columnLabels[i] "Triadic1", triadic[1]) 1168 | UpdateColorDisplay(columnLabels[i] "Triadic2", triadic[2]) 1169 | UpdateColorDisplay(columnLabels[i] "Triadic3", triadic[3]) 1170 | } 1171 | 1172 | gradient := startColor.Gradient(endColor, 54) 1173 | Loop 54 1174 | { 1175 | hex := gradient[A_Index].ToHex("{R}{G}{B}").Full 1176 | controls["Gradient" A_Index].Opt("c" hex " Background" hex) 1177 | } 1178 | } 1179 | 1180 | UpdateColorDisplay(label, _color) 1181 | { 1182 | hex := _color.ToHex("{R}{G}{B}").Full 1183 | controls[label].Opt("c" hex " Background" hex) 1184 | 1185 | if (controls.Has(label "Text")) 1186 | { 1187 | controls[label "Text"].Value := _color.ToHex("0x{R}{G}{B}").Full 1188 | } 1189 | } 1190 | 1191 | TestGui.OnEvent("Close", (*) => ExitApp()) -------------------------------------------------------------------------------- /Lib/ColorPicker.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotKey v2.0 2 | ; #Include Color.ahk 3 | 4 | /** 5 | * ColorPicker.ahk 6 | * 7 | * @version 1.5 8 | * @author Komrad Toast (komrad.toast@hotmail.com) 9 | * @see https://www.autohotkey.com/boards/viewtopic.php?f=83&t=132295 10 | * @license 11 | * Copyright (c) 2024 Tyler J. Colby (Komrad Toast) 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 14 | * documentation files (the "Software"), to deal in the Software without restriction, including without limitation 15 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 16 | * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 23 | * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | **/ 25 | 26 | /** 27 | * ColorPicker class for encapsulating the color picker functionality. 28 | */ 29 | class ColorPicker 30 | { 31 | ; Configuration variables 32 | /** @property {String} FontName The font to use for the color preview text. Can be any font installed on your system. */ 33 | FontName := "Maple Mono" 34 | 35 | /** @property {Number} FontSize The font size for the color preview text. */ 36 | FontSize := 16 37 | 38 | /** @property {String} ViewMode The view mode of the color picker. Can be "crosshair", "grid", or any other value which will result in no overlay. */ 39 | ViewMode := "grid" 40 | 41 | /** @property {Integer} UpdateInterval The interval at which the preview will update, in milliseconds. 16ms = ~60 updates / second. */ 42 | UpdateInterval := 16 43 | 44 | /** @property {Boolean} HighlightCenter If True, highlights the pixel that the color is copied from. */ 45 | HighlightCenter := True 46 | 47 | /** @property {Integer} BorderWidth Thickness of preview border, in pixels. */ 48 | BorderWidth := 4 49 | 50 | /** @property {Integer} CrosshairWidth Thickness of crosshair lines, in pixels. */ 51 | CrosshairWidth := 1 52 | 53 | /** @property {Integer} GridWidth Thickness of grid lines, in pixels. */ 54 | GridWidth := 1 55 | 56 | /** @property {Integer} CenterDotRadius Radius of the Center Dot when not in "grid" or "crosshair" mode, in pixels. */ 57 | CenterDotRadius := 2 58 | 59 | /** @property {Integer} TextPadding The padding added above and below the preview Hex String, in pixels (half above, half below) */ 60 | TextPadding := 6 61 | 62 | /** @property {Integer} DefaultCaptureSize The size of area you want to capture around the cursor in pixels (N by N square) */ 63 | DefaultCaptureSize := 19 64 | 65 | /** @property {Integer} DefaultZoomFactor How much to multiply each pixel by. Default is 10x. */ 66 | DefaultZoomFactor := 10 67 | 68 | /** @property {Integer} LargeJumpAmount How many pixels to move the preview window by when holding shift and moving it with the keyboard. */ 69 | LargeJumpAmount := 16 70 | 71 | /** @property {Integer} PreviewXOffset Horizontal offset of the preview window from the cursor, in pixels. */ 72 | PreviewXOffset := 10 73 | 74 | /** @property {Integer} PreviewYOffset Vertical offset of the preview window from the cursor, in pixels. */ 75 | PreviewYOffset := 10 76 | 77 | /** @property {String} HexFormat The format string used to display and output the hex color value. Can use {R}, {G}, {B}, and {A}. */ 78 | HexFormat := "#{R}{G}{B}" 79 | 80 | /** @property {String} RGBFormat The format string used for output of the RGB color value. Can use {R}, {G}, {B}, and {A}. */ 81 | RGBFormat := "rgb({R}, {G}, {B})" 82 | 83 | /** @property {Boolean} Anchored Whether or not the picker should be anchored in place. */ 84 | Anchored := False 85 | 86 | /** @property {Boolean} CanUnanchor Whether or not the picker should be able to be un-anchored. */ 87 | CanUnanchor := True 88 | 89 | /** @property {Integer} AnchorTarget HWND of the window to anchor the picker to, if Anchored is True. */ 90 | 91 | /** @property {Integer} AnchoredX If anchored, the `X` position at which the picker should be anchored 92 | * Relative to the `AnchorTarget`'s position. */ 93 | AnchoredX := 0 94 | 95 | /** @property {Integer} AnchoredY If anchored, the `Y` position at which the picker should be anchored. 96 | * Relative to the `AnchorTarget`'s position. */ 97 | AnchoredY := 0 98 | 99 | ; Color Configuration. Press "i" to cycle between the two color sets. 100 | ;=========================== SET 1 === SET 2 ================================; 101 | /** @property {Color[]} TextFGColors Text Foreground colors. Supports 2 indices, any more will be ignored. */ 102 | TextFGColors := [ Color("White"), Color("Black") ] 103 | 104 | /** @property {Color[]} TextBGColors Text Background colors. Supports 2 indices, any more will be ignored. */ 105 | TextBGColors := [ Color("Black"), Color("White") ] 106 | 107 | /** @property {Color[]} BorderColors Border colors. Supports 2 indices, any more will be ignored. */ 108 | BorderColors := [ Color("Black"), Color("White") ] 109 | 110 | /** @property {Color[]} CrosshairColors Crosshair Color. Supports 2 indices, any more will be ignored. */ 111 | CrosshairColors := [ Color("Black"), Color("White") ] 112 | 113 | /** @property {Color[]} GridColors Grid Color. Supports 2 indices, any more will be ignored. */ 114 | GridColors := [ Color("Black"), Color("White") ] 115 | 116 | /** @property {Color[]} HighlightColors Highlight Color for selected grid square. Supports 2 indices, any more will be ignored. */ 117 | HighlightColors := [ Color("White"), Color("Black") ] 118 | 119 | ; Nothing below this line should need to be changed 120 | ;===========================================================================; 121 | /** @property {Object} Color An object containing the current RGB color. Has methods to convert to and from Hex, HSL, HWB, CMYK, and NCol */ 122 | Color := Color() 123 | 124 | /** @property {Boolean} Clip Whether to automatically copy the selected color to clipboard. */ 125 | Clip := False 126 | 127 | /** @property {Number} TargetHWND The window or control handle to confine the color picker to. Default is 0 */ 128 | TargetHWND := 0 129 | 130 | /** @property {Function} OnStart The function called when the picker starts. */ 131 | OnStart := 0 132 | 133 | /** @property {Function} OnUpdate The function called when the picker is updated. Passed the `ColorPicker.Color` object. */ 134 | OnUpdate := 0 135 | 136 | /** @property {Function} OnExit The function called when the picker is closed. */ 137 | OnExit := 0 138 | 139 | /** @property {Integer} `0` or `1` to select between the two color sets. */ 140 | ColorSet := 0 141 | 142 | /** @property {Color} TextFGColor Gets or Sets the currently selected color set's Text Foreground Color */ 143 | TextFGColor 144 | { 145 | get => this.TextFGColors[this.ColorSet + 1] 146 | set => this.TextFGColors[this.ColorSet + 1] := value 147 | } 148 | 149 | /** @property {Color} TextBGColor Gets or Sets the currently selected color set's Text Background Color */ 150 | TextBGColor 151 | { 152 | get => this.TextBGColors[this.ColorSet + 1] 153 | set => this.TextBGColors[this.ColorSet + 1] := value 154 | } 155 | 156 | /** @property {Color} BorderColor Gets or Sets the currently selected color set's Border Color */ 157 | BorderColor 158 | { 159 | get => this.BorderColors[this.ColorSet + 1] 160 | set => this.BorderColors[this.ColorSet + 1] := value 161 | } 162 | 163 | /** @property {Color} CrosshairColor Gets or Sets the currently selected color set's Crosshair Color */ 164 | CrosshairColor 165 | { 166 | get => this.CrosshairColors[this.ColorSet + 1] 167 | set => this.CrosshairColors[this.ColorSet + 1] := value 168 | } 169 | 170 | /** @property {Color} GridColor Gets or Sets the currently selected color set's Grid Color */ 171 | GridColor 172 | { 173 | get => this.GridColors[this.ColorSet + 1] 174 | set => this.GridColors[this.ColorSet + 1] := value 175 | } 176 | 177 | /** @property {Color} HighlightColor Gets or Sets the currently selected color set's Highlight Color */ 178 | HighlightColor 179 | { 180 | get => this.HighlightColors[this.ColorSet + 1] 181 | set => this.HighlightColors[this.ColorSet + 1] := value 182 | } 183 | 184 | /** 185 | * Creates a new instance of `ColorPicker` 186 | * @param {boolean} [clip=False] Whether to copy the selected color to clipboard. 187 | * @param {number} [hwnd=0] The handle of the target window to confine the picker to. 188 | * @param {Function} [callback=0] A callback function to be called with the selected color. 189 | */ 190 | __New(clip := False, hwnd := 0, callback := 0) 191 | { 192 | if (hwnd != 0) and (WinExist(hwnd)) 193 | this.TargetHWND := hwnd 194 | 195 | if (callback != 0) and (callback is func) 196 | this.OnUpdate := callback 197 | 198 | this.Clip := clip 199 | } 200 | 201 | /** 202 | * Runs the `ColorPicker` with default settings. 203 | * @param {boolean} [clip=True] Whether to copy the selected color to clipboard. 204 | * @param {number} [hwnd=0] The handle of the target window to confine the picker to. 205 | * @param {Function} [callback=0] A callback function to be called with the selected color. 206 | * @returns {Boolean | Object} The `Color` object if a color was chosen, `False` otherwise. 207 | */ 208 | static Run(clip := True, hwnd := 0, callback := 0) 209 | { 210 | picker := ColorPicker(clip, hwnd, callback) 211 | return picker.Start() 212 | } 213 | 214 | /** 215 | * Starts the current instance of `ColorPicker`. 216 | * @returns {Boolean | Color} The `Color` object if a color was chosen, `False` otherwise. 217 | */ 218 | Start() 219 | { 220 | if this.OnStart is Func 221 | this.OnStart.Call() 222 | 223 | startColor := this.Color 224 | 225 | try dpiContext := DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 226 | 227 | GetDpiScale(guiHwnd) 228 | { 229 | dpi := DllCall("User32.dll\GetDpiForWindow", "Ptr", guiHwnd, "UInt") 230 | return dpi / 96 231 | } 232 | 233 | BlockLButton(*) 234 | { 235 | KeyWait("LButton", "D") 236 | return 237 | } 238 | 239 | CaptureAndPreview(*) 240 | { 241 | if not frozen 242 | { 243 | ; Get cursor position 244 | CoordMode "Mouse", "Screen" 245 | CoordMode "Pixel", "Screen" 246 | MouseGetPos(&cursorX, &cursorY) 247 | dpiScale := GetDpiScale(previewGui.Hwnd) 248 | 249 | ; Calculate capture region 250 | halfSize := (captureSize - 1) // 2 251 | left := cursorX - halfSize 252 | top := cursorY - halfSize 253 | width := captureSize 254 | height := captureSize 255 | 256 | ; Capture screen region 257 | hDC := DllCall("GetDC", "Ptr", 0, "Ptr") 258 | hMemDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr") 259 | hBitmap := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", width, "Int", height, "Ptr") 260 | DllCall("SelectObject", "Ptr", hMemDC, "Ptr", hBitmap) 261 | DllCall("BitBlt", "Ptr", hMemDC, "Int", 0, "Int", 0, "Int", width, "Int", height, "Ptr", hDC, "Int", left, "Int", top, "UInt", 0x00CC0020) 262 | 263 | ; Get color of central pixel 264 | centralX := width // 2 265 | centralY := height // 2 266 | centralColor := DllCall("GetPixel", "Ptr", hMemDC, "Int", centralX, "Int", centralY, "UInt") 267 | hexColor := Format("{:06X}", centralColor & 0xFFFFFF) 268 | this.Color := Color(Format("{1}{2}{3}", SubStr(hexColor, 5, 2), SubStr(hexColor, 3, 2), SubStr(hexColor, 1, 2))) 269 | hexColor := this.Color.ToHex(this.HexFormat).Full 270 | 271 | ; Calculate preview size 272 | scaledZoomFactor := Round(zoomFactor * dpiScale) 273 | previewWidth := captureSize * scaledZoomFactor 274 | previewHeight := captureSize * scaledZoomFactor 275 | 276 | ; Prepare to draw text 277 | scaledFontSize := Round(this.FontSize * dpiScale) 278 | LOGFONT := Buffer(92, 0) 279 | NumPut("Int", scaledFontSize * 4, LOGFONT, 0) 280 | StrPut(this.FontName, LOGFONT.Ptr + 28, 32, "UTF-16") 281 | hFont := DllCall("CreateFontIndirect", "Ptr", LOGFONT, "Ptr") 282 | size := Buffer(8) 283 | DllCall("GetTextExtentPoint32", "Ptr", hDC, "Str", "Ay", "Int", 2, "Ptr", size) 284 | textHeight := Round((NumGet(size, 4, "Int") + this.TextPadding) * dpiScale) 285 | 286 | ; Conclude size calculations 287 | totalHeight := (previewHeight + textHeight) 288 | 289 | ; Create high-resolution memory DC 290 | hHighResDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr") 291 | hHighResBitmap := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", previewWidth * 4, "Int", totalHeight * 4, "Ptr") 292 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hHighResBitmap) 293 | DllCall("SetStretchBltMode", "Ptr", hHighResDC, "Int", 4) 294 | DllCall("StretchBlt", "Ptr", hHighResDC, "Int", 0, "Int", 0, "Int", previewWidth * 4, "Int", previewHeight * 4, "Ptr", hMemDC, "Int", 0, "Int", 0, "Int", width, "Int", height, "UInt", 0x00CC0020) 295 | 296 | ; Draw background rectangle 297 | hBrush := DllCall("CreateSolidBrush", "UInt", this.TextBGColor.ToHex("0x{B}{G}{R}").Full, "Ptr") 298 | rect := Buffer(16, 0) 299 | NumPut("Int", 0, rect, 0) 300 | NumPut("Int", previewHeight * 4, rect, 4) 301 | NumPut("Int", previewWidth * 4, rect, 8) 302 | NumPut("Int", totalHeight * 4, rect, 12) 303 | DllCall("FillRect", "Ptr", hHighResDC, "Ptr", rect, "Ptr", hBrush) 304 | DllCall("DeleteObject", "Ptr", hBrush) 305 | 306 | ; Render text at high resolution 307 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hFont) 308 | DllCall("SetTextColor", "Ptr", hHighResDC, "UInt", this.TextFGColor.ToHex("0x{B}{G}{R}").Full) 309 | DllCall("SetBkColor", "Ptr", hHighResDC, "UInt", this.TextBGColor.ToHex("0x{B}{G}{R}").Full) 310 | textWidth := DllCall("GetTextExtentPoint32", "Ptr", hHighResDC, "Str", hexColor, "Int", StrLen(hexColor), "Ptr", rect) 311 | textX := (previewWidth * 4 - NumGet(rect, 0, "Int")) // 2 312 | textY := previewHeight * 4 + (textHeight * 4 - scaledFontSize * 4) // 2 313 | DllCall("TextOut", "Ptr", hHighResDC, "Int", textX, "Int", textY, "Str", hexColor, "Int", StrLen(hexColor)) 314 | 315 | ; Calculate the offset based on captureSize 316 | offset := (Mod(captureSize, 2) == 0) ? Round(zoomFactor * 2) : 0 317 | if (this.ViewMode == "crosshair") 318 | { 319 | centerX := Round(previewWidth * 2) + offset 320 | centerY := Round(previewHeight * 2) + offset 321 | halfZoom := Round(zoomFactor * 2) 322 | hCrosshairPen := DllCall("CreatePen", "Int", 0, "Int", Round(this.CrosshairWidth * dpiScale) * 4, "UInt", this.CrosshairColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 323 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hCrosshairPen) 324 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", centerX, "Int", 0, "Ptr", 0) 325 | DllCall("LineTo", "Ptr", hHighResDC, "Int", centerX, "Int", previewHeight * 4) 326 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", 0, "Int", centerY, "Ptr", 0) 327 | DllCall("LineTo", "Ptr", hHighResDC, "Int", previewWidth * 4, "Int", centerY) 328 | if this.HighlightCenter 329 | { 330 | hInnerCrosshairPen := DllCall("CreatePen", "Int", 0, "Int", Round(this.CrosshairWidth * dpiScale) * 4, "UInt", this.HighlightColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 331 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hInnerCrosshairPen) 332 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", centerX, "Int", centerY - halfZoom, "Ptr", 0) 333 | DllCall("LineTo", "Ptr", hHighResDC, "Int", centerX, "Int", centerY + halfZoom) 334 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", centerX - halfZoom, "Int", centerY, "Ptr", 0) 335 | DllCall("LineTo", "Ptr", hHighResDC, "Int", centerX + halfZoom, "Int", centerY) 336 | DllCall("DeleteObject", "Ptr", hInnerCrosshairPen) 337 | } 338 | DllCall("DeleteObject", "Ptr", hCrosshairPen) 339 | } 340 | else if (this.ViewMode == "grid") 341 | { 342 | ; Calculate the center square 343 | if Mod(captureSize, 2) == 0 344 | centerIndex := captureSize // 2 + 1 345 | else 346 | centerIndex := captureSize // 2 + (captureSize & 1) 347 | 348 | ; Draw grid 349 | hGridPen := DllCall("CreatePen", "Int", 0, "Int", Round(this.GridWidth * dpiScale) * 4, "UInt", this.GridColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 350 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hGridPen) 351 | 352 | Loop captureSize + 1 353 | { 354 | x := (A_Index - 1) * scaledZoomFactor * 4 355 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", x, "Int", 0, "Ptr", 0) 356 | DllCall("LineTo", "Ptr", hHighResDC, "Int", x, "Int", previewHeight * 4) 357 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", 0, "Int", x, "Ptr", 0) 358 | DllCall("LineTo", "Ptr", hHighResDC, "Int", previewWidth * 4, "Int", x) 359 | } 360 | 361 | if this.HighlightCenter 362 | { 363 | ; Highlight the center or lower-right of center square 364 | hHighlightPen := DllCall("CreatePen", "Int", 0, "Int", Round(this.GridWidth * dpiScale) * 4, "UInt", this.HighlightColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 365 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hHighlightPen) 366 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", (centerIndex - 1) * scaledZoomFactor * 4, "Int", (centerIndex - 1) * scaledZoomFactor * 4, "Ptr", 0) 367 | DllCall("LineTo", "Ptr", hHighResDC, "Int", centerIndex * scaledZoomFactor * 4, "Int", (centerIndex - 1) * scaledZoomFactor * 4) 368 | DllCall("LineTo", "Ptr", hHighResDC, "Int", centerIndex * scaledZoomFactor * 4, "Int", centerIndex * scaledZoomFactor * 4) 369 | DllCall("LineTo", "Ptr", hHighResDC, "Int", (centerIndex - 1) * scaledZoomFactor * 4, "Int", centerIndex * scaledZoomFactor * 4) 370 | DllCall("LineTo", "Ptr", hHighResDC, "Int", (centerIndex - 1) * scaledZoomFactor * 4, "Int", (centerIndex - 1) * scaledZoomFactor * 4) 371 | DllCall("DeleteObject", "Ptr", hHighlightPen) 372 | } 373 | 374 | DllCall("DeleteObject", "Ptr", hGridPen) 375 | } 376 | else if this.HighlightCenter 377 | { 378 | ; Draw a dot in the center 379 | centerX := Round(previewWidth * 2) + offset 380 | centerY := Round(previewHeight * 2) + offset 381 | dotSize := Round(4 * dpiScale) * this.CenterDotRadius 382 | hDotBrush := DllCall("CreateSolidBrush", "UInt", this.HighlightColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 383 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hDotBrush) 384 | DllCall("Ellipse", "Ptr", hHighResDC, "Int", centerX - Round(dotSize * dpiScale), "Int", centerY - Round(dotSize * dpiScale), "Int", centerX + Round(dotSize * dpiScale), "Int", centerY + Round(dotSize * dpiScale)) 385 | DllCall("DeleteObject", "Ptr", hDotBrush) 386 | } 387 | 388 | ; Draw border 389 | hBorderPen := DllCall("CreatePen", "Int", 0, "Int", this.BorderWidth * 4, "UInt", this.BorderColor.ToHex("0x{A}{B}{G}{R}").Full & 0xFFFFFF, "Ptr") 390 | DllCall("SelectObject", "Ptr", hHighResDC, "Ptr", hBorderPen) 391 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", 0, "Int", 0, "Ptr", 0) 392 | DllCall("LineTo", "Ptr", hHighResDC, "Int", previewWidth * 4, "Int", 0) 393 | DllCall("LineTo", "Ptr", hHighResDC, "Int", previewWidth * 4, "Int", totalHeight * 4) 394 | DllCall("LineTo", "Ptr", hHighResDC, "Int", 0, "Int", totalHeight * 4) 395 | DllCall("LineTo", "Ptr", hHighResDC, "Int", 0, "Int", 0) 396 | 397 | ; Draw separator line 398 | DllCall("MoveToEx", "Ptr", hHighResDC, "Int", 0, "Int", previewHeight * 4, "Ptr", 0) 399 | DllCall("LineTo", "Ptr", hHighResDC, "Int", previewWidth * 4, "Int", previewHeight * 4) 400 | DllCall("DeleteObject", "Ptr", hBorderPen) 401 | 402 | ; Create preview DC and scale down from high-res DC 403 | hPreviewDC := DllCall("CreateCompatibleDC", "Ptr", hDC, "Ptr") 404 | hPreviewBitmap := DllCall("CreateCompatibleBitmap", "Ptr", hDC, "Int", previewWidth, "Int", totalHeight, "Ptr") 405 | DllCall("SelectObject", "Ptr", hPreviewDC, "Ptr", hPreviewBitmap) 406 | DllCall("SetStretchBltMode", "Ptr", hPreviewDC, "Int", 4) 407 | DllCall("StretchBlt", "Ptr", hPreviewDC, "Int", 0, "Int", 0, "Int", previewWidth, "Int", totalHeight, "Ptr", hHighResDC, "Int", 0, "Int", 0, "Int", previewWidth * 4, "Int", totalHeight * 4, "UInt", 0x00CC0020) 408 | 409 | ; Update preview GUI 410 | hPreviewHWND := WinExist("A") 411 | DllCall("UpdateLayeredWindow", "Ptr", hPreviewHWND, "Ptr", 0, "Ptr", 0, "Int64*", previewWidth | (totalHeight << 32), "Ptr", hPreviewDC, "Int64*", 0, "UInt", 0, "UInt*", 0xFF << 16, "UInt", 2) 412 | 413 | ; Clean up 414 | DllCall("DeleteObject", "Ptr", hFont) 415 | DllCall("DeleteDC", "Ptr", hPreviewDC) 416 | DllCall("DeleteObject", "Ptr", hPreviewBitmap) 417 | DllCall("DeleteDC", "Ptr", hHighResDC) 418 | DllCall("DeleteObject", "Ptr", hHighResBitmap) 419 | DllCall("DeleteDC", "Ptr", hMemDC) 420 | DllCall("DeleteObject", "Ptr", hBitmap) 421 | DllCall("ReleaseDC", "Ptr", 0, "Ptr", hDC) 422 | 423 | if this.OnUpdate is Func 424 | this.OnUpdate.Call(this.Color) 425 | } 426 | } 427 | 428 | CoordMode "Mouse", "Screen" 429 | CoordMode "Pixel", "Screen" 430 | Suspend(True) 431 | Hotkey("*LButton", BlockLButton, "On S") 432 | 433 | frozen := False, outType := "", colorSet := 0, textHeight := 0 434 | zoomFactor := this.DefaultZoomFactor 435 | captureSize := this.DefaultCaptureSize 436 | 437 | previewGui := Gui("+AlwaysOnTop -Caption +ToolWindow -DPIScale") 438 | if not DllCall("User32\SetWindowDisplayAffinity", "Ptr", previewGui.hWnd, "Int", 0x00000011, "Int") ; WDA_EXCLUDEFROMCAPTURE 439 | OutputDebug("Failed to set affinity:" A_LastError) 440 | previewGui.Opt(" +E0x80000") 441 | previewGui.Show() 442 | SetTimer(CaptureAndPreview, this.UpdateInterval) 443 | 444 | ; Set the cursor to crosshair 445 | hCross := DllCall("LoadCursor", "Ptr", 0, "Ptr", 32515) 446 | for cursorId in [32512, 32513, 32514, 32515, 32516, 32631, 32640, 32641, 32642, 32643, 32644, 32645, 32646, 32648, 32649, 32650, 32651] 447 | DllCall("SetSystemCursor", "Ptr", DllCall("CopyImage", "Ptr", hCross, "UInt", 2, "Int", 0, "Int", 0, "UInt", 0), "UInt", cursorId) 448 | 449 | ; If a Valid HWND was passed as an argument, confine the cursor to that window 450 | if (this.TargetHWND != 0) and WinExist("ahk_id " this.TargetHWND) 451 | { 452 | windowRect := Buffer(16) 453 | DllCall("GetWindowRect", "Ptr", this.TargetHWND, "Ptr", windowRect) 454 | confineLeft := NumGet(windowRect, 0, "Int") 455 | confineTop := NumGet(windowRect, 4, "Int") 456 | confineRight := NumGet(windowRect, 8, "Int") 457 | confineBottom := NumGet(windowRect, 12, "Int") 458 | 459 | DllCall("ClipCursor", "Ptr", windowRect) 460 | } 461 | 462 | ; Main loop 463 | while (True) 464 | { 465 | MouseGetPos(&mouseX, &mouseY) 466 | 467 | if (this.TargetHWND != 0) and WinExist("ahk_id " this.TargetHWND) 468 | { 469 | mouseX := Max(confineLeft, Min(mouseX, confineRight)) 470 | mouseY := Max(confineTop, Min(mouseY, confineBottom)) 471 | } 472 | 473 | if this.Anchored 474 | { 475 | previewGui.Move(this.AnchoredX, this.AnchoredY) 476 | } 477 | else 478 | { 479 | previewWidth := captureSize * zoomFactor + this.BorderWidth * 2 480 | previewHeight := captureSize * zoomFactor + this.BorderWidth * 2 + textHeight 481 | 482 | newX := mouseX + this.PreviewXOffset 483 | newY := mouseY + this.PreviewYOffset 484 | 485 | monitorCount := MonitorGetCount() 486 | dpiScale := GetDpiScale(previewGui.Hwnd) 487 | Loop monitorCount 488 | { 489 | MonitorGet(A_Index, &left, &top, &right, &bottom) 490 | 491 | if (mouseX >= left && mouseX < right && mouseY >= top && mouseY < bottom) 492 | { 493 | ; Apply DPI scaling to preview dimensions 494 | scaledPreviewWidth := previewWidth * dpiScale 495 | scaledPreviewHeight := previewHeight * dpiScale 496 | 497 | ; Adjust for right edge 498 | if (newX + scaledPreviewWidth > right) 499 | newX := mouseX - this.PreviewXOffset * dpiScale - scaledPreviewWidth 500 | 501 | ; Adjust for bottom edge, including taskbar 502 | if (newY + scaledPreviewHeight > bottom) 503 | newY := mouseY - this.PreviewYOffset * dpiScale - scaledPreviewHeight 504 | 505 | ; Ensure the preview stays within the monitor bounds 506 | newX := Max(left, Min(newX, right - scaledPreviewWidth)) 507 | newY := Max(top, Min(newY, bottom - scaledPreviewHeight)) 508 | 509 | break 510 | } 511 | } 512 | 513 | previewGui.Move(newX, newY) 514 | } 515 | 516 | ; "LButton", "Enter", or "NumpadEnter" Captures HEX, Shift in combination with them captures RGB 517 | if GetKeyState("LButton", "P") or GetKeyState("Enter", "P") or GetKeyState("NumpadEnter", "P") or GetKeyState("Space", "P") 518 | { 519 | if GetKeyState("Shift", "P") 520 | outType := "RGB" 521 | else 522 | outType := "HEX" 523 | break 524 | } 525 | 526 | ; "Escape" or "Q" exits 527 | if GetKeyState("Escape", "P") or GetKeyState("q", "P") 528 | { 529 | outType := "Exit" 530 | break 531 | } 532 | 533 | ; "C" cycles between color schemes 534 | if GetKeyState("c", "P") 535 | { 536 | this.ColorSet := !this.ColorSet 537 | 538 | KeyWait("c") 539 | } 540 | 541 | ; "A" toggles anchoring 542 | if GetKeyState("a", "P") or GetKeyState("NumpadDot", "P") 543 | { 544 | if this.CanUnanchor 545 | { 546 | this.Anchored := !this.Anchored 547 | if this.Anchored 548 | { 549 | this.AnchoredX := mouseX + this.PreviewXOffset 550 | this.AnchoredY := mouseY + this.PreviewYOffset 551 | } 552 | } 553 | 554 | if !KeyWait("a") or !KeyWait("NumpadDot") 555 | continue 556 | } 557 | 558 | ; "M" cycles between view modes (grid, crosshair, none) 559 | if GetKeyState("m", "P") 560 | { 561 | this.ViewModes := [ "grid", "crosshair", "none" ] 562 | index := 0 563 | 564 | for mode in this.ViewModes 565 | if (mode == this.ViewMode) 566 | index := A_Index 567 | 568 | if index == 0 569 | { 570 | this.ViewMode := "none" 571 | index := 3 572 | } 573 | 574 | this.ViewMode := this.ViewModes[Mod(index, this.ViewModes.Length) + 1] 575 | KeyWait("m") 576 | } 577 | 578 | ; "Left" or "Numpad4" moves cursor left one pixel 579 | if GetKeyState("Left", "P") or GetKeyState("Numpad4", "P") 580 | { 581 | if GetKeyState("Shift", "P") 582 | MouseMove(-this.LargeJumpAmount, 0, 0, "R") 583 | else 584 | MouseMove(-1, 0, 0, "R") 585 | 586 | if !KeyWait("Left", "T0.10") or !KeyWait("Numpad4", "T0.10") 587 | continue 588 | } 589 | 590 | ; "Right" or "Numpad6" moves cursor right one pixel 591 | if GetKeyState("Right", "P") or GetKeyState("Numpad6", "P") 592 | { 593 | if GetKeyState("Shift", "P") 594 | MouseMove(this.LargeJumpAmount, 0, 0, "R") 595 | else 596 | MouseMove(1, 0, 0, "R") 597 | 598 | if !KeyWait("Right", "T0.10") or !KeyWait("Numpad6", "T0.10") 599 | continue 600 | } 601 | 602 | ; "Up" or "Numpad8" moves cursor up one pixel 603 | if GetKeyState("Up", "P") or GetKeyState("Numpad8", "P") 604 | { 605 | if GetKeyState("Shift", "P") 606 | MouseMove(0, -this.LargeJumpAmount, 0, "R") 607 | else 608 | MouseMove(0, -1, 0, "R") 609 | 610 | if !KeyWait("Up", "T0.10") or !KeyWait("Numpad8", "T0.10") 611 | continue 612 | } 613 | 614 | ; "Down" or "Numpad2" moves cursor down one pixel 615 | if GetKeyState("Down", "P") or GetKeyState("Numpad2", "P") 616 | { 617 | if GetKeyState("Shift", "P") 618 | MouseMove(0, this.LargeJumpAmount, 0, "R") 619 | else 620 | MouseMove(0, 1, 0, "R") 621 | 622 | if !KeyWait("Down", "T0.10") or !KeyWait("Numpad2", "T0.10") 623 | continue 624 | } 625 | 626 | ; "H" toggles highlighting the center pixel 627 | if GetKeyState("h", "P") 628 | { 629 | this.HighlightCenter := !this.HighlightCenter 630 | KeyWait("h") 631 | } 632 | 633 | ; "-" or "NumpadSub" decreases capture size 634 | if GetKeyState("-", "P") or GetKeyState("NumpadSub", "P") 635 | { 636 | captureSize := Max(1, --captureSize) 637 | 638 | if !KeyWait("-") or !KeyWait("NumpadSub") 639 | continue 640 | } 641 | 642 | ; "=" or "NumpadAdd" increases capture size 643 | if GetKeyState("=", "P") or GetKeyState("NumpadAdd", "P") 644 | { 645 | captureSize := ++captureSize 646 | 647 | if !KeyWait("=") or !KeyWait("NumpadAdd") 648 | continue 649 | } 650 | 651 | ; "[" or "NumpadDiv" decreases zoom factor 652 | if GetKeyState("[", "P") or GetKeyState("NumpadDiv", "P") 653 | { 654 | zoomFactor := Max(1, --zoomFactor) 655 | 656 | if !KeyWait("[") or !KeyWait("NumpadDiv") 657 | continue 658 | } 659 | 660 | ; "]" or "NumpadMult" increases zoom factor 661 | if GetKeyState("]", "P") or GetKeyState("NumpadMult", "P") 662 | { 663 | zoomFactor := ++zoomFactor 664 | if !KeyWait("]") or !KeyWait("NumpadMult") 665 | continue 666 | } 667 | 668 | ; "0" or "Numpad0" resets zoom and capture size 669 | if GetKeyState("0", "P") or GetKeyState("Numpad0", "P") 670 | { 671 | zoomFactor := this.DefaultZoomFactor 672 | captureSize := this.DefaultCaptureSize 673 | 674 | if !KeyWait("0") or !KeyWait("Numpad0") 675 | continue 676 | } 677 | 678 | ; "F" or "Numpad5" toggles freezing the preview update cycle 679 | if GetKeyState("f", "P") or GetKeyState("Numpad5", "P") 680 | { 681 | frozen := !frozen 682 | 683 | if !KeyWait("f") or !KeyWait("Numpad5") 684 | continue 685 | } 686 | 687 | Sleep(10) 688 | } 689 | 690 | this.Color.RGBFormat := this.RGBFormat 691 | this.Color.HexFormat := this.HexFormat 692 | 693 | if (this.Clip == True) and ((outType == "HEX") or (outType == "RGB")) 694 | A_Clipboard := (outType == "HEX" ? this.Color.ToHex().Full : this.Color.Full) 695 | 696 | ; Cleanup 697 | SetTimer(CaptureAndPreview, 0) ; Turn off the timer 698 | DllCall("SystemParametersInfo", "UInt", 0x57, "UInt", 0, "Ptr", 0, "UInt", 0) ; Reset cursor 699 | DllCall("DestroyCursor", "Ptr", hCross) 700 | Sleep(50) 701 | Hotkey("*LButton", "Off") 702 | Suspend(False) 703 | 704 | previewGui.Destroy() 705 | try DllCall("SetThreadDpiAwarenessContext", "ptr", dpiContext, "ptr") 706 | 707 | if (this.TargetHWND != 0) and WinExist("ahk_id " this.TargetHWND) 708 | DllCall("ClipCursor", "Ptr", 0) 709 | 710 | this.Color := (outType == "Exit" ? startColor : this.Color) 711 | 712 | if this.OnUpdate is Func 713 | this.OnUpdate.Call(this.Color) 714 | 715 | if this.OnExit is Func 716 | this.OnExit.Call(this.Color) 717 | 718 | return (outType == "Exit" ? False : this.Color) 719 | } 720 | } 721 | 722 | /** Place a ";" at the beginning of this line to test the color picker 723 | #c:: 724 | { 725 | mainWindow := Gui() 726 | mainWindow.MarginX := 5 727 | mainWindow.MarginY := 5 728 | mainWindow.SetFont("s8", "Lucida Console") 729 | mainWindow.Show("w310 h550") 730 | 731 | colorWheel := mainWindow.AddPicture("w300 h-1 +Border", "colorWheel.jpg") 732 | colorBox := mainWindow.AddText("x10 y+10 w290 h64 +BackgroundBlack", "") 733 | hexLabel := mainWindow.AddText("x10 y+10 w140", "Hex: #000000") 734 | rgbLabel := mainWindow.AddText("x160 yp+0 w140", "RGB: 0, 0, 0") 735 | hex_r := mainWindow.AddText("x10 y+5 w140", "R: 0x00") 736 | rgb_r := mainWindow.AddText("x160 yp+0 w140", "R: 0") 737 | hex_g := mainWindow.AddText("x10 y+5 w140", "G: 0x00") 738 | rgb_g := mainWindow.AddText("x160 yp+0 w140", "G: 0") 739 | hex_b := mainWindow.AddText("x10 y+5 w140", "B: 0x00") 740 | rgb_b := mainWindow.AddText("x160 yp+0 w140", "B: 0") 741 | hslLabel := mainWindow.AddText("x10 y+20 w140", "HSL: 0, 0%, 0%") 742 | hsl_h := mainWindow.AddText("x10 y+5 w140", "H: 0") 743 | hsl_s := mainWindow.AddText("x10 y+5 w140", "S: 0%") 744 | hsl_l := mainWindow.AddText("x10 y+5 w140", "L: 0%") 745 | 746 | picker := ColorPicker(True,, UpdateColors) 747 | picker.FontName := "Papyrus" 748 | picker.FontSize := 24 749 | picker.AnchoredX := 50 750 | picker.AnchoredY := 50 751 | picker.Anchored := True 752 | picker.CanUnanchor := True 753 | picker.DefaultCaptureSize := 5 754 | picker.DefaultZoomFactor := 12 755 | picker.OnExit := (color) => MsgBox(color.ToHex("Color selected: #{R}{G}{B}").Full) 756 | 757 | colorWheel.OnEvent("Click", (*) => picker.Start()) 758 | 759 | UpdateColors(color) 760 | { 761 | hex := color.ToHex("0x{B}{G}{R}") 762 | 763 | picker.TextFGColor := color 764 | 765 | hex := color.ToHex("{R}{G}{B}") 766 | colorBox.Opt("+Redraw +Background" hex.Full) 767 | 768 | hexLabel.Text := "Hex: #" hex.Full 769 | hex_r.Text := "R: " hex.R 770 | hex_g.Text := "G: " hex.G 771 | hex_b.Text := "B: " hex.B 772 | 773 | color.RGBFormat := "RGB: {R}, {G}, {B}" 774 | rgbLabel.Text := color.Full 775 | rgb_r.Text := "R: " color.R 776 | rgb_g.Text := "G: " color.G 777 | rgb_b.Text := "B: " color.B 778 | 779 | hsl := color.ToHSL("HSL: {H}, {S}%, {L}%") 780 | hslLabel.Text := hsl.Full 781 | hsl_h.Text := "H: " hsl.H 782 | hsl_s.Text := "S: " hsl.S 783 | hsl_l.Text := "L: " hsl.L 784 | 785 | hexLabel.Opt("C" hex.Full) 786 | hex_r.Opt("C" hex.R . "0000") 787 | hex_g.Opt("C00" hex.G . "00") 788 | hex_b.Opt("C0000" hex.B) 789 | 790 | rgbLabel.Opt("C" hex.Full) 791 | rgb_r.Opt("C" hex.R . "0000") 792 | rgb_g.Opt("C00" hex.G . "00") 793 | rgb_b.Opt("C0000" hex.B) 794 | } 795 | } -------------------------------------------------------------------------------- /Lib/GuiButtonIcon.ahk: -------------------------------------------------------------------------------- 1 | /** 2 | * Assigns an icon to a GUI button. 3 | * @author Fanatic Guru 4 | * @date 2019-03-26 5 | * @param {number|Gui.Button} Handle - The HWND handle of the GUI button or the Gui button object. 6 | * @param {string} File - The file containing the icon image. 7 | * @param {number} [Index=1] - The index of the icon in the file. 8 | * @param {string} [Options=''] - Single-letter flags followed by a number, with multiple options delimited by a space: 9 | * - `W`: Width of the icon (default = 16) 10 | * - `H`: Height of the icon (default = 16) 11 | * - `S`: Size of the icon, makes both width and height equal to the size 12 | * - `L`: Left margin 13 | * - `T`: Top margin 14 | * - `R`: Right margin 15 | * - `B`: Bottom margin 16 | * - `A`: Alignment (0 = left, 1 = right, 2 = top, 3 = bottom, 4 = center, default = 4) 17 | * @returns {number} 1 if the icon was found, 0 if the icon was not found. 18 | * @example 19 | * MyGui := Gui() 20 | * MyButton := MyGui.Add("Button", "w70 h38", "Save") 21 | * GuiButtonIcon(MyButton, "shell32.dll", 259, "s32 a1 r2") 22 | * MyGui.Show 23 | */ 24 | GuiButtonIcon(Handle, File, Index := 1, Options := "") 25 | { 26 | Type(Handle) = 'Gui.Button' && Handle := Handle.Hwnd 27 | RegExMatch(Options, "i)w\K\d+", &W) ? W := W.0 : W := 16 28 | RegExMatch(Options, "i)h\K\d+", &H) ? H := H.0 : H := 16 29 | RegExMatch(Options, "i)s\K\d+", &S) ? W := H := S.0 : "" 30 | RegExMatch(Options, "i)l\K\d+", &L) ? L := L.0 : L := 0 31 | RegExMatch(Options, "i)t\K\d+", &T) ? T := T.0 : T := 0 32 | RegExMatch(Options, "i)r\K\d+", &R) ? R := R.0 : R := 0 33 | RegExMatch(Options, "i)b\K\d+", &B) ? B := B.0 : B := 0 34 | RegExMatch(Options, "i)a\K\d+", &A) ? A := A.0 : A := 4 35 | W := W * (A_ScreenDPI / 96), H := H * (A_ScreenDPI / 96) 36 | Psz := (A_PtrSize = "" ? 4 : A_PtrSize), DW := "UInt", Ptr := (A_PtrSize = "" ? DW : "Ptr") 37 | button_il := Buffer(20 + Psz) 38 | normal_il := DllCall("ImageList_Create", DW, W, DW, H, DW, 0x21, DW, 1, DW, 1) 39 | NumPut(Ptr, normal_il, button_il, 0) ; Width & Height 40 | NumPut(DW, L, button_il, 0 + Psz) ; Left Margin 41 | NumPut(DW, T, button_il, 4 + Psz) ; Top Margin 42 | NumPut(DW, R, button_il, 8 + Psz) ; Right Margin 43 | NumPut(DW, B, button_il, 12 + Psz) ; Bottom Margin 44 | NumPut(DW, A, button_il, 16 + Psz) ; Alignment 45 | SendMessage(BCM_SETIMAGELIST := 5634, 0, button_il, , "AHK_ID " Handle) 46 | Return IL_Add(normal_il, File, Index) 47 | } -------------------------------------------------------------------------------- /Lib/GuiCtrlTips.ahk: -------------------------------------------------------------------------------- 1 | ; ====================================================================================================================== 2 | ; Release date: 2023-05-28 3 | ; ====================================================================================================================== 4 | ; Add tooltips to your Gui controls. 5 | ; Tooltips are managed per Gui, so you first have to create a new instance of the class for the Gui, e.g.: 6 | ; MyGui := Gui() 7 | ; MyGui.Tips := GuiCtrlTips(MyGui) 8 | ; Then you can create your controls and add tooltips, e.g.: 9 | ; MyBtn := MyGui.AddButton(...) 10 | ; MyGui.Tips.SetTip(MyBtn, "My Tooltip!") 11 | ; You can activate, deactivate, or change the delay times for all tooltips at any time by calling the corresponding 12 | ; methods. 13 | ; To remove a tooltip from a single control pass an empty text, e.g.: 14 | ; MyGui.Tips.SetTip(MyBtn, "") 15 | ; Text and Picture controls require the SS_NOTIFY (+0x0100) style or a 'Click' event function. 16 | ; ---------------------------------------------------------------------------------------------------------------------- 17 | ; Tooltip control: https://learn.microsoft.com/en-us/windows/win32/controls/tooltip-control-reference 18 | ; ====================================================================================================================== 19 | Class GuiCtrlTips { 20 | Static TOOLINFO { 21 | Get { 22 | Static SizeOfTI := 24 + (A_PtrSize * 6) 23 | Local TI := Buffer(SizeOfTI, 0) 24 | NumPut("UInt", SizeOfTI, TI) 25 | Return TI 26 | } 27 | } 28 | ; ------------------------------------------------------------------------------------------------------------------- 29 | Static ToolTipFont { 30 | Get { 31 | Static SizeOfLFW := 92 ; LOGFONTW structure 32 | Static SizeOfNCM := 44 + (SizeOfLFW * 5) ; NONCLIENTMETRICSW structure 33 | Static OffStatusFont := 40 + (SizeOfLFW * 3) ; lfStatusFont 34 | Static LOGFONTW := 0 35 | If !IsObject(LOGFONTW) { ; first call 36 | Local NCM := Buffer(SizeOfNCM, 0) 37 | NumPut("UInt", SizeOfNCM, NCM) 38 | DllCall("SystemParametersInfoW", "UInt", 0x0029, "UInt", 0, "Ptr", NCM.Ptr, "UInt", 0) ; SPI_GETNONCLIENTMETRICS 39 | LOGFONTW := Buffer(SizeOfLFW, 0) 40 | DllCall("RtlMoveMemory", "Ptr", LOGFONTW.Ptr, "Ptr", NCM.Ptr + OffStatusFont, "Ptr", SizeOfLFW) 41 | } 42 | Local LF := Buffer(SizeOfLFW, 0) 43 | DllCall("RtlMoveMemory", "Ptr", LF.Ptr, "Ptr", LOGFONTW.Ptr, "Ptr", SizeOfLFW) 44 | Return LF 45 | } 46 | } 47 | ; ------------------------------------------------------------------------------------------------------------------- 48 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-addtool 49 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setmaxtipwidth 50 | ; https://learn.microsoft.com/en-us/windows/win32/api/commctrl/ns-commctrl-tttoolinfow 51 | __New(GuiObj, UseAhkStyle := True, UseComboEdit := True) { 52 | Local Flags, HGUI, HTIP, TI 53 | If !(GuiObj Is Gui) 54 | Throw TypeError(A_ThisFunc . ": Expected a Gui object!", -1 "GuiObj") 55 | HGUI := GuiObj.Hwnd 56 | ; Create the TOOLINFO structure 57 | Flags := 0x11 ; TTF_SUBCLASS | TTF_IDISHWND 58 | TI := GuiCtrlTips.TOOLINFO 59 | NumPut("UInt", Flags, "UPtr", HGUI, "UPtr", HGUI, TI, 4) ; uFlags, hwnd, uID 60 | ; Create a tooltip control for this Gui 61 | If !(HTIP := DllCall("CreateWindowEx", "UInt", 0, "Str", "tooltips_class32", "Ptr", 0, "UInt", 0x80000003 62 | , "Int", 0x80000000, "Int", 0x80000000, "Int", 0x80000000, "Int", 0x80000000 63 | , "Ptr", HGUI, "Ptr", 0, "Ptr", 0, "Ptr", 0, "UPtr")) 64 | Throw Error(A_ThisFunc . ": Could not create a tooltip control", -1) 65 | If (UseAhkStyle) 66 | DllCall("Uxtheme.dll\SetWindowTheme", "Ptr", HTIP, "Ptr", 0, "Str", " ") 67 | SendMessage(0x0418, 0, A_ScreenWidth, HTIP) ; TTM_SETMAXTIPWIDTH 68 | ; SendMessage(0x0432, 0, TI.Ptr, HTIP) ; TTM_ADDTOOLW <--- doesn't seem required any more 69 | This.DefineProp("HTIP", {Get: (*) => HTIP}) 70 | This.DefineProp("HGUI", {Get: (*) => HGUI}) 71 | This.DefineProp("UAS", {Get: (*) => !!UseAhkStyle}) 72 | This.DefineProp("UCE", {Get: (*) => !!UseComboEdit}) 73 | This.Ctrls := Map() 74 | } 75 | ; ------------------------------------------------------------------------------------------------------------------- 76 | __Delete() { 77 | If This.HasProp("HTIP") 78 | DllCall("DestroyWindow", "Ptr", This.HTIP) 79 | } 80 | ; ------------------------------------------------------------------------------------------------------------------- 81 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-activate 82 | Activate() { 83 | SendMessage(0x0401, True, 0, This.HTIP) ; TTM_ACTIVATE 84 | Return True 85 | } 86 | ; ------------------------------------------------------------------------------------------------------------------- 87 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-activate 88 | Deactivate() { 89 | SendMessage(0x0401, False, 0, This.HTIP) ; TTM_ACTIVATE 90 | Return True 91 | } 92 | ; ------------------------------------------------------------------------------------------------------------------- 93 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-settipbkcolor 94 | SetBkColor(Color) { 95 | If IsInteger(Color) { 96 | Color := ((Color & 0x0000FF) << 16) | (Color & 0x00FF00) | ((Color & 0xFF0000) >> 16) 97 | SendMessage(0x0413, Color, 0, This.HTIP) 98 | } 99 | } 100 | ; ------------------------------------------------------------------------------------------------------------------- 101 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime 102 | ; Flag - one of the string keys defined in Flags 103 | ; MilliSecs - time in millisecons, pass -1 to reset to the default 104 | SetDelayTime(Flag, MilliSecs) { 105 | Static Flags := {AUTOMATIC: 0, AUTOPOP: 2, INITIAL: 3, RESHOW: 1} 106 | If !Flags.HasProp(Flag) || !IsInteger(MilliSecs) 107 | Return False 108 | If (MilliSecs < -1) 109 | MilliSecs := -1 110 | SendMessage(0x0403, Flags.%Flag%, MilliSecs, This.HTIP) 111 | Return True 112 | } 113 | ; ------------------------------------------------------------------------------------------------------------------- 114 | ; Any value passed in Bold and Italic will set the related font option 115 | SetFont(FontSize?, FontName?, Bold?, Italic?) { 116 | Static LOGPIXELSY := 0, PrevFont := 0 117 | If (LOGPIXELSY = 0) { ; first call 118 | Local HDC := DllCall("GetDC", "Ptr", 0, "UPtr") 119 | LOGPIXELSY := DllCall("GetDeviceCaps", "Ptr", HDC, "Int", 90, "Int") ; LOGPIXELSY 120 | DllCall("ReleaseDC", "Ptr", 0, "Ptr", HDC) 121 | } 122 | Local LOGFONT := GuiCtrlTips.ToolTipFont 123 | If IsSet(FontSize) && IsNumber(FontSize) 124 | NumPut("Int", -Round(FontSize * LOGPIXELSY / 72), LOGFONT) 125 | If IsSet(Bold) 126 | NumPut("Int", 700, LOGFONT, 16) 127 | If IsSet(Italic) 128 | NumPut("UChar", 1, LOGFONT, 20) 129 | If IsSet(FontName) 130 | StrPut(FontName, LOGFONT.Ptr + 28, 32) 131 | Local HFONT := DllCall("CreateFontIndirectW", "Ptr", LOGFONT.Ptr, "UPtr") 132 | SendMessage(0x0030, HFONT, 1, This.HTIP) 133 | If PrevFont 134 | DllCall("DeleteObject", "Ptr", PrevFont) 135 | PrevFont := HFONT 136 | } 137 | ; ------------------------------------------------------------------------------------------------------------------- 138 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setmargin 139 | SetMargins(L := 0, T := 0, R := 0, B := 0) { 140 | RC := Buffer(16, 0) 141 | NumPut("Int", L, "Int", T, "Int", R, "Int", B, RC) 142 | SendMessage(0x041A, 0, RC.Ptr, This.HTIP) 143 | } 144 | ; ------------------------------------------------------------------------------------------------------------------- 145 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-addtool 146 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-deltool 147 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-updatetiptext 148 | SetTip(GuiCtrl, TipText, CenterTip := False) { 149 | Local Flags, HCTL, TI 150 | ; Check the passed GuiCtrl 151 | If !(GuiCtrl Is Gui.Control) || (GuiCtrl.Gui.Hwnd != This.HGUI) 152 | Return False 153 | If (GuiCtrl.Type = "ComboBox") && This.UCE ; use the Edit control of the Combobox 154 | HCTL := DllCall("FindWindowExW", "Ptr", GuiCtrl.Hwnd, "Ptr", 0, "Ptr", 0, "Ptr", 0, "UPtr") 155 | Else 156 | HCTL := GuiCtrl.Hwnd 157 | ; Create the TOOLINFO structure 158 | Flags := 0x11 | (CenterTip ? 0x02 : 0x00) ; TTF_SUBCLASS | TTF_IDISHWND [| TTF_CENTERTIP] 159 | TI := GuiCtrlTips.TOOLINFO 160 | NumPut("UInt", Flags, "UPtr", This.HGUI, "UPtr", HCTL, TI, 4) ; cbSize, uFlags, hwnd, uID 161 | If (TipText = "") { 162 | If This.Ctrls.Has(HCTL) { 163 | SendMessage(0x0433, 0, TI.Ptr, This.HTIP) ; TTM_DELTOOLW 164 | This.Ctrls.Delete(HCTL) 165 | } 166 | Return True 167 | } 168 | If !This.Ctrls.Has(HCTL) { 169 | SendMessage(0x0432, 0, TI.Ptr, This.HTIP) ; TTM_ADDTOOLW 170 | This.Ctrls[HCTL] := True 171 | } 172 | ; Set / Update the tool's text. 173 | NumPut("UPtr", StrPtr(TipText), TI, 24 + (A_PtrSize * 3)) ; lpszText 174 | SendMessage(0x0439, 0, TI.Ptr, This.HTIP) ; TTM_UPDATETIPTEXTW 175 | Return True 176 | } 177 | ; ------------------------------------------------------------------------------------------------------------------- 178 | ; https://learn.microsoft.com/en-us/windows/win32/controls/ttm-settiptextcolor 179 | SetTxColor(Color) { 180 | If IsInteger(Color) { 181 | Color := ((Color & 0x0000FF) << 16) | (Color & 0x00FF00) | ((Color & 0xFF0000) >> 16) 182 | SendMessage(0x0414, Color, 0, This.HTIP) 183 | } 184 | } 185 | } 186 | ; ====================================================================================================================== -------------------------------------------------------------------------------- /Lib/LV_GridColor.ahk: -------------------------------------------------------------------------------- 1 | ; ====================================================================================================================== 2 | ; just me -> https://www.autohotkey.com/boards/viewtopic.php?f=83&t=125259 3 | ; LV_GridColor - Sets/resets the color used to draw the gridlines in a ListView 4 | ; Parameter: 5 | ; LV - ListView control object 6 | ; GridColor - RGB integer value in the range from 0x000000 to 0xFFFFFF or HTML color nameas defined in the docs 7 | ; Remarks: 8 | ; Drawing is implemented using Custom Draw -> https://learn.microsoft.com/en-us/windows/win32/controls/custom-draw 9 | ; ====================================================================================================================== 10 | LV_GridColor(LV, GridColor?) { 11 | Static Controls := Map() 12 | If Controls.Has(LV.Hwnd) { 13 | LV.OnNotify(-12, NM_CUSTOMDRAW, 0) 14 | If LV.HasProp("GridPen") { 15 | DllCall("DeleteObject", "Ptr", LV.GridPen) 16 | LV.DeleteProp("GridPen") 17 | } 18 | If (Controls[LV.Hwnd] = 1) 19 | LV.Opt("+LV0x00000001") ; LV_EX_GRIDLINES 20 | Controls.Delete(LV.Hwnd) 21 | } 22 | If IsSet(GridColor) { 23 | GridColor := BGR(GridColor) 24 | If (GridColor = "") 25 | Throw Error("Invald parameter GridColor!") 26 | LV.GridPen := DllCall("CreatePen", "Int", 0, "Int", 1, "UInt", GridColor, "UPtr") 27 | LV_EX_Styles := SendMessage(0x1037, 0, 0, LV.Hwnd) ; LVM_GETEXTENDEDLISTVIEWSTYLE 28 | If (LV_EX_Styles & 0x00000001) ; LV_EX_GRIDLINES 29 | LV.Opt("-LV0x00000001") 30 | Controls[LV.Hwnd] := LV_EX_Styles & 0x00000001 31 | LV.OnNotify(-12, NM_CUSTOMDRAW, 1) 32 | } 33 | Return True 34 | ; ------------------------------------------------------------------------------------------------------------------- 35 | NM_CUSTOMDRAW(LV, LP) { 36 | Static Points := Buffer(24, 0) ; Polyline points 37 | Static SizeNMHDR := A_PtrSize * 3 ; Size of NMHDR structure 38 | Static SizeNCD := SizeNMHDR + 16 + (A_PtrSize * 5) ; Size of NMCUSTOMDRAW structure 39 | Static OffDC := SizeNMHDR + A_PtrSize 40 | Static OffRC := OffDC + A_PtrSize 41 | Static OffItem := SizeNMHDR + 16 + (A_PtrSize * 2) ; Offset of dwItemSpec (NMCUSTOMDRAW) 42 | Static OffCT := SizeNCD ; Offset of clrText (NMLVCUSTOMDRAW) 43 | Static OffCB := OffCT + 4 ; Offset of clrTextBk (NMLVCUSTOMDRAW) 44 | Static OffSubItem := OffCB + 4 ; Offset of iSubItem (NMLVCUSTOMDRAW) 45 | Critical -1 46 | Local DrawStage := NumGet(LP + SizeNMHDR, "UInt") ; drawing stage 47 | Switch DrawStage { 48 | Case 0x030002: ; CDDS_SUBITEMPOSTPAINT 49 | Local SI := NumGet(LP + OffSubItem, "Int") ; subitem 50 | Local DC := NumGet(LP + OffDC, "UPtr") ; device context 51 | Local RC := LP + OffRC ; drawing rectangle 52 | Local L := SI = 0 ? 0 : NumGet(RC, "Int") ; left 53 | Local T := NumGet(RC + 4, "Int") ; top 54 | Local R := NumGet(RC + 8, "Int") ; right 55 | Local B := NumGet(RC + 12, "Int") ; bottom 56 | Local PP := DllCall("SelectObject", "Ptr", DC, "Ptr", LV.GridPen, "UPtr") ; previous pen 57 | If (SI = 0) 58 | NumPut("Int", L, "Int", B - 1, "Int", R, "Int", B - 1, Points) 59 | Else 60 | NumPut("Int", L, "Int", T, "Int", L, "Int", B - 1, "Int", R, "Int", B - 1, Points) 61 | DllCall("Polyline", "Ptr", DC, "Ptr", Points, "Int", SI = 0 ? 2 : 3) 62 | NumPut("Int", L, "Int", B - 1, "Int", R, "Int", B - 1, "Int", R, "Int", T - 1, Points) 63 | DllCall("Polyline", "Ptr", DC, "Ptr", Points, "Int", 3) 64 | DllCall("SelectObject", "Ptr", DC, "Ptr", PP, "UPtr") 65 | Return 0x00 ; CDRF_DODEFAULT 66 | Case 0x030001: ; CDDS_SUBITEMPREPAINT 67 | Return 0x10 ; CDRF_NOTIFYPOSTPAINT 68 | Case 0x010001: ; CDDS_ITEMPREPAINT 69 | Return 0x20 ; CDRF_NOTIFYSUBITEMDRAW 70 | Case 0x000001: ; CDDS_PREPAINT 71 | Return 0x20 ; CDRF_NOTIFYITEMDRAW 72 | Default: 73 | Return 0x00 ; CDRF_DODEFAULT 74 | } 75 | } 76 | ; ------------------------------------------------------------------------------------------------------------------- 77 | BGR(Color, Default := "") { ; converts colors to BGR 78 | ; HTML Colors (BGR) 79 | Static HTML := {AQUA: 0xFFFF00, BLACK: 0x000000, BLUE: 0xFF0000, FUCHSIA: 0xFF00FF, GRAY: 0x808080, 80 | GREEN: 0x008000, LIME: 0x00FF00, MAROON: 0x000080, NAVY: 0x800000, OLIVE: 0x008080, 81 | PURPLE: 0x800080, RED: 0x0000FF, SILVER: 0xC0C0C0, TEAL: 0x808000, WHITE: 0xFFFFFF, 82 | YELLOW: 0x00FFFF} 83 | If IsInteger(Color) 84 | Return ((Color >> 16) & 0xFF) | (Color & 0x00FF00) | ((Color & 0xFF) << 16) 85 | Return (HTML.HasOwnProp(Color) ? HTML.%Color% : Default) 86 | } 87 | } -------------------------------------------------------------------------------- /Licenses/ColorPicker - LICENSE (YACS).txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Tyler J. Colby (Komrad Toast) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 4 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 5 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 6 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 11 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 12 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 13 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /Licenses/ColorPicker_ahk2 - LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TheArkive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Licenses/FontPicker_ahk2 - LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TheArkive 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Licenses/GuiCtrlTips - LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Licenses/JSON - LICENSE (ahk2_lib).txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 thqby 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Notify.ahk: -------------------------------------------------------------------------------- 1 | /******************************************************************************************** 2 | * Notify - Simplifies the creation and display of notification GUIs. 3 | * @author Martin Chartier (XMCQCX) 4 | * @date 2025/04/26 5 | * @version 1.10.1 6 | * @see {@link https://github.com/XMCQCX/NotifyClass-NotifyCreator GitHub} 7 | * @see {@link https://www.autohotkey.com/boards/viewtopic.php?f=83&t=129635 AHK Forum} 8 | * @license MIT license 9 | * @credits 10 | * - JSON by thqby, HotKeyIt. {@link https://github.com/thqby/ahk2_lib/blob/master/JSON.ahk GitHub} 11 | * - FrameShadow by Klark92. {@link https://www.autohotkey.com/boards/viewtopic.php?f=6&t=29117 AHK Forum} 12 | * - DrawBorder by ericreeves. {@link https://gist.github.com/ericreeves/fd426cc0457a5a47058e1ad1a29d9bd6 GitHub} 13 | * - CalculatePopupWindowPosition by lexikos. {@link https://www.autohotkey.com/boards/viewtopic.php?t=103459 AHK Forum} 14 | * - PlayWavConcurrent by Faddix. {@link https://www.autohotkey.com/boards/viewtopic.php?f=83&t=130425 AHK Forum} 15 | * - CreatePixel by TheDewd. {@link https://www.autohotkey.com/boards/viewtopic.php?t=7312 AHK Forum} 16 | * - Notify by gwarble. {@link https://www.autohotkey.com/board/topic/44870-notify-multiple-easy-tray-area-notifications-v04991 AHK Forum} 17 | * - Notify by the-Automator. {@link https://www.the-automator.com/downloads/maestrith-notify-class-v2 the-automator.com} 18 | * - WiseGui by SKAN. {@link https://www.autohotkey.com/boards/viewtopic.php?t=94044 AHK Forum} 19 | * @features 20 | * - Customize various parameters such as text, font, color, image, sound, animation, and more. 21 | * - Choose from built-in themes or create your own custom themes with Notify Creator. 22 | * - Rounded or edged corners. 23 | * - Position at different locations on the screen. 24 | * - Call a function when clicking on it. 25 | * - GUI stacking and repositioning. 26 | * - Multi-Monitor support. 27 | * - Multi-Script support. 28 | * @methods 29 | * - Show(title, msg, image, sound, callback, options) - Builds and displays a notification GUI. 30 | * - Destroy(param, force) - Destroys GUIs. 31 | * - param 32 | * - Window handle (hwnd) - Destroys the GUI with the specified window handle. 33 | * - tag - Destroys every GUI containing this tag across all scripts. 34 | * - 'oldest' or no param - Destroys the oldest GUI. 35 | * - 'latest' - Destroys the most recent GUI. 36 | * - force - When true, overrides Destroy GUI block (DGB) setting and forces GUI destruction. 37 | * - DestroyAllOnMonitorAtPosition(monitorNumber, position, force) - Destroys all GUIs on a specific monitor at a given position. 38 | * - DestroyAllOnAllMonitorAtPosition(position, force) - Destroys all GUIs on all monitors at a specific position. 39 | * - DestroyAllOnMonitor(monitorNumber, force) - Destroys all GUIs on a specific monitor. 40 | * - DestroyAll(force) - Destroys all GUIs. 41 | * - Exist(tag) - Checks if a GUI with the specified tag exists and returns the unique ID (HWND) of the first matching GUI. 42 | * - SetDefaultTheme(theme) - Set a different theme as the default. 43 | * - Sound(sound) - Plays a sound. 44 | ********************************************************************************************/ 45 | Class Notify { 46 | 47 | /******************************************************************************************** 48 | * @method Show(title, msg, image, sound, callback, options) 49 | * @description Builds and displays a notification GUI. 50 | * @param title Title 51 | * @param msg Message 52 | * @param image 53 | * - File path of an image. Supported file types: `.ico, .dll, .exe, .cpl, .png, .jpeg, .jpg, .gif, .bmp, .tif` 54 | * - String: `'icon!'`, `'icon?'`, `'iconx'`, `'iconi'` 55 | * - Icon from dll. For example: `'C:\Windows\System32\imageres.dll|icon19'` 56 | * - Image Handle. For example: `'HICON:' hwnd` 57 | * - Filename of an image located in "Pictures\Notify". 58 | * @param sound 59 | * - File path of a WAV file. 60 | * - String: `'soundx'`, `'soundi'` 61 | * - Filename of a WAV file located in "C:\Windows\Media" or "Music\Sounds". For example: `'Ding'`, `'tada'`, `'Windows Error'` etc. 62 | * - Use Notify Creator to view and play all available notification sounds. 63 | * @param callback The function(s) to execute when clicking on the GUI, image, or background image. 64 | * - Accepts a single function or an array of functions. 65 | * - If a single function is provided, it will be used as the callback when clicking on the GUI. 66 | * - If an array is provided: 67 | * - First item: Executed when clicking on the GUI. 68 | * - Second item: Executed when clicking on the image. 69 | * - Third item: Executed when clicking on the background image. 70 | * @param options For example: `'POS=TL DUR=6 IW=70 TF=Impact TS=42 TC=GREEN MC=blue BC=Silver STYLE=edge SHOW=Fade Hide=Fade@250'` 71 | * - The string is case-insensitive. 72 | * - The asterisk (*) indicates the default option. 73 | * - `THEME` - Built-in themes and user-created themes. 74 | * - Use Notify Creator to view all themes and their visual appearance. 75 | * - `STYLE` - Notification Appearance 76 | * - `Round` - Rounded corners* 77 | * - `Edge` - Edged corners 78 | * - `POS` - Position 79 | * - `TL` - Top left 80 | * - `TC` - Top center 81 | * - `TR` - Top right 82 | * - `CTL` - Center left 83 | * - `CT` - Center 84 | * - `CTR` - Center right 85 | * - `BL` - Bottom left 86 | * - `BC` - Bottom center 87 | * - `BR` - Bottom right* 88 | * - `Mouse` - Near the cursor. 89 | * - `DUR` - Display duration (in seconds). Set to 0 to keep it on the screen until left-clicking on the GUI or programmatically destroying it. `*8` 90 | * - `MON` - Monitor to display the GUI. AutoHotkey monitor numbers may differ from those in Windows Display settings or NVIDIA Control Panel. 91 | * - `Number` - A specific monitor number. 92 | * - `Active` - The monitor on which the active window is displayed. 93 | * - `Mouse` - The monitor on which the mouse is currently positioned. 94 | * - `Primary`* - The primary monitor. 95 | * - `IW` - Image width - `*32` 96 | * - `IH` - Image height `*-1` 97 | * - Image Dimensions (width and height) 98 | * - To resize the image while preserving its aspect ratio, specify -1 for one dimension and a positive number for the other. 99 | * - Specify 0 to retain the image's original width or height (DPI scaling does not apply). 100 | * - `BGIMG` - Background image. A valid image format. See the documentation for the image parameter. 101 | * - `BGIMGPOS` - Background image Position. Parameters for positioning and sizing the background image. For example: `ct Scale1.5`, `ctl w20 hStretch`, `tr w20 h-1 ofstx-10 ofsty10` 102 | * - Positions 103 | * - `TL` - Top left 104 | * - `TC` - Top center 105 | * - `TR` - Top right 106 | * - `CTL` - Center left 107 | * - `CT` - Center 108 | * - `CTR` - Center right 109 | * - `BL` - Bottom left 110 | * - `BC` - Bottom center 111 | * - `BR` - Bottom right 112 | * - `X` - Custom horizontal position. 113 | * - `Y` - Custom vertical position. 114 | * - `OFSTX` - Horizontal pixel offset. 115 | * - `OFSTY` - Vertical pixel offset. 116 | * - Display Modes 117 | * - `STRETCH`* - Stretches both the width and height of the image to fill the entire GUI. 118 | * - `SCALE` - Resizes the image proportionally. 119 | * - Image Dimensions (width and height) 120 | * - `STRETCH` - Adjusts either the width or height of the image to match the GUI dimension. 121 | * - To resize the image while preserving its aspect ratio, specify -1 for one dimension and a positive number for the other. 122 | * - Specify 0 to retain the image's original width or height (DPI scaling does not apply). 123 | * - Omit the W or H options to retain the image's original width or height (DPI scaling applies). 124 | * - `SHOW` and `HIDE` - Animation when showing and destroying the GUI. The duration, which is optional, can range from 1 to 2500 milliseconds. For example: `STYLE=EDGE SHOW=SlideNorth HIDE=SlideSouth@250` 125 | * - The round style is not compatible with most animations. To use all available animations, choose the edge style. If animations aren’t specified, the default animations for the style and position will be set. 126 | * - `None` 127 | * - `Fade` 128 | * - `Expand` 129 | * - `SlideEast` 130 | * - `SlideWest` 131 | * - `SlideNorth` 132 | * - `SlideSouth` 133 | * - `SlideNorthEast` 134 | * - `SlideNorthWest` 135 | * - `SlideSouthEast` 136 | * - `SlideSouthWest` 137 | * - `RollEast` 138 | * - `RollWest` 139 | * - `RollNorth` 140 | * - `RollSouth` 141 | * - `RollNorthEast` 142 | * - `RollNorthWest` 143 | * - `RollSouthEast` 144 | * - `RollSouthWest` 145 | * - `TF` - Title font `*Segoe UI` 146 | * - `TFO` - Title font options. `*Bold` For example: `tfo=underline italic strike` 147 | * - `TS` - Title size `*15` 148 | * - `TC` - Title color `*White` 149 | * - `TALI` - Title alignment 150 | * - `Left`* 151 | * - `Right` 152 | * - `Center` 153 | * - `MF` - Message font `*Segoe UI` 154 | * - `MFO` - Message font options. For example: `mfo=underline italic strike` 155 | * - `MS` - Message size `*12` 156 | * - `MC` - Message color `*0xEAEAEA` 157 | * - `MALI` - Message alignment 158 | * - `Left`* 159 | * - `Right` 160 | * - `Center` 161 | * - `BC` - Background color `*0x1F1F1F` 162 | * - `BDR` - Border. For example: `bdr=Aqua`,`bdr=Red,4` 163 | * - The round style's maximum border width is limited to 1 pixel, while the edge style allows up to 10 pixels. 164 | * - If the theme includes a border and the style is set to edge, you can specify only the border width like this: `bdr=,3` 165 | * - `0` - No border 166 | * - `1` - Border 167 | * - `Default` 168 | * - `Color` 169 | * - `Color,border width` - Specify color and width, separated by a comma. 170 | * - `WSTC` - WinSetTransColor. Not compatible with the round style, fade animation. For example: `style=edge bdr=0 bc=black wstc=black` {@link https://www.autohotkey.com/docs/v2/lib/WinSetTransColor.htm WinSetTransColor} 171 | * - `WSTP` - WinSetTransparent. Not compatible with the round style, fade animation. For example: `style=edge wstp=120` {@link https://www.autohotkey.com/docs/v2/lib/WinSetTransparent.htm WinSetTransparent} 172 | * - `PAD` - Padding, margins, and spacing. Accepts comma-separated values or explicit key-value pairs. For examples: `pad=0,0,16,16,16,16,8,10`, `pad=,10`, `padx=10 pady=10` 173 | * - `PadX` and `PadY` can range from 0 to 25. The others can range from 0 to 999. 174 | * - `PadX` - Padding between the GUI's left or right edge and the screen's edge. 175 | * - `PadY` - Padding between the GUI's top or bottom edge and the screen's edge or taskbar. 176 | * - `gmT` - Top margin of the GUI. 177 | * - `gmB` - Bottom margin of the GUI. 178 | * - `gmL` - Left margin of the GUI. 179 | * - `gmR` - Right margin of the GUI. 180 | * - `SpX` - Horizontal spacing between the right side of the image and other controls. 181 | * - `SpY` - Vertical spacing between the title, message, and progress bar. 182 | * - `MAXW` - Maximum width of the GUI (excluding image width and margins). 183 | * - `WIDTH` - Fixed width of the GUI (excluding the image width and margins). 184 | * - `MINH` - Minimum height of the GUI. 185 | * - `PROG` - Progress bar. For example: `prog=1`, `prog=h40 cGreen`, `prog=w400` {@link https://www.autohotkey.com/docs/v2/lib/GuiControls.htm#Progress Progress Options} 186 | * - `TAG` - Marker to identify a GUI. The Destroy method accepts a handle or a tag, it destroys every GUI containing this tag across all scripts. 187 | * - `DGC` - Destroy GUI click. Allow or prevent the GUI from being destroyed when clicked. 188 | * - `0` - Clicking on the GUI does not destroy it. 189 | * - `1` - Clicking on the GUI destroys it.* 190 | * - `DG` - Destroy GUIs before showing the new GUI. 191 | * - `0` - Do not destroy GUIs.* 192 | * - `1` - Destroy all GUIs on the monitor option at the position option. 193 | * - `2` - Destroy all GUIs on all monitors at the position option. 194 | * - `3` - Destroy all GUIs on the monitor option. 195 | * - `4` - Destroy all GUIs. 196 | * - `5` - Destroy all GUIs containing the tag. For example: `dg=5 tag=myTAG` 197 | * - `DGB` - Destroy GUI block. Prevents the GUI from being destroyed unless the force parameter of the destroy methods is set to true. 198 | * It does not prevent GUI destruction after the duration expires or when the GUI is clicked. In most cases, you’ll likely want to set 199 | * both the Duration (DUR) and Destroy GUI Click (DGC) to 0. For example: `dgb=1 dur=0 dgc=0` 200 | * - `0` - The GUI can be destroyed without setting the force parameter to true. 201 | * - `1` - The GUI cannot be destroyed unless the force parameter is set to true. 202 | * - `DGA` - Destroy GUI animation. Enables or disables the hide animation when destroying the GUI using the destroy methods. 203 | * - `0` - No animation. 204 | * - `1` - Animation enabled. 205 | * - `OPT` - Sets various options and styles for the appearance and behavior of the window. `*+Owner -Caption +AlwaysOnTop +E0x08000000` {@link https://www.autohotkey.com/docs/v2/lib/Gui.htm#Opt GUI Opt} 206 | * @returns Map object 207 | ********************************************************************************************/ 208 | static Show(title:='', msg:='', image:='', sound:='', callback:='', options:='') => this._Show(title, msg, image, sound, callback, options) 209 | 210 | static __New() 211 | { 212 | this.mDefaults := this.MapCI().Set( 213 | 'theme', 'Default', 214 | 'mon', 'Primary', 215 | 'pos', 'BR', 216 | 'dur', 8, 217 | 'style', 'Round', 218 | 'ts', 15, 219 | 'tc', 'White', 220 | 'tf', 'Segoe UI', 221 | 'tfo', 'norm Bold', 222 | 'tali', 'Left', 223 | 'ms', 12, 224 | 'mc', '0xEAEAEA', 225 | 'mf', 'Segoe UI', 226 | 'mfo', 'norm', 227 | 'mali', 'Left', 228 | 'bc', '0x1F1F1F', 229 | 'bdr', 'Default', 230 | 'sound', 'None', 231 | 'image', 'None', 232 | 'iw', 32, 233 | 'ih', -1, 234 | 'bgImg', 'None', 235 | 'bgImgPos', 'Stretch', 236 | 'pad', ',,16,16,16,16,8,10', 237 | 'width', '', 238 | 'minH', '', 239 | 'maxW', '', 240 | 'prog', '', 241 | 'tag', '', 242 | 'dgc', 1, 243 | 'dg', 0, 244 | 'dgb', 0, 245 | 'dga', 0, 246 | 'wstc', '', 247 | 'wstp', '', 248 | 'opt', '+Owner -Caption +AlwaysOnTop +E0x08000000', 249 | ) 250 | 251 | this.mThemesStrings := this.MapCI().Set( 252 | 'Light', 'tc=Black mc=Black bc=White', 253 | 'Dark', 'tc=White mc=0xEAEAEA bc=0x1F1F1F', 254 | 'Matrix', 'tc=Lime mc=0x00FF7F bc=Black bdr=0x00FF7F tf=Consolas mf=Lucida Console', 255 | 'Cyberpunk', 'tc=0xFF005F mc=Aqua bc=0x0D0D0D bdr=Aqua tf=Consolas mf=Lucida Console', 256 | 'Cybernetic', 'tc=Aqua mc=0xFF005F bc=0x1A1A1A bdr=0xFF005F tf=Lucida Console mf=Consolas', 257 | 'Synthwave', 'tc=Fuchsia mc=Aqua bc=0x1A0E2F bdr=Aqua tf=Consolas mf=Arial', 258 | 'Dracula', 'tc=0xFF79C6 mc=0x8BE9FD bc=0x282A36 bdr=0x8BE9FD tf=Consolas mf=Arial', 259 | 'Monokai', 'tc=0xF8F8F2 mc=0xA6E22E bc=0x272822 bdr=0xE8F7C8 tf=Lucida Console mf=Tahoma', 260 | 'Solarized Dark', 'tc=0xD9A300 mc=0x9FADAE bc=0x002B36 bdr=0x9FADAE tf=Consolas mf=Calibri', 261 | 'Atomic', 'tc=0xE49013 mc=0xDFCA9B bc=0x1F1F1F bdr=0xDFCA9B tf=Consolas mf=Lucida Console', 262 | 'PCB', 'tc=0xCCAA00 mc=0x00CC00 bc=0x002200 bdr=0x00CC00 tf=Consolas mf=Arial', 263 | 'Deep Sea', 'tc=0x00CED1 mc=0x5F9EA0 bc=0x002B36 bdr=0x4682B4 tf=Arial mf=Verdana', 264 | 'Firewatch', 'tc=0xFF4500 mc=0xFF8C00 bc=0x2C1B18 bdr=0xFFA500 tf=Verdana mf=Georgia', 265 | 'Nord', 'tc=0xD8DEE9 mc=0xECEFF4 bc=0x2E3440 bdr=0x88C0D0 tf=Calibri mf=Lucida Console', 266 | 'Ivory', 'tc=0x333333 mc=0x666666 bc=0xFFFFF0 bdr=0xCCCCCC tf=Georgia mf=Tahoma', 267 | 'Neon', 'tc=Yellow mc=Fuchsia bc=Black bdr=Fuchsia tf=Consolas mf=Lucida Console', 268 | 'Frost', 'tc=Aqua mc=0xE0FFFF bc=0x002233 bdr=Aqua tf=Calibri mf=Arial', 269 | 'Charcoal', 'tc=0xD3D3D3 mc=0xA9A9A9 bc=0x2F2F2F bdr=0xA9A9A9 tf=Tahoma mf=Consolas', 270 | 'Zenburn', 'tc=0xDECEA3 mc=0xD3D3C4 bc=0x3F3F3F bdr=0x839496 tf=Consolas mf=Calibri', 271 | 'Matcha', 'tc=0xD5D1B5 mc=0x87A9A2 bc=0x273136 bdr=0xD5D1B5', 272 | 'Monaspace', 'tc=0x79C1FD mc=0xD1A7FE bc=0x0D1116 bdr=0xD1A7FE', 273 | 'Milky Way', 'tc=0x9370DB mc=0xC0CAD7 bc=0x0D1B2A bdr=0x9370DB tf=Trebuchet MS mf=Calibri', 274 | 'Reptilian', 'tc=Yellow mc=0xF0FFF0 bc=0x004D00 bdr=Yellow', 275 | 'Aurora', 'tc=0x47F0AC mc=0xEAEAEA bc=0x0C1631 bdr=0x47F0AC', 276 | 'Venom', 'tc=0xF9EA2C mc=0xFAF2A4 bc=0x317140 tf=Segoe UI mf=Segoe UI bdr=0x86EE99', 277 | 'Forum', 'tc=0x3F5770 mc=0x272727 bc=0xDFDFDF bdr=0x686868', 278 | 'Cappuccino', 'tc=0x6F4E37 mc=0x886434 bc=0xFFF8DC bdr=0x886434 tf=Trebuchet MS mf=Times New Roman', 279 | 'Earthy', 'tc=0xF5FFFA mc=0x4DCA22 bc=0x3E2723 bdr=0x41A91D tf=Lucida Console mf=Arial', 280 | 'Rust', 'tc=0xFFC107 mc=0xF5DEB3 bc=0x8F3209 bdr=0xF5DEB3 tf=Georgia mf=Arial', 281 | 'Galactic', 'tc=0xFFD700 mc=White bc=Black bdr=White tf=Verdana mf=Arial', 282 | 'Steampunk', 'tc=0xFFD700 mc=0xB87333 bc=0x3E2723 bdr=0xFFD700 tf=Trebuchet MS mf=Times New Roman', 283 | 'Pastel', 'tc=0xFF69B4 mc=0x0072E3 bc=0xFFF0F5 bdr=0x0072E3 tf=Calibri', 284 | 'Nature', 'tc=0x2E8B57 mc=0x4A5E4A bc=0xE8F3E8 bdr=0x4A5E4A tf=Trebuchet MS', 285 | 'Pink Light', 'tc=0xFF1493 mc=0xFF69B4 bc=0xFFE4E1 bdr=0xFF69B4 tf=Comic Sans MS mf=Verdana', 286 | 'Pink Dark', 'tc=0xFF1493 mc=0xFF69B4 bc=0x1F1F1F bdr=0xFF69B4 tf=Comic Sans MS mf=Verdana', 287 | 'Sticky', 'tc=Black mc=0x333333 bc=0xF9E15B bdr=0x5F5103 tf=Arial mf=Verdana', 288 | 'Chestnut', 'tc=0xF8F8E8 mc=0xC5735F bc=0x282828 bdr=0xF8F8E8', 289 | 'Amber', 'tc=0xFFF8DC mc=0xFFBF00 bc=0x292929 bdr=0xFFF8DC', 290 | 'Volcanic', 'tc=0xFE5F55 mc=0xFFC857 bc=0x1D1D1D bdr=0xFE5F55 tf=Lucida Console mf=Tahoma', 291 | 'Amethyst', 'tc=0xD6ADFF mc=0xA56FFF bc=0x2A1B3D bdr=0xA56FFF tf=Trebuchet MS mf=Consolas', 292 | 'Cosmos', 'tc=0x87CEEB mc=0xE6E6FA bc=0x0C0032 bdr=0x87CEEB tf=Consolas mf=Lucida Console', 293 | 'OK', 'tc=Black mc=Black bc=0x49C149 bdr=0x336F50', 294 | 'OKLight', 'tc=0x52CB43 mc=Black bc=0xF1F8F4 bdr=0x52CB43', 295 | 'OKDark', 'tc=0x52CB43 mc=0xEAEAEA bc=0x1F1F1F bdr=0x52CB43 ', 296 | 'x', 'tc=White mc=0xEAEAEA bc=0xC61111 bdr=0xEAEAEA image=iconx', 297 | 'i', 'tc=White mc=0xEAEAEA bc=0x4682B4 bdr=0xEAEAEA image=iconi', 298 | '!', 'tc=Black mc=Black bc=0xFFD953 bdr=0x6F5600 image=icon!', 299 | '?', 'tc=White mc=0xEAEAEA bc=0x4682B4 bdr=0xEAEAEA image=icon?', 300 | 'xLight', 'tc=0xC61111 mc=Black bc=0xFBEFEB bdr=0xC61111 image=iconx', 301 | '!Light', 'tc=0xE1AA04 mc=Black bc=0xFEF8EB bdr=0xE1AA04 image=icon!', 302 | 'iLight', 'tc=0x2543AC mc=Black bc=0xE7EFFA bdr=0x2543AC image=iconi', 303 | '?Light', 'tc=0x2543AC mc=Black bc=0xE7EFFA bdr=0x2543AC image=icon?', 304 | 'xDark', 'tc=0xC61111 mc=0xEAEAEA bc=0x1F1F1F bdr=0xC61111 image=iconx', 305 | '!Dark', 'tc=0xDEA309 mc=0xEAEAEA bc=0x1F1F1F bdr=0xDEA309 image=icon!', 306 | 'iDark', 'tc=0x41A5EE mc=0xEAEAEA bc=0x1F1F1F bdr=0x41A5EE image=iconi', 307 | '?Dark', 'tc=0x41A5EE mc=0xEAEAEA bc=0x1F1F1F bdr=0x41A5EE image=icon?', 308 | ) 309 | 310 | this.mAHKcolors := this.MapCI().Set( 311 | 'Black', '0x000000', 'Silver', '0xC0C0C0', 312 | 'Gray', '0x808080', 'White', '0xFFFFFF', 313 | 'Maroon', '0x800000', 'Red', '0xFF0000', 314 | 'Purple', '0x800080', 'Fuchsia','0xFF00FF', 315 | 'Green', '0x008000', 'Lime', '0x00FF00', 316 | 'Olive', '0x808000', 'Yellow', '0xFFFF00', 317 | 'Navy', '0x000080', 'Blue', '0x0000FF', 318 | 'Teal', '0x008080', 'Aqua', '0x00FFFF' 319 | ) 320 | 321 | this.mAW := this.MapCI().Set( 322 | 'none', '', 323 | 'fade', '0x80000', ; AW_BLEND 324 | 'expand', '0x00010', ; AW_CENTER 325 | 'slideEast', '0x40001', ; AW_SLIDE | AW_HOR_POSITIVE 326 | 'slideWest', '0x40002', ; AW_SLIDE | AW_HOR_NEGATIVE 327 | 'slideNorth', '0x40008', ; AW_SLIDE | AW_VER_NEGATIVE 328 | 'slideSouth', '0x40004', ; AW_SLIDE | AW_VER_POSITIVE 329 | 'slideNorthEast', '0x40009', ; AW_SLIDE | AW_VER_NEGATIVE | AW_HOR_POSITIVE 330 | 'slideNorthWest', '0x4000A', ; AW_SLIDE | AW_VER_NEGATIVE | AW_HOR_NEGATIVE 331 | 'slideSouthEast', '0x40005', ; AW_SLIDE | AW_VER_POSITIVE | AW_HOR_POSITIVE 332 | 'slideSouthWest', '0x40006', ; AW_SLIDE | AW_VER_POSITIVE | AW_HOR_NEGATIVE 333 | 'rollEast', '0x00001', ; AW_HOR_POSITIVE 334 | 'rollWest', '0x00002', ; AW_HOR_NEGATIVE 335 | 'rollNorth', '0x00008', ; AW_VER_NEGATIVE 336 | 'rollSouth', '0x00004', ; AW_VER_POSITIVE 337 | 'rollNorthEast', '0x00009', ; ROLL_DIAG_BL_TO_TR 338 | 'rollNorthWest', '0x0000a', ; ROLL_DIAG_BR_TO_TL 339 | 'rollSouthEast', '0x00005', ; ROLL_DIAG_TL_TO_BR 340 | 'rollSouthWest', '0x00006' ; ROLL_DIAG_TR_TO_BL 341 | ) 342 | 343 | this.mNotifyGUIs := this.MapCI() 344 | this.padG := 10 ; Pad between GUIs 345 | this.arrAnimDurRange := [1,2500] 346 | this.bdrWdefaultEdge := 2 347 | this.padXYdefaultEdge := 0 348 | this.padXYdefaultRound := 10 349 | this.arrBdrWrange := [1,10] 350 | this.arrPadXYrange := [0,25] 351 | this.arrPadRange := [0,999] 352 | this.arrFonts := Array() 353 | this.isTooManyFonts := false 354 | 355 | ;============================================== 356 | 357 | this.pathImagesFolder := RegRead('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', 'My Pictures') '\Notify' 358 | this.arrImageExt := ['ico', 'dll', 'exe', 'cpl', 'png', 'jpeg', 'jpg', 'gif', 'bmp', 'tif'] 359 | this.strImageExt := this.ArrayToString(this.arrImageExt, '|') 360 | this.mImages := this.MapCI().Set('icon!', 2, 'icon?', 3, 'iconx', 4, 'iconi', 5) 361 | 362 | Loop Files this.pathImagesFolder '\*.*' 363 | if RegExMatch(A_LoopFileExt, 'i)^(' this.strImageExt ')$') 364 | SplitPath(A_LoopFilePath,,,, &fileName), this.mImages[fileName] := A_LoopFilePath 365 | 366 | ;============================================== 367 | 368 | this.pathSoundsFolder := RegRead('HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders', 'My Music') '\Sounds' 369 | this.mSounds := this.MapCI().Set('soundx', '*16', 'soundi', '*64') 370 | 371 | for path in [A_WinDir '\Media', this.pathSoundsFolder] 372 | Loop Files path '\*.wav' 373 | SplitPath(A_LoopFilePath,,,, &fileName), this.mSounds[fileName] := A_LoopFilePath 374 | 375 | ;============================================== 376 | 377 | this.mThemes := this.MapCI() 378 | this.mThemes['Default'] := this.MapCI() 379 | 380 | for theme, str in this.mThemesStrings 381 | this.OptionsStringToMap(this.mThemes[theme] := this.MapCI(), str) 382 | 383 | ;============================================== 384 | 385 | this.mOrig_mDefaults := this.MapCI() 386 | 387 | for key, value in this.mDefaults 388 | this.mOrig_mDefaults[key] := value 389 | 390 | ;============================================== 391 | 392 | this.mOrig_mThemes := this.MapCI() 393 | 394 | for theme, mTheme in this.mThemes { 395 | this.mOrig_mThemes[theme] := this.MapCI() 396 | 397 | for key, value in mTheme 398 | this.mOrig_mThemes[theme][key] := value 399 | } 400 | 401 | ;============================================== 402 | 403 | sourceFile := A_IsCompiled ? A_ScriptFullPath : A_LineFile 404 | SplitPath(sourceFile,, &pathDir) 405 | 406 | if (FileExist(pathDir '\Preferences.json')) { 407 | objFile := FileOpen(pathDir '\Preferences.json', 'r', 'UTF-8') 408 | mJSON := _JSON_thqby.parse(objFile.Read(), false, true) 409 | objFile.Close() 410 | 411 | if mJSON.Has('mDefaults') 412 | for key, value in mJSON['mDefaults'] 413 | this.mDefaults[key] := value 414 | 415 | if (mJSON.Has('mThemes')) { 416 | for key, value in mJSON['mThemes'] { 417 | this.mThemes[key] := this.MapCI() 418 | 419 | for k, v in value 420 | this.mThemes[key][k] := v 421 | } 422 | } 423 | } 424 | 425 | ;============================================== 426 | 427 | if !this.mThemes.Has(this.mDefaults['theme']) 428 | this.mDefaults['theme'] := 'Default' 429 | 430 | ;============================================== 431 | 432 | for value in ['mThemes', 'mOrig_mThemes'] { 433 | for theme, mTheme in this.%value% { 434 | if (theme != 'default') { 435 | arrKeyDefined := Array() 436 | 437 | for key, v in mTheme 438 | arrKeyDefined.Push(key) 439 | 440 | mTheme['arrKeyDefined'] := arrKeyDefined 441 | } 442 | } 443 | } 444 | 445 | ;============================================== 446 | 447 | for theme, mTheme in this.mThemes 448 | this.ParseBorderOption(mTheme) 449 | } 450 | 451 | ;============================================================================================ 452 | 453 | static _Show(title:='', msg:='', image:='', sound:='', callback:='', options:='') 454 | { 455 | static gIndex := 0 456 | this.OptionsStringToMap(m := this.MapCI(), options) 457 | 458 | if !m.Has('theme') || !this.mThemes.Has(m['theme']) 459 | m['theme'] := this.mDefaults['theme'] 460 | 461 | if image 462 | m['image'] := image 463 | 464 | if sound 465 | m['sound'] := sound 466 | 467 | this.SetThemeSettings(m, this.mThemes[m['theme']]) 468 | this.SetDefault_MiscValues(m) 469 | this.ParseAnimationOption(m) 470 | this.SetAnimationDefault(m) 471 | this.ParsePadOption(m) 472 | this.SetPadDefault(m) 473 | this.ParseBorderOption(m) 474 | this.SetBorderOption(m) 475 | 476 | if !title && !msg && m['image'] = 'none' 477 | return 478 | 479 | ;============================================== 480 | 481 | switch { 482 | case (m['mon'] = 'mouse' || m['pos'] = 'mouse'): 483 | m['mon'] := this.MonitorGetMouseIsIn() 484 | case m['mon'] = 'active': 485 | m['mon'] := this.MonitorGetWindowIsIn('A') 486 | case (m['mon'] = 'primary' || m['mon'] < 1 || m['mon'] > MonitorGetCount()): 487 | m['mon'] := MonitorGetPrimary() 488 | } 489 | 490 | switch m['dg'] { 491 | case 1: this.DestroyAllOnMonitorAtPosition(m['mon'], m['pos']) 492 | case 2: this.DestroyAllOnAllMonitorAtPosition(m['pos']) 493 | case 3: this.DestroyAllOnMonitor(m['mon']) 494 | case 4: this.DestroyAll() 495 | case 5: m['tag'] && this.Destroy(m['tag']) 496 | } 497 | 498 | ;============================================== 499 | 500 | g := Gui(m['opt'], 'NotifyGUI_' m['dgb'] '_' m['mon'] '_' m['pos'] '_' m['style'] '_' m['bdrC'] '_' m['bdrW'] '_' m['padY'] '_' A_Now A_MSec (m['tag'] && '_' m['tag'])) 501 | g.BackColor := m['bc'] 502 | g.MarginX := m['gmL'] + m['bdrW'] 503 | g.MarginY := m['gmT'] + m['bdrW'] 504 | g.gIndex := ++gIndex 505 | m['hwnd'] := g.handle := g.hwnd 506 | 507 | for value in ['pos', 'mon', 'hideHex', 'hideDur', 'tag', 'dga'] 508 | g.%value% := m[value] 509 | 510 | ;============================================== 511 | 512 | MonitorGetWorkArea(m['mon'], &monWALeft, &monWATop, &monWARight, &monWABottom) 513 | monWAwidth := Abs(monWARight - monWALeft) 514 | monWAheight := Abs(monWABottom - monWATop) 515 | visibleScreenWidth := this.DpiScale(monWAwidth) 516 | marginsWidth := (g.MarginX + m['gmR'] + m['bdrW']) 517 | 518 | if (mPicDimensions := this.GetImageDimensions(m['image'], 'x' monWALeft ' y' monWATop, ' w' m['iw'] ' h' m['ih'])) 519 | pic_spX_mgn_Width := mPicDimensions['ctrlW'] + m['spX'] + marginswidth 520 | 521 | if title 522 | titleCtrlW := this.GetTextWidth(title, m['tf'], m['ts'], m['tfo'], monWALeft, monWATop) 523 | 524 | if msg 525 | msgCtrlW := this.GetTextWidth(msg, m['mf'], m['ms'], m['mfo'], monWALeft, monWATop) 526 | 527 | if title && (titleCtrlW + (pic_spX_mgn_Width ?? marginsWidth)) > visibleScreenWidth 528 | titleWidth := visibleScreenWidth - m['padX']*2 - (pic_spX_mgn_Width ?? marginsWidth) 529 | 530 | if msg && (msgCtrlW + (pic_spX_mgn_Width ?? marginsWidth)) > visibleScreenWidth 531 | msgWidth := visibleScreenWidth - m['padX']*2 - (pic_spX_mgn_Width ?? marginsWidth) 532 | 533 | if m['prog'] && RegExMatch(m['prog'], 'i)\bw(\d+)\b', &match_width) 534 | progUserW := match_width[1] 535 | 536 | if (m['prog'] && IsSet(progUserW)) && ((progUserW + (pic_spX_mgn_Width ?? marginsWidth)) > (visibleScreenWidth)) 537 | progWidth := visibleScreenWidth - m['padX']*2 - (pic_spX_mgn_Width ?? marginsWidth) 538 | 539 | bodyWidth := Max( 540 | (title ? (titleWidth ?? titleCtrlW ?? 0) : 0), 541 | (msg ? (msgWidth ?? msgCtrlW ?? 0) : 0), 542 | (m['prog'] ? (progWidth ?? progUserW ?? 0) : 0) 543 | ) 544 | 545 | switch { 546 | case m['width']: bodyWidth := m['width'] 547 | case (m['maxW'] && m['maxW'] < bodyWidth): bodyWidth := m['maxW'] 548 | } 549 | 550 | if (m['bgImg'] != 'none') 551 | if mPicDim := this.GetImageDimensions(m['bgImg'], 'x' monWALeft ' y' monWATop,, false) 552 | m['bgPic'] := g.Add('Picture', 'x0 y0') 553 | 554 | ;============================================== 555 | if (m['image'] != 'none') { 556 | switch { 557 | case this.isInternalString(m['image']): 558 | try m['pic'] := g.Add('Picture', 'xm ym w' m['iw'] ' h' m['ih'] ' Icon' this.mImages[m['image']] ' BackgroundTrans', A_WinDir '\system32\user32.dll') 559 | 560 | case this.isInternalImage(m['image']): 561 | try m['pic'] := g.Add('Picture', 'xm ym w' m['iw'] ' h' m['ih'] ' BackgroundTrans', this.mImages[m['image']]) 562 | 563 | case arrRegExMatch := this.isIconResourceFile(m['image']): 564 | try m['pic'] := g.Add('Picture', 'xm ym w' m['iw'] ' h' m['ih'] ' Icon' arrRegExMatch[2] ' BackgroundTrans', arrRegExMatch[1]) 565 | 566 | case this.isImagePathOrHandle(m['image']): 567 | try m['pic'] := g.Add('Picture', 'xm ym w' m['iw'] ' h' m['ih'] ' BackgroundTrans', m['image']) 568 | } 569 | } 570 | 571 | if (title) { 572 | this.SetFont(g, m['ts'], m['tc'], m['tfo'], m['tf']) 573 | 574 | switch { 575 | case m.Has('bgPic') && !m.Has('pic'): titlePosX := 'xm ym' 576 | case m.Has('pic'): titlePosX := 'x+' m['spX'] 577 | } 578 | 579 | m['title'] := g.Add('Text', m['tali'] ' ' (titlePosX ?? '') ' w' bodyWidth ' BackgroundTrans', title) 580 | } 581 | 582 | if (m['prog']) { 583 | switch { 584 | case !IsSet(progUserW): m['prog'] := m['prog'] ' w' bodyWidth 585 | case IsSet(progUserW): m['prog'] := progUserW > bodyWidth ? RegExReplace(m['prog'], 'w\d+', 'w' bodyWidth) : m['prog'] 586 | } 587 | 588 | g.MarginY := title ? m['spY'] : m['gmT'] + m['bdrW'] 589 | 590 | switch { 591 | case m.Has('bgPic') && !title && !m.Has('pic'): progPosXY := 'xm ym' 592 | case !title && m.Has('pic'): progPosXY := 'x+' m['spX'] 593 | } 594 | 595 | m['prog'] := g.Add('Progress', (progPosXY ?? '') ' ' m['prog']) 596 | } 597 | 598 | if (msg) { 599 | g.MarginY := title || m['prog'] ? m['spY'] : m['gmT'] + m['bdrW'] 600 | this.SetFont(g, m['ms'], m['mc'], m['mfo'], m['mf']) 601 | 602 | switch { 603 | case m.Has('bgPic') && !title && !m['prog'] && !m.Has('pic'): msgPosXY := 'xm ym' 604 | case !title && !m['prog'] && m.Has('pic'): msgPosXY := 'x+' m['spX'] ' ym' 605 | } 606 | 607 | m['msg'] := g.Add('Text', m['mali'] ' ' (msgPosXY ?? '') ' w' bodyWidth ' BackgroundTrans', msg) 608 | } 609 | 610 | g.MarginX := m['gmR'] + m['bdrW'] 611 | g.MarginY := m['gmB'] + m['bdrW'] 612 | 613 | g.Show('hide') 614 | WinGetPos(&gX, &gY, &gW, &gH, g) 615 | 616 | if (m['minH'] && (gH < m['minH'])) 617 | g.Show('hide h' m['minH']), WinGetPos(&gX, &gY, &gW, &gH, g) 618 | 619 | m['gW'] := gW 620 | m['gH'] := gH 621 | 622 | ;============================================== 623 | 624 | if (m.Has('bgPic')) { 625 | try { 626 | for value in ['w', 'h'] { 627 | switch { 628 | case RegExMatch(m['bgImgPos'], 'i)\b' value '0\b'): 629 | continue 630 | 631 | case RegExMatch(m['bgImgPos'], 'i)\b' value 'stretch\b') || m['bgImgPos'] = 'stretch': 632 | mPicDim['ctrl' value] := m['g' value] 633 | 634 | case RegExMatch(m['bgImgPos'], 'i)\b' value '-1\b'): 635 | mPicDim['ctrl' value] := -1 636 | 637 | case RegExMatch(m['bgImgPos'], 'i)\b' value '\K\d+\b', &matchWH): 638 | mPicDim['ctrl' value] := matchWH[0] * (A_ScreenDPI / 96) 639 | 640 | default: mPicDim['ctrl' value] *= (A_ScreenDPI / 96) 641 | } 642 | } 643 | 644 | switch { 645 | case mPicDim['ctrlW'] = -1: mPicDim['ctrlW'] := Round(mPicDim['ctrlH'] * mPicDim['aspectRatio']) 646 | case mPicDim['ctrlH'] = -1: mPicDim['ctrlH'] := Round(mPicDim['ctrlW'] / mPicDim['aspectRatio']) 647 | } 648 | 649 | if RegExMatch(m['bgImgPos'], 'i)\bscale\K([\d\.]+)\b', &matchScale) 650 | for value in ['w', 'h'] 651 | mPicDim['ctrl' value] *= matchScale[0] 652 | 653 | switch { 654 | case m['bgImgPos'] ~= 'i)\btl\b' : m['bgImgPosX'] := 0, m['bgImgPosY'] := 0 655 | case m['bgImgPos'] ~= 'i)\btc\b' : m['bgImgPosX'] := this.DpiScale(m['gW']/2 - mPicDim['ctrlW']/2) 656 | case m['bgImgPos'] ~= 'i)\btr\b' : m['bgImgPosX'] := this.DpiScale(m['gW'] - mPicDim['ctrlW']) 657 | case m['bgImgPos'] ~= 'i)\bctl\b' : m['bgImgPosY'] := this.DpiScale(m['gH']/2 - mPicDim['ctrlH']/2) 658 | case m['bgImgPos'] ~= 'i)\bct\b' : m['bgImgPosX'] := this.DpiScale(m['gW']/2 - mPicDim['ctrlW']/2), m['bgImgPosY'] := this.DpiScale(m['gH']/2 - mPicDim['ctrlH']/2) 659 | case m['bgImgPos'] ~= 'i)\bctr\b' : m['bgImgPosY'] := this.DpiScale(m['gH']/2 - mPicDim['ctrlH']/2), m['bgImgPosX'] := this.DpiScale(m['gW'] - mPicDim['ctrlW']) 660 | case m['bgImgPos'] ~= 'i)\bbl\b' : m['bgImgPosY'] := this.DpiScale(m['gH'] - mPicDim['ctrlH']) 661 | case m['bgImgPos'] ~= 'i)\bbc\b' : m['bgImgPosX'] := this.DpiScale(m['gW']/2 - mPicDim['ctrlW']/2), m['bgImgPosY'] := this.DpiScale(m['gH'] - mPicDim['ctrlH']) 662 | case m['bgImgPos'] ~= 'i)\bbr\b' : m['bgImgPosX'] := this.DpiScale(m['gW'] - mPicDim['ctrlW']), m['bgImgPosY'] := this.DpiScale(m['gH'] - mPicDim['ctrlH']) 663 | } 664 | 665 | for value in ['x', 'y'] { 666 | m['bgImgPos' value] := m.Get('bgImgPos' value, 0) 667 | 668 | switch { 669 | case RegExMatch(m['bgImgPos'], 'i)\b' value '\K-?\d+\b', &matchPos): 670 | m['bgImgPos' value] := matchPos[0] 671 | 672 | case RegExMatch(m['bgImgPos'], 'i)\bofst' value '\K-?\d+\b', &matchOfst): 673 | m['bgImgPos' value] += matchOfst[0] 674 | } 675 | } 676 | 677 | switch { 678 | case this.isInternalString(m['bgImg']): 679 | m['bgPic'].Value := '*w' mPicDim['ctrlW'] ' *h' mPicDim['ctrlH'] ' *Icon' this.mImages[m['bgImg']] ' ' A_WinDir '\system32\user32.dll' 680 | 681 | case this.isInternalImage(m['bgImg']): 682 | m['bgPic'].Value := '*w' mPicDim['ctrlW'] ' *h' mPicDim['ctrlH'] ' ' this.mImages[m['bgImg']] 683 | 684 | case arrRegExMatch := this.isIconResourceFile(m['bgImg']): 685 | m['bgPic'].Value := '*w' mPicDim['ctrlW'] ' *h' mPicDim['ctrlH'] ' *Icon' arrRegExMatch[2] ' ' arrRegExMatch[1] 686 | 687 | case this.isImagePathOrHandle(m['bgImg']): 688 | m['bgPic'].Value := '*w' mPicDim['ctrlW'] ' *h' mPicDim['ctrlH'] ' ' m['bgImg'] 689 | 690 | case this.isValidColor(m['bgImg']): 691 | m['bgPic'].Value := '*w' mPicDim['ctrlW'] ' *h' mPicDim['ctrlH'] ' hBitmap: ' this.CreatePixel(m['bgImg']) 692 | } 693 | 694 | m['bgPic'].Move(m['bgImgPosX'], m['bgImgPosY']) 695 | } 696 | } 697 | 698 | ;============================================== 699 | 700 | clickArea := g.Add('Text', 'x0 y0 w' gW ' h' gH ' BackgroundTrans') 701 | 702 | switch Type(callback) { 703 | case 'Func', 'BoundFunc': clickArea.OnEvent('Click', callback) 704 | case 'Array': 705 | { 706 | if callback.Has(1) && callback[1] != '' 707 | clickArea.OnEvent('Click', callback[1]) 708 | 709 | for value in ['pic', 'bgPic'] 710 | if m.Has(value) && callback.Has(A_Index+1) && callback[A_Index+1] != '' 711 | m[value].OnEvent('Click', callback[A_Index+1]) 712 | } 713 | } 714 | 715 | if m['dgc'] 716 | clickArea.OnEvent('Click', this.gDestroy.Bind(this, g, 'clickArea')) 717 | 718 | g.OnEvent('Close', this.gDestroy.Bind(this, g, 'close')) 719 | g.boundFuncTimer := this.gDestroy.Bind(this, g, 'timer') 720 | 721 | if m['sound'] != 'none' 722 | this.Sound(m['sound']) 723 | 724 | ;============================================== 725 | 726 | switch m['pos'], false { 727 | case 'bl', 'bc', 'br': minMaxPosY := monWABottom 728 | case 'tl', 'tc', 'tr', 'ctl', 'ct', 'ctr': minMaxPosY := monWATop 729 | } 730 | 731 | mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx') 732 | 733 | for id in WinGetList('i)^NotifyGUI_[0-1]_' m['mon'] '_' m['pos'] '_ ahk_class AutoHotkeyGUI') { 734 | try { 735 | WinGetPos(, &guiY,, &guiH, 'ahk_id ' id) 736 | switch m['pos'], false { 737 | case 'bl', 'bc', 'br': minMaxPosY := Min(minMaxPosY, guiY) 738 | case 'tl', 'tc', 'tr', 'ctl', 'ct', 'ctr': minMaxPosY := Max(minMaxPosY, guiY + guiH) 739 | } 740 | } catch 741 | break 742 | } 743 | 744 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 745 | 746 | switch m['pos'], false { 747 | case 'tl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG) 748 | case 'tc': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG) 749 | case 'tr': gPos := 'x' monWARight - m['padX'] - gW ' y' ((minMaxPosY = monWATop) ? monWATop + m['padY'] : minMaxPosY + this.padG) 750 | case 'ctl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG) 751 | case 'ct': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG) 752 | case 'ctr': gPos := 'x' monWARight - m['padX'] - gW ' y' ((minMaxPosY = monWATop) ? monWATop + monWAheight/2 - gH/2 : minMaxPosY + this.padG) 753 | case 'bl': gPos := 'x' monWALeft + m['padX'] ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG) 754 | case 'bc': gPos := 'x' monWARight - monWAwidth/2 - gW/2 ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG) 755 | case 'br': gPos := 'x' monWARight - gW - m['padX'] ' y' ((minMaxPosY = monWABottom) ? monWABottom - gH - m['padY'] : minMaxPosY - gH - this.padG) 756 | case 'mouse': gPos := this.CalculatePopupWindowPosition(g.hwnd) 757 | } 758 | 759 | switch g.pos, false { 760 | case 'bl', 'bc', 'br': outOfWorkArea := (minMaxPosY < (monWATop + gH + this.padG)) 761 | case 'tl', 'tc', 'tr', 'ctl', 'ct', 'ctr': outOfWorkArea := (minMaxPosY > (monWABottom - gH - this.padG)) 762 | case 'mouse': outOfWorkArea := false 763 | } 764 | 765 | ;============================================== 766 | 767 | this.mNotifyGUIs[gIndex] := g 768 | 769 | switch m['style'], false { 770 | case 'round': this.FrameShadow(g.hwnd), !RegExMatch(m['bdrC'], 'i)^(default|1|0)$') && this.DrawBorderRound(g.hwnd, m['bdrC']) 771 | case 'edge': RegExMatch(m['bdrC'], 'i)^(default|1)$') && g.Opt('+Border') 772 | } 773 | 774 | if m['wstp'] || m['wstp'] = 0 775 | WinSetTransparent(m['wstp'], g) 776 | 777 | if m['wstc'] 778 | WinSetTransColor(m['wstc'], g) 779 | 780 | g.Show(gPos ' NoActivate Hide') 781 | 782 | if (m['showHex']) { 783 | try DllCall('AnimateWindow', 'Ptr', g.hwnd, 'Int', m['showDur'], 'Int', m['showHex']) 784 | catch 785 | g.Show(gPos ' NoActivate') 786 | } else 787 | g.Show(gPos ' NoActivate') 788 | 789 | if m['style'] = 'edge' && !RegExMatch(m['bdrC'], 'i)^(default|1|0)$') 790 | try this.DrawBorderEdge(g.Hwnd, m['bdrC'], m['bdrW']) 791 | 792 | if m['dur'] 793 | SetTimer(g.boundFuncTimer, - ((m['dur'] + (outOfWorkArea ? 8 : 0)) * 1000)) 794 | 795 | return m 796 | } 797 | 798 | ;============================================================================================ 799 | 800 | static gDestroy(g, fromMethod:='', *) 801 | { 802 | SetTimer(g.boundFuncTimer, 0) 803 | 804 | if g.hideHex && (fromMethod != 'close' || g.dga) 805 | try DllCall('AnimateWindow', 'Ptr', g.hwnd, 'Int', g.hideDur, 'Int', Format('{:#X}', g.hideHex + 0x10000)) 806 | 807 | g.Destroy() 808 | 809 | if this.mNotifyGUIs.Has(g.gIndex) 810 | this.mNotifyGUIs.Delete(g.gIndex) 811 | 812 | ;============================================== 813 | 814 | Sleep(25) 815 | arrGUIs := Array() 816 | mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx') 817 | 818 | for id in WinGetList('i)^NotifyGUI_[0-1]_' g.mon '_' g.pos '_ ahk_class AutoHotkeyGUI') { 819 | try { 820 | WinGetPos(, &gY,, &gH, 'ahk_id ' id) 821 | RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_[0-1]_\d+_([a-z]+)_([a-z]+)_(\w+)_(\d+)_(\d+)_\d{17}', &match) 822 | 823 | if match[1] = 'mouse' 824 | continue 825 | 826 | arrGUIs.Push(this.MapCI().Set('gY', gY, 'gH', gH, 'id', id, 'style', match[2], 'bdrC', match[3], 'bdrW', match[4], 'padY', match[5])) 827 | } catch { 828 | arrGUIs := Array() 829 | break 830 | } 831 | } 832 | 833 | if (arrGUIs.Length) { 834 | try MonitorGetWorkArea(g.mon,, &monWATop,, &monWABottom) 835 | catch { 836 | this.RedrawAllBorderEdge() 837 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 838 | return 839 | } 840 | 841 | monWAheight := Abs(monWABottom - monWATop) 842 | SetWinDelay(0) 843 | 844 | switch g.pos, false { 845 | case 'bl', 'bc', 'br': arrGUIs := this.SortArrayGUIPosY(arrGUIs, true), posY := monWABottom - arrGUIs[1]['padY'] 846 | case 'tl', 'tc', 'tr', 'ctl', 'ct', 'ctr': arrGUIs := this.SortArrayGUIPosY(arrGUIs), posY := monWATop + arrGUIs[1]['padY'] 847 | } 848 | 849 | for value in arrGUIs { 850 | switch g.pos, false { 851 | case 'bl', 'bc', 'br': posY -= value['gH'] 852 | case 'ctl', 'ct', 'ctr': (A_Index = 1 && posY := monWATop + monWAheight/2 - value['gH']/2) 853 | } 854 | 855 | if (Abs(posY - value['gY']) > 10) { 856 | try { 857 | WinMove(, posY,,, 'ahk_id ' value['id']) 858 | this.ReDrawBorderEdge(value['id'], value['style'], value['bdrC'], value['bdrW']) 859 | } catch 860 | break 861 | } 862 | 863 | switch g.pos, false { 864 | case 'bl', 'bc', 'br': posY -= this.padG 865 | case 'tl', 'tc', 'tr', 'ctl', 'ct', 'ctr': posY += value['gH'] + this.padG 866 | } 867 | } 868 | } 869 | 870 | this.RedrawAllBorderEdge() 871 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 872 | } 873 | 874 | /******************************************************************************************** 875 | * Destroys GUIs. 876 | * @param {integer|string} param 877 | * - Window handle (hwnd) - Destroys the GUI with the specified window handle. 878 | * - Tag - Destroys every GUI containing this tag across all scripts. 879 | * - 'oldest' or no param - Destroys the oldest GUI. 880 | * - 'latest' - Destroys the most recent GUI. 881 | * @param {boolean} force - When true, overrides Destroy GUI block (DGB) setting and forces GUI destruction. 882 | */ 883 | static Destroy(param:='', force:=false) 884 | { 885 | mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx') 886 | bin := (force ? '[0-1]' : 0) 887 | SetWinDelay(25) 888 | 889 | switch { 890 | case (param = 'oldest' || param = 'latest' || param = ''): 891 | { 892 | m := Map() 893 | for id in WinGetList('i)^NotifyGUI_' bin '_ ahk_class AutoHotkeyGUI') { 894 | try { 895 | RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_' bin '_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_(\d{17})', &match) 896 | m[match[1]] := id 897 | } 898 | } 899 | 900 | if (param = 'latest') { 901 | for timestamp, id in m 902 | destroyId := id 903 | } else { 904 | for timestamp, id in m { 905 | destroyId := id 906 | break 907 | } 908 | } 909 | 910 | if IsSet(destroyId) 911 | try WinClose('ahk_id ' destroyId) 912 | } 913 | 914 | ;============================================== 915 | 916 | case (WinExist('ahk_id ' param)): 917 | { 918 | for id in WinGetList('i)^NotifyGUI_' bin '_ ahk_class AutoHotkeyGUI') { 919 | if (param = id) { 920 | try WinClose('ahk_id ' id) 921 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 922 | return 923 | } 924 | } 925 | } 926 | 927 | ;============================================== 928 | 929 | default: 930 | for id in WinGetList('i)^NotifyGUI_' bin '_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_\d{17}_\Q' param '\E$ ahk_class AutoHotkeyGUI') 931 | try WinClose('ahk_id ' id) 932 | } 933 | 934 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 935 | } 936 | 937 | ;============================================================================================ 938 | 939 | static DestroyAllOnMonitorAtPosition(monNum, position, force:=false)=> this.WinGetList_WinClose('i)^NotifyGUI_' (force ? '[0-1]' : '0') '_' monNum '_' position '_ ahk_class AutoHotkeyGUI', 0, 'RegEx') 940 | 941 | static DestroyAllOnAllMonitorAtPosition(position, force:=false)=> this.WinGetList_WinClose('i)^NotifyGUI_' (force ? '[0-1]' : '0') '_\d+_' position '_ ahk_class AutoHotkeyGUI', 0, 'RegEx') 942 | 943 | static DestroyAllOnMonitor(monNum, force:=false)=> this.WinGetList_WinClose('i)NotifyGUI_' (force ? '[0-1]' : '0') '_' monNum '_ ahk_class AutoHotkeyGUI', 0, 'RegEx') 944 | 945 | static DestroyAll(force:=false)=> this.WinGetList_WinClose('i)^NotifyGUI_' (force ? '[0-1]' : '0') '_ ahk_class AutoHotkeyGUI', 0, 'RegEx') 946 | 947 | ;============================================================================================ 948 | 949 | static WinGetList_WinClose(winTitle, dhWindows, tmMode) 950 | { 951 | mDhwTmm := this.Set_DHWindows_TMMode(dhWindows, tmMode) 952 | SetWinDelay(25) 953 | 954 | for id in WinGetList(winTitle) 955 | try WinClose('ahk_id ' id) 956 | 957 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 958 | } 959 | 960 | /******************************************************************************************** 961 | * Checks if a GUI with the specified tag exists. 962 | * @param {string} tag - The tag to search. 963 | * @returns {integer|false} - The unique ID (HWND) of the first matching GUI if found, otherwise false. 964 | */ 965 | static Exist(tag) 966 | { 967 | mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx') 968 | 969 | for id in WinGetList('i)^NotifyGUI_[0-1]_\d+_[a-z]+_[a-z]+_\w+_\d+_\d+_\d{17}_\Q' tag '\E$ ahk_class AutoHotkeyGUI') { 970 | idFound := id 971 | break 972 | } 973 | 974 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 975 | return idFound ?? false 976 | } 977 | 978 | ;============================================================================================ 979 | 980 | static SetDefaultTheme(theme:='') 981 | { 982 | switch { 983 | case this.mThemes.Has(theme): this.mDefaults['theme'] := theme 984 | case !theme: this.mDefaults['theme'] := 'default' 985 | } 986 | } 987 | 988 | ;============================================================================================ 989 | 990 | static SetDefault_MiscValues(m) 991 | { 992 | for key, value in this.mDefaults 993 | m[key] := m.Get(key, value) 994 | 995 | if !RegExMatch(m['style'], 'i)^(round|edge)$') 996 | m['style'] := this.mDefaults['style'] 997 | 998 | if !RegExMatch(m['dgb'], '^(0|1)$') 999 | m['dgb'] := this.mDefaults['dgb'] 1000 | 1001 | for value in ['tfo', 'mfo'] { 1002 | m['arr' value] := Array() 1003 | 1004 | for v in ['bold', 'italic', 'strike', 'underline'] 1005 | if InStr(m[value], v) 1006 | m['arr' value].Push(v) 1007 | 1008 | if !InStr(m[value], 'norm') 1009 | m[value] := Trim('norm ' m[value]) 1010 | } 1011 | } 1012 | 1013 | ;============================================================================================ 1014 | 1015 | static ParsePadOption(m) 1016 | { 1017 | if (m.has('pad')) { 1018 | arrPad := StrSplit(m['pad'], ',', A_Space) 1019 | 1020 | for key in ['padX', 'padY', 'gmT', 'gmB', 'gmL', 'gmR', 'spX', 'spY'] 1021 | if !m.Has(key) && arrPad.Has(A_Index) && arrPad[A_Index] != '' 1022 | m[key] := arrPad[A_Index] 1023 | 1024 | this.SetValidPadRange(m) 1025 | } 1026 | } 1027 | 1028 | ;============================================================================================ 1029 | 1030 | static SetPadDefault(m) 1031 | { 1032 | padXYdefault := (m['style'] = 'edge' ? this.padXYdefaultEdge : this.padXYdefaultRound) 1033 | 1034 | for key in ['padX', 'padY'] 1035 | m[key] := m.Get(key, padXYdefault) 1036 | 1037 | if this.mThemes[m['theme']].Has('pad') 1038 | arrPad := StrSplit(this.mThemes[m['theme']]['pad'], ',', A_Space) 1039 | else 1040 | arrPad := StrSplit(this.mDefaults['pad'], ',', A_Space) 1041 | 1042 | for key in ['gmT', 'gmB', 'gmL', 'gmR', 'spX', 'spY'] 1043 | if !m.Has(key) && arrPad.Has(A_Index+2) && arrPad[A_Index+2] != '' 1044 | m[key] := arrPad[A_Index+2] 1045 | 1046 | this.SetValidPadRange(m) 1047 | } 1048 | 1049 | ;============================================================================================ 1050 | 1051 | static SetValidPadRange(m) 1052 | { 1053 | for key in ['padX', 'padY', 'gmT', 'gmB', 'gmL', 'gmR', 'spX', 'spY'] { 1054 | strPad := RegExMatch(key, '^(padX|padY)$') ? 'arrPadXYrange' : 'arrPadRange' 1055 | m.Has(key) ? m[key] := Min(this.%strPad%[2], Max(this.%strPad%[1], Integer(m[key]))) : '' 1056 | } 1057 | } 1058 | 1059 | ;============================================================================================ 1060 | 1061 | static ParseAnimationOption(m) 1062 | { 1063 | for value in ['show', 'hide'] { 1064 | if (m.has(value)) { 1065 | arrAnim := StrSplit(m[value], '@', A_Space) 1066 | m[value 'Hex'] := this.mAW[arrAnim[1] = 0 ? 'none' : arrAnim[1]] 1067 | arrAnim.Has(2) && (m[value 'Dur'] := Min(this.arrAnimDurRange[2], Max(this.arrAnimDurRange[1], integer(arrAnim[2])))) 1068 | } 1069 | } 1070 | } 1071 | 1072 | ;============================================================================================ 1073 | 1074 | static SetAnimationDefault(m) 1075 | { 1076 | switch m['style'], false { 1077 | case 'edge': 1078 | { 1079 | if !m.Has('showHex') { 1080 | switch m['pos'], false { 1081 | case 'br', 'tr', 'ctr': m['showHex'] := this.mAW['slideWest'] 1082 | case 'bl', 'tl', 'ctl': m['showHex'] := this.mAW['slideEast'] 1083 | case 'bc': m['showHex'] := this.mAW['slideNorth'] 1084 | case 'tc': m['showHex'] := this.mAW['slideSouth'] 1085 | case 'ct': m['showHex'] := this.mAW['expand'] 1086 | case 'mouse': m['showHex'] := this.mAW['none'] 1087 | } 1088 | } 1089 | 1090 | if !m.Has('hideHex') { 1091 | switch m['pos'], false { 1092 | case 'br', 'tr', 'ctr': m['hideHex'] := this.mAW['slideEast'] 1093 | case 'bl', 'tl', 'ctl': m['hideHex'] := this.mAW['slideWest'] 1094 | case 'bc': m['hideHex'] := this.mAW['slideSouth'] 1095 | case 'tc': m['hideHex'] := this.mAW['slideNorth'] 1096 | case 'ct': m['hideHex'] := this.mAW['expand'] 1097 | case 'mouse': m['hideHex'] := this.mAW['none'] 1098 | } 1099 | } 1100 | m['showDur'] := m.Get('showDur', 75) 1101 | m['hideDur'] := m.Get('hideDur', 100) 1102 | } 1103 | 1104 | case 'round': 1105 | { 1106 | m['showHex'] := m.Get('showHex', this.mAW['fade']) 1107 | m['hideHex'] := m.Get('hideHex', this.mAW['none']) 1108 | m['showDur'] := m.Get('showDur', 1) 1109 | m['hideDur'] := m.Get('hideDur', 1) 1110 | } 1111 | } 1112 | } 1113 | 1114 | ;============================================================================================ 1115 | 1116 | static ParseBorderOption(m) 1117 | { 1118 | if (m.Has('bdr')) { 1119 | arrBdr := StrSplit(m['bdr'], ',', A_Space) 1120 | m['bdr'] := this.NormAHKColor(m['bdr']) 1121 | m['bdrC'] := this.NormAHKColor(arrBdr[1]) 1122 | arrBdr.Has(2) && (m['bdrW'] := this.SetValidBorderWidth(arrBdr[2])) 1123 | } 1124 | } 1125 | 1126 | ;============================================================================================ 1127 | 1128 | static SetBorderOption(m) 1129 | { 1130 | mTheme := this.mThemes.Has(m['theme']) ? this.mThemes[m['theme']] : this.MapCI() 1131 | 1132 | switch { 1133 | case m['bdr'] = 0: m['bdrC'] := 0 1134 | case m.Has('bdrC') && m['bdrC']: m['bdrC'] := m['bdrC'] 1135 | case (!m.Has('bdrC') || !m['bdrC']) && mTheme.Has('bdrC'): m['bdrC'] := mTheme['bdrC'] 1136 | case (!m.Has('bdrC') || !m['bdrC']) && !mTheme.Has('bdrC'): m['bdrC'] := 'default' 1137 | } 1138 | 1139 | switch { 1140 | case RegExMatch(m['bdrC'], 'i)^(default|1|0)$'): m['bdrW'] := 0 1141 | case !m.Has('bdrW'): m['bdrW'] := (m['style'] = 'edge' ? this.bdrWdefaultEdge : 0) 1142 | case m.Has('bdrW'): 1143 | switch m['style'], false { 1144 | case 'edge': m['bdrW'] := this.SetValidBorderWidth(m['bdrW']) 1145 | case 'round': m['bdrW'] := 0 1146 | } 1147 | } 1148 | } 1149 | 1150 | ;============================================================================================ 1151 | 1152 | static SetValidBorderWidth(width) => Min(this.arrBdrWrange[2], Max(this.arrBdrWrange[1], Integer(width))) 1153 | 1154 | ;============================================================================================ 1155 | 1156 | static SetFont(g, size, color, fontOption, fontName) 1157 | { 1158 | g.SetFont('s' size ' c' color ' ' fontOption, fontName) 1159 | strFont := size ' ' this.NormalizeFontOptions(fontOption) . fontName 1160 | 1161 | if !this.HasVal(strFont, this.arrFonts) 1162 | this.arrFonts.Push(strFont) 1163 | 1164 | if (this.arrFonts.Length >= 190) { 1165 | this.isTooManyFonts := true 1166 | return false 1167 | } 1168 | 1169 | return true 1170 | } 1171 | 1172 | ;============================================================================================ 1173 | 1174 | static NormalizeFontOptions(str) 1175 | { 1176 | strFontopt := '' 1177 | 1178 | for option in ['bold', 'italic', 'strike', 'underline'] 1179 | if InStr(str, option) 1180 | strFontopt .= option ' ' 1181 | 1182 | return strFontopt 1183 | } 1184 | 1185 | ;============================================================================================ 1186 | 1187 | static Set_DHWindows_TMMode(dhw, tmm) 1188 | { 1189 | dhwPrev := A_DetectHiddenWindows 1190 | tmmPrev := A_TitleMatchMode 1191 | DetectHiddenWindows(dhw) 1192 | SetTitleMatchMode(tmm) 1193 | return Map('dhwPrev', dhwPrev, 'tmmPrev', tmmPrev) 1194 | } 1195 | 1196 | /******************************************************************************************** 1197 | * Plays a sound. 1198 | * @param {string|integer} sound - A valid sound format. See the documentation for the sound parameter. 1199 | */ 1200 | static Sound(sound) 1201 | { 1202 | if this.mSounds.Has(sound) 1203 | sound := this.mSounds[sound] 1204 | 1205 | switch { 1206 | case FileExist(sound): try this.PlayWavConcurrent(sound) 1207 | case RegExMatch(sound,'^\*\-?\d+'): try Soundplay(sound) 1208 | } 1209 | } 1210 | 1211 | /******************************************************************************************** 1212 | * @credits Faddix, XMCQCX (minor modifications) 1213 | * @see {@link https://www.autohotkey.com/boards/viewtopic.php?f=83&t=130425 AHK Forum} 1214 | */ 1215 | static PlayWavConcurrent(fPath) 1216 | { 1217 | static obj := initialize() 1218 | SplitPath(fPath,,, &ext) 1219 | 1220 | initialize() { 1221 | if !hModule := DllCall("LoadLibrary", "Str", "XAudio2_9.dll", "Ptr") 1222 | return false 1223 | 1224 | DllCall("XAudio2_9\XAudio2Create", "Ptr*", IXAudio2 := ComValue(13, 0), "Uint", 0, "Uint", 1) 1225 | ComCall(7, IXAudio2, "Ptr*", &IXAudio2MasteringVoice := 0, "Uint", 0, "Uint", 0, "Uint", 0, "Ptr", 0, "Ptr", 0, "Int", 6) ; CreateMasteringVoice 1226 | return { IXAudio2: IXAudio2, someMap: Map() } 1227 | } 1228 | 1229 | if !obj || !RegExMatch(ext, 'i)^wav$') 1230 | return 1231 | 1232 | ; freeing is unnecessary, but.. 1233 | XAUDIO2_VOICE_STATE := Buffer(A_PtrSize * 2 + 0x8) 1234 | keys_to_delete := [] 1235 | for IXAudio2SourceVoice in obj.someMap { 1236 | ComCall(25, IXAudio2SourceVoice, "Ptr", XAUDIO2_VOICE_STATE, "Uint", 0, "Int") ;GetState 1237 | if (!NumGet(XAUDIO2_VOICE_STATE, A_PtrSize, "Uint")) { ; BuffersQueued (includes the one that is being processed) 1238 | keys_to_delete.Push(IXAudio2SourceVoice) 1239 | } 1240 | } 1241 | for IXAudio2SourceVoice in keys_to_delete { 1242 | ComCall(20, IXAudio2SourceVoice, "Uint", 0, "Uint", 0) ;Stop 1243 | ComCall(18, IXAudio2SourceVoice, "Int") ; void DestroyVoice 1244 | obj.someMap.Delete(IXAudio2SourceVoice) 1245 | } 1246 | 1247 | waveFile := FileRead(fPath, "RAW") 1248 | 1249 | if !root_tag_to_offset := get_tag_to_offset_map(0, waveFile.Size) 1250 | return 1251 | 1252 | if !idk_tag_to_offset := get_tag_to_offset_map(root_tag_to_offset["RIFF"].ofs + 0xc, waveFile.Size) 1253 | return 1254 | 1255 | WAVEFORMAT_ofs := idk_tag_to_offset["fmt "].ofs + 0x8 1256 | data_ofs := idk_tag_to_offset["data"].ofs + 0x8 1257 | data_size := idk_tag_to_offset["data"].size 1258 | 1259 | get_tag_to_offset_map(i, end) { 1260 | tag_to_offset := Map() 1261 | while (i + 8 <= end) { ; Ensure there's enough data for a chunk header 1262 | tag := StrGet(waveFile.Ptr + i, 4, "UTF-8") ; RIFFChunk::tag 1263 | size := NumGet(waveFile, i + 0x4, "Uint") ; RIFFChunk::size 1264 | 1265 | ; Stop execution and return false if chunk size exceeds file bounds 1266 | if (i + 8 + size > end) 1267 | return false 1268 | 1269 | tag_to_offset[tag] := { ofs: i, size: size } 1270 | ; Align to next 2-byte or 4-byte boundary 1271 | i += size + 8 1272 | if (i & 1) ; 2-byte alignment 1273 | i += 1 1274 | } 1275 | return tag_to_offset 1276 | } 1277 | 1278 | ComCall(5, obj.IXAudio2, "Ptr*", &IXAudio2SourceVoice := 0, "Ptr", waveFile.Ptr + WAVEFORMAT_ofs, "int", 0, "float", 2.0, "Ptr", 0, "Ptr", 0, "Ptr", 0) ; CreateSourceVoice 1279 | XAUDIO2_BUFFER := Buffer(A_PtrSize * 2 + 0x1c, 0) 1280 | NumPut("Uint", 0x0040, XAUDIO2_BUFFER, 0x0) ; Flags=XAUDIO2_END_OF_STREAM 1281 | NumPut("Uint", data_size, XAUDIO2_BUFFER, 0x4) ; AudioBytes 1282 | NumPut("Ptr", waveFile.Ptr + data_ofs, XAUDIO2_BUFFER, 0x8) ; pAudioData 1283 | ComCall(21, IXAudio2SourceVoice, "Ptr", XAUDIO2_BUFFER, "Ptr", 0) ; SubmitSourceBuffer 1284 | ComCall(19, IXAudio2SourceVoice, "Uint", 0, "Uint", 0) ; Start 1285 | obj.someMap[IXAudio2SourceVoice] := waveFile 1286 | } 1287 | 1288 | ;============================================================================================ 1289 | 1290 | static OptionsStringToMap(m, haystack) 1291 | { 1292 | pos := 1 1293 | while (pos := RegExMatch(haystack, 'i)(\w+)\s*=\s*(.*?)\s*(?=\s*\w+\s*=|$)', &match, pos)) 1294 | m[match[1]] := match[2], pos += StrLen(match[0]) 1295 | } 1296 | 1297 | ;============================================================================================ 1298 | 1299 | static SetThemeSettings(m, mTheme) 1300 | { 1301 | if (m['theme'] != 'default') 1302 | for key in mTheme['arrKeyDefined'] 1303 | m[key] := m.Get(key, mTheme[key]) 1304 | } 1305 | 1306 | ;============================================================================================ 1307 | 1308 | static GetTextWidth(str:='', font:='', fontSize:='', fontOption:='', monWALeft:='', monWATop:='') 1309 | { 1310 | g := Gui() 1311 | g.SetFont('s' fontSize ' ' fontOption, font) 1312 | g.txt := g.Add('Text',, str) 1313 | g.Show('x' monWALeft ' y' monWATop ' Hide') 1314 | g.txt.GetPos(,, &ctrlW) 1315 | g.Destroy() 1316 | return ctrlW 1317 | } 1318 | 1319 | ;============================================================================================ 1320 | 1321 | static GetImageDimensions(image, posXY?, dimWH:='', dpiScale:=true) 1322 | { 1323 | g := Gui(dpiScale ? '' : '-DPIScale') 1324 | posXY := (posXY ?? 'x0 y0') 1325 | 1326 | switch { 1327 | case this.isInternalString(image): 1328 | try g.pic := g.Add('Picture', dimWH ' Icon' this.mImages[image] ' BackgroundTrans', A_WinDir '\system32\user32.dll') 1329 | 1330 | case this.isInternalImage(image): 1331 | try g.pic := g.Add('Picture', dimWH ' BackgroundTrans', this.mImages[image]) 1332 | 1333 | case arrRegExMatch := this.isIconResourceFile(image): 1334 | try g.pic := g.Add('Picture', dimWH ' Icon' arrRegExMatch[2] ' BackgroundTrans', arrRegExMatch[1]) 1335 | 1336 | case this.isImagePathOrHandle(image): 1337 | try g.pic := g.Add('Picture', dimWH ' BackgroundTrans', image) 1338 | 1339 | case this.isValidColor(image): 1340 | try g.pic := g.Add('Picture',, 'hBitmap: ' this.CreatePixel(image)) 1341 | } 1342 | 1343 | if (g.HasOwnProp('pic')) { 1344 | g.Show(posXY ' hide') 1345 | g.pic.GetPos(,, &ctrlW, &ctrlH) 1346 | mDim := this.MapCI().Set('ctrlW', ctrlW, 'ctrlH', ctrlH, 'aspectRatio', Round(ctrlW / ctrlH, 2)) 1347 | } 1348 | 1349 | g.Destroy() 1350 | return mDim ?? false 1351 | } 1352 | 1353 | ;============================================================================================ 1354 | 1355 | static SortArrayGUIPosY(arr, sortReverse := false) 1356 | { 1357 | for value in arr 1358 | listValueY .= value['gY'] ',' 1359 | 1360 | listSortValueY := Sort(RTrim(listValueY, ','), (sortReverse ? 'RN' : 'N') ' D,') 1361 | sortArray := Array() 1362 | 1363 | for value in StrSplit(listSortValueY, ',') 1364 | for v in arr 1365 | if v['gY'] = value 1366 | sortArray.Push(v) 1367 | 1368 | return sortArray 1369 | } 1370 | 1371 | ;============================================================================================= 1372 | 1373 | static HasVal(needle, haystack, caseSensitive := false) 1374 | { 1375 | for index, value in haystack 1376 | if (caseSensitive && value == needle) || (!caseSensitive && value = needle) 1377 | return index 1378 | 1379 | return false 1380 | } 1381 | 1382 | ;============================================================================================ 1383 | 1384 | static ArrayToString(arr, delim) 1385 | { 1386 | for value in arr 1387 | str .= value delim 1388 | 1389 | return RTrim(str, delim) 1390 | } 1391 | 1392 | ;============================================================================================ 1393 | 1394 | static NormAllColors(m) 1395 | { 1396 | for key in ['tc', 'mc', 'bc'] 1397 | if m.Has(key) 1398 | m[key] := NormColor(m[key]) 1399 | 1400 | for key in ['bdr', 'bdrC'] 1401 | if m.Has(key) && !RegExMatch(m[key], 'i)^(1|0|default)$') 1402 | m[key] := NormColor(m[key]) 1403 | 1404 | NormColor(key) => this.NormAHKColor(this.NormHexClrCode(key)) 1405 | } 1406 | 1407 | ;============================================================================================ 1408 | 1409 | static NormAHKColor(color) 1410 | { 1411 | if RegExMatch(color, '^(0|1)$') 1412 | return color 1413 | 1414 | for colorName, colorValue in this.mAHKcolors { 1415 | if (colorValue = color) { 1416 | color := colorName 1417 | break 1418 | } 1419 | } 1420 | 1421 | return color 1422 | } 1423 | 1424 | ;============================================================================================ 1425 | 1426 | static NormHexClrCode(color) 1427 | { 1428 | if this.mAHKcolors.Has(color) 1429 | color := this.mAHKcolors[color] 1430 | 1431 | if RegExMatch(Color, '^[0-9A-Fa-f]{1,6}$') && SubStr(color, 1, 2) != '0x' 1432 | color := '0x' color 1433 | 1434 | if (RegExMatch(color, '^0x[0-9A-Fa-f]{1,6}$')) { 1435 | hexPart := SubStr(color, 3) 1436 | while StrLen(hexPart) < 6 1437 | hexPart := '0' hexPart 1438 | color := '0x' hexPart 1439 | } 1440 | else color := '0xFFFFFF' 1441 | 1442 | return color 1443 | } 1444 | 1445 | /******************************************************************************************** 1446 | * @credits TheDewd, XMCQCX (v2 conversion, minor modifications) 1447 | * @see {@link https://www.autohotkey.com/boards/viewtopic.php?t=7312 AHK Forum} 1448 | */ 1449 | static CreatePixel(color) 1450 | { 1451 | color := this.NormHexClrCode(color) 1452 | hBitmap := DllCall("CreateBitmap", "Int", 1, "Int", 1, "UInt", 1, "UInt", 32, "Ptr", 0, "Ptr") 1453 | hBM := DllCall("CopyImage", "Ptr", hBitmap, "UInt", 0, "Int", 0, "Int", 0, "UInt", 0x2008, "Ptr") 1454 | BMBITS := Buffer(4, 0) 1455 | NumPut("UInt", color, BMBITS, 0) 1456 | DllCall("SetBitmapBits", "Ptr", hBM, "UInt", 4, "Ptr", BMBITS) 1457 | DllCall("DeleteObject", "Ptr", hBitmap) 1458 | return hBM 1459 | } 1460 | 1461 | /******************************************************************************************** 1462 | * @credits Klark92, XMCQCX (v2 conversion) 1463 | * @see {@link https://www.autohotkey.com/boards/viewtopic.php?f=6&t=29117 AHK Forum} 1464 | */ 1465 | static FrameShadow(hwnd) 1466 | { 1467 | DllCall("dwmapi.dll\DwmIsCompositionEnabled", "int*", &dwmEnabled:=0) 1468 | 1469 | if !dwmEnabled { 1470 | DllCall("user32.dll\SetClassLongPtr", "ptr", hwnd, "int", -26, "ptr", DllCall("user32.dll\GetClassLongPtr", "ptr", hwnd, "int", -26) | 0x20000) 1471 | } 1472 | else { 1473 | margins := Buffer(16, 0) 1474 | NumPut("int", 1, "int", 1, "int", 1, "int", 1, margins) 1475 | DllCall("dwmapi.dll\DwmSetWindowAttribute", "ptr", hwnd, "Int", 2, "Int*", 2, "Int", 4) 1476 | DllCall("dwmapi.dll\DwmExtendFrameIntoClientArea", "ptr", hwnd, "ptr", margins) 1477 | } 1478 | } 1479 | 1480 | /******************************************************************************************** 1481 | * @credits ericreeves 1482 | * @see {@link https://gist.github.com/ericreeves/fd426cc0457a5a47058e1ad1a29d9bd6 GitHub Gist} 1483 | */ 1484 | static DrawBorderRound(hwnd, color) 1485 | { 1486 | color := this.RGB_BGR(this.NormHexClrCode(color)) 1487 | DllCall("dwmapi\DwmSetWindowAttribute", "ptr", hwnd, "int", DWMWA_BORDER_COLOR := 34, "int*", color, "int", 4) 1488 | } 1489 | 1490 | ;============================================================================================ 1491 | 1492 | static DrawBorderEdge(hwnd, color, width) 1493 | { 1494 | color := this.RGB_BGR(this.NormHexClrCode(color)) 1495 | rect := Buffer(16) 1496 | DllCall("GetClientRect", "ptr", hwnd, "ptr", rect) 1497 | left := NumGet(rect, 0, "int") 1498 | top := NumGet(rect, 4, "int") 1499 | right := NumGet(rect, 8, "int") 1500 | bottom := NumGet(rect, 12, "int") 1501 | hdc := DllCall("GetDC", "ptr", hwnd) 1502 | hBrush := DllCall("gdi32\CreateSolidBrush", "uint", color, "ptr") 1503 | hOldBrush := DllCall("gdi32\SelectObject", "ptr", hdc, "ptr", hBrush, "ptr") 1504 | DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", top, "int", right - left, "int", width, "uint", 0x00F00021) ; Top border 1505 | DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", bottom - width, "int", right - left, "int", width, "uint", 0x00F00021) ; Bottom border 1506 | DllCall("gdi32\PatBlt", "ptr", hdc, "int", left, "int", top, "int", width, "int", bottom - top, "uint", 0x00F00021) ; Left border 1507 | DllCall("gdi32\PatBlt", "ptr", hdc, "int", right - width, "int", top, "int", width, "int", bottom - top, "uint", 0x00F00021) ; Right border 1508 | DllCall("gdi32\SelectObject", "ptr", hdc, "ptr", hOldBrush) 1509 | DllCall("gdi32\DeleteObject", "ptr", hBrush) 1510 | DllCall("ReleaseDC", "ptr", hwnd, "ptr", hdc) 1511 | } 1512 | 1513 | ;============================================================================================ 1514 | 1515 | static ReDrawBorderEdge(hwnd, style, color, width) 1516 | { 1517 | if style = 'edge' && !RegExMatch(color, 'i)^(default|1|0)$') 1518 | this.DrawBorderEdge(hwnd, color, width) 1519 | } 1520 | 1521 | ;============================================================================================ 1522 | 1523 | static RedrawAllBorderEdge() 1524 | { 1525 | mDhwTmm := this.Set_DHWindows_TMMode(0, 'RegEx') 1526 | 1527 | for id in WinGetList('i)^NotifyGUI_[0-1]_\d+_[a-z]+_edge_\w+_\d+_\d+_\d{17} ahk_class AutoHotkeyGUI') { 1528 | try { 1529 | RegExMatch(WinGetTitle('ahk_id ' id), 'i)^NotifyGUI_[0-1]_\d+_[a-z]+_edge_(\w+)_(\d+)_\d+_\d{17}', &match) 1530 | this.ReDrawBorderEdge(id, 'edge', match[1], match[2]) 1531 | } 1532 | } 1533 | 1534 | this.Set_DHWindows_TMMode(mDhwTmm['dhwPrev'], mDhwTmm['tmmPrev']) 1535 | } 1536 | 1537 | ;============================================================================================ 1538 | 1539 | static MonitorGetMouseIsIn() 1540 | { 1541 | cmmPrev := A_CoordModeMouse 1542 | CoordMode('Mouse', 'Screen') 1543 | MouseGetPos(&posX, &posY) 1544 | CoordMode('Mouse', cmmPrev) 1545 | 1546 | Loop MonitorGetCount() { 1547 | MonitorGet(A_Index, &monLeft, &monTop, &monRight, &monBottom) 1548 | if (posX >= monLeft) && (posX < monRight) && (posY >= monTop) && (posY < monBottom) 1549 | return A_Index 1550 | } 1551 | 1552 | return MonitorGetPrimary() 1553 | } 1554 | 1555 | ;============================================================================================ 1556 | 1557 | static MonitorGetWindowIsIn(winTitle) 1558 | { 1559 | try WinGetPos(&posX, &posY, &winW, &winH, winTitle) 1560 | catch 1561 | return MonitorGetPrimary() 1562 | 1563 | centerWinX := posX + winW/2 1564 | centerWinY := posY + winH/2 1565 | 1566 | Loop MonitorGetCount() { 1567 | MonitorGet(A_Index, &monLeft, &monTop, &monRight, &monBottom) 1568 | if (centerWinX >= monLeft) && (centerWinX < monRight) && (centerWinY >= monTop) && (centerWinY < monBottom) 1569 | return A_Index 1570 | } 1571 | 1572 | return MonitorGetPrimary() 1573 | } 1574 | 1575 | /******************************************************************************************** 1576 | * @credits lexikos 1577 | * @see {@link https://www.autohotkey.com/boards/viewtopic.php?t=103459 AHK Forum} 1578 | */ 1579 | static CalculatePopupWindowPosition(hwnd) 1580 | { 1581 | cmmPrev := A_CoordModeMouse 1582 | CoordMode('Mouse', 'Screen') 1583 | MouseGetPos(&x, &y) 1584 | CoordMode('Mouse', cmmPrev) 1585 | anchorPt := Buffer(8) 1586 | windowRect := Buffer(16), windowSize := windowRect.ptr + 8 1587 | excludeRect := Buffer(16) 1588 | outRect := Buffer(16) 1589 | DllCall("GetClientRect", "ptr", hwnd, "ptr", windowRect) 1590 | 1591 | /* 1592 | Windows 7 permits overlap with the taskbar, whereas Windows 10 requires the 1593 | tooltip to be within the work area (WinMove can subvert that, so this is just 1594 | for consistency with the normal behaviour). 1595 | */ 1596 | flags := VerCompare(A_OSVersion, "6.2") < 0 ? 0 : 0x10000 ; TPM_WORKAREA 1597 | 1598 | NumPut("int", x+16, "int", y+16, anchorPt) ; ToolTip normally shows at an offset of 16,16 from the cursor. 1599 | NumPut("int", x-3, "int", y-3, "int", x+3, "int", y+3, excludeRect) ; Avoid the area around the mouse pointer. 1600 | DllCall("CalculatePopupWindowPosition", "ptr", anchorPt, "ptr", windowSize, "uint", flags, "ptr", excludeRect, "ptr", outRect) 1601 | return 'x' NumGet(outRect, 0, 'int') ' y' NumGet(outRect, 4, 'int') 1602 | } 1603 | 1604 | ;============================================================================================ 1605 | 1606 | static isInternalString(image) => RegExMatch(image, 'i)^(icon!|icon\?|iconx|iconi)$') 1607 | 1608 | static isInternalImage(image) => this.mImages.Has(image) && FileExist(this.mImages[image]) 1609 | 1610 | static isIconResourceFile(image) => RegExMatch(image, 'i)^(.+?\.(?:dll|exe|cpl))\|icon(\d+)$', &match) && FileExist(match[1]) ? [match[1], match[2]] : '' 1611 | 1612 | static isImagePathOrHandle(image) => FileExist(image) || RegExMatch(image, 'i)^h(icon|bitmap).*\d+') 1613 | 1614 | static isValidColor(color) => this.mAHKcolors.Has(color) || color ~= 'i)^(?:0x)?[0-9A-F]{1,6}$' 1615 | 1616 | static RGB_BGR(c) => ((c & 0xFF) << 16 | c & 0xFF00 | c >> 16) 1617 | 1618 | static DpiScale(value) => (value / (A_ScreenDPI / 96)) 1619 | 1620 | static MapCI() => (m := Map(), m.CaseSense := false, m) 1621 | } 1622 | 1623 | /**************************************************************************************************************************************** 1624 | * @description: JSON格式字符串序列化和反序列化, 修改自[HotKeyIt/Yaml](https://github.com/HotKeyIt/Yaml) 1625 | * 增加了对true/false/null类型的支持, 保留了数值的类型 1626 | * @author thqby, HotKeyIt 1627 | * @date 2024/02/24 1628 | * @version 1.0.7 1629 | ************************************************************************************************/ 1630 | class _JSON_thqby { 1631 | static null := ComValue(1, 0), true := ComValue(0xB, 1), false := ComValue(0xB, 0) 1632 | 1633 | /** 1634 | * Converts a AutoHotkey Object Notation JSON string into an object. 1635 | * @param text A valid JSON string. 1636 | * @param keepbooltype convert true/false/null to JSON.true / JSON.false / JSON.null where it's true, otherwise 1 / 0 / '' 1637 | * @param as_map object literals are converted to map, otherwise to object 1638 | */ 1639 | static parse(text, keepbooltype := false, as_map := true) { 1640 | keepbooltype ? (_true := this.true, _false := this.false, _null := this.null) : (_true := true, _false := false, _null := "") 1641 | as_map ? (map_set := (maptype := Map).Prototype.Set) : (map_set := (obj, key, val) => obj.%key% := val, maptype := Object) 1642 | NQ := "", LF := "", LP := 0, P := "", R := "" 1643 | D := [C := (A := InStr(text := LTrim(text, " `t`r`n"), "[") = 1) ? [] : maptype()], text := LTrim(SubStr(text, 2), " `t`r`n"), L := 1, N := 0, V := K := "", J := C, !(Q := InStr(text, '"') != 1) ? text := LTrim(text, '"') : "" 1644 | Loop Parse text, '"' { 1645 | Q := NQ ? 1 : !Q 1646 | NQ := Q && RegExMatch(A_LoopField, '(^|[^\\])(\\\\)*\\$') 1647 | if !Q { 1648 | if (t := Trim(A_LoopField, " `t`r`n")) = "," || (t = ":" && V := 1) 1649 | continue 1650 | else if t && (InStr("{[]},:", SubStr(t, 1, 1)) || A && RegExMatch(t, "m)^(null|false|true|-?\d+(\.\d*(e[-+]\d+)?)?)\s*[,}\]\r\n]")) { 1651 | Loop Parse t { 1652 | if N && N-- 1653 | continue 1654 | if InStr("`n`r `t", A_LoopField) 1655 | continue 1656 | else if InStr("{[", A_LoopField) { 1657 | if !A && !V 1658 | throw Error("Malformed JSON - missing key.", 0, t) 1659 | C := A_LoopField = "[" ? [] : maptype(), A ? D[L].Push(C) : map_set(D[L], K, C), D.Has(++L) ? D[L] := C : D.Push(C), V := "", A := Type(C) = "Array" 1660 | continue 1661 | } else if InStr("]}", A_LoopField) { 1662 | if !A && V 1663 | throw Error("Malformed JSON - missing value.", 0, t) 1664 | else if L = 0 1665 | throw Error("Malformed JSON - to many closing brackets.", 0, t) 1666 | else C := --L = 0 ? "" : D[L], A := Type(C) = "Array" 1667 | } else if !(InStr(" `t`r,", A_LoopField) || (A_LoopField = ":" && V := 1)) { 1668 | if RegExMatch(SubStr(t, A_Index), "m)^(null|false|true|-?\d+(\.\d*(e[-+]\d+)?)?)\s*[,}\]\r\n]", &R) && (N := R.Len(0) - 2, R := R.1, 1) { 1669 | if A 1670 | C.Push(R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R) 1671 | else if V 1672 | map_set(C, K, R = "null" ? _null : R = "true" ? _true : R = "false" ? _false : IsNumber(R) ? R + 0 : R), K := V := "" 1673 | else throw Error("Malformed JSON - missing key.", 0, t) 1674 | } else { 1675 | ; Added support for comments without '"' 1676 | if A_LoopField == '/' { 1677 | nt := SubStr(t, A_Index + 1, 1), N := 0 1678 | if nt == '/' { 1679 | if nt := InStr(t, '`n', , A_Index + 2) 1680 | N := nt - A_Index - 1 1681 | } else if nt == '*' { 1682 | if nt := InStr(t, '*/', , A_Index + 2) 1683 | N := nt + 1 - A_Index 1684 | } else nt := 0 1685 | if N 1686 | continue 1687 | } 1688 | throw Error("Malformed JSON - unrecognized character.", 0, A_LoopField " in " t) 1689 | } 1690 | } 1691 | } 1692 | } else if A || InStr(t, ':') > 1 1693 | throw Error("Malformed JSON - unrecognized character.", 0, SubStr(t, 1, 1) " in " t) 1694 | } else if NQ && (P .= A_LoopField '"', 1) 1695 | continue 1696 | else if A 1697 | LF := P A_LoopField, C.Push(InStr(LF, "\") ? UC(LF) : LF), P := "" 1698 | else if V 1699 | LF := P A_LoopField, map_set(C, K, InStr(LF, "\") ? UC(LF) : LF), K := V := P := "" 1700 | else 1701 | LF := P A_LoopField, K := InStr(LF, "\") ? UC(LF) : LF, P := "" 1702 | } 1703 | return J 1704 | UC(S, e := 1) { 1705 | static m := Map('"', '"', "a", "`a", "b", "`b", "t", "`t", "n", "`n", "v", "`v", "f", "`f", "r", "`r") 1706 | local v := "" 1707 | Loop Parse S, "\" 1708 | if !((e := !e) && A_LoopField = "" ? v .= "\" : !e ? (v .= A_LoopField, 1) : 0) 1709 | v .= (t := m.Get(SubStr(A_LoopField, 1, 1), 0)) ? t SubStr(A_LoopField, 2) : 1710 | (t := RegExMatch(A_LoopField, "i)^(u[\da-f]{4}|x[\da-f]{2})\K")) ? 1711 | Chr("0x" SubStr(A_LoopField, 2, t - 2)) SubStr(A_LoopField, t) : "\" A_LoopField, 1712 | e := A_LoopField = "" ? e : !e 1713 | return v 1714 | } 1715 | } 1716 | 1717 | /** 1718 | * Converts a AutoHotkey Array/Map/Object to a Object Notation JSON string. 1719 | * @param obj A AutoHotkey value, usually an object or array or map, to be converted. 1720 | * @param expandlevel The level of JSON string need to expand, by default expand all. 1721 | * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. 1722 | */ 1723 | static stringify(obj, expandlevel := unset, space := " ") { 1724 | expandlevel := IsSet(expandlevel) ? Abs(expandlevel) : 10000000 1725 | return Trim(CO(obj, expandlevel)) 1726 | CO(O, J := 0, R := 0, Q := 0) { 1727 | static M1 := "{", M2 := "}", S1 := "[", S2 := "]", N := "`n", C := ",", S := "- ", E := "", K := ":" 1728 | if (OT := Type(O)) = "Array" { 1729 | D := !R ? S1 : "" 1730 | for key, value in O { 1731 | F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E 1732 | Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" 1733 | D .= (J > R ? "`n" CL(R + 2) : "") (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (OT = "Array" && O.Length = A_Index ? E : C) 1734 | } 1735 | } else { 1736 | D := !R ? M1 : "" 1737 | for key, value in (OT := Type(O)) = "Map" ? (Y := 1, O) : (Y := 0, O.OwnProps()) { 1738 | F := (VT := Type(value)) = "Array" ? "S" : InStr("Map,Object", VT) ? "M" : E 1739 | Z := VT = "Array" && value.Length = 0 ? "[]" : ((VT = "Map" && value.count = 0) || (VT = "Object" && ObjOwnPropCount(value) = 0)) ? "{}" : "" 1740 | D .= (J > R ? "`n" CL(R + 2) : "") (Q = "S" && A_Index = 1 ? M1 : E) ES(key) K (F ? (%F%1 (Z ? "" : CO(value, J, R + 1, F)) %F%2) : ES(value)) (Q = "S" && A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? M2 : E) (J != 0 || R ? (A_Index = (Y ? O.count : ObjOwnPropCount(O)) ? E : C) : E) 1741 | if J = 0 && !R 1742 | D .= (A_Index < (Y ? O.count : ObjOwnPropCount(O)) ? C : E) 1743 | } 1744 | } 1745 | if J > R 1746 | D .= "`n" CL(R + 1) 1747 | if R = 0 1748 | D := RegExReplace(D, "^\R+") (OT = "Array" ? S2 : M2) 1749 | return D 1750 | } 1751 | ES(S) { 1752 | switch Type(S) { 1753 | case "Float": 1754 | if (v := '', d := InStr(S, 'e')) 1755 | v := SubStr(S, d), S := SubStr(S, 1, d - 1) 1756 | if ((StrLen(S) > 17) && (d := RegExMatch(S, "(99999+|00000+)\d{0,3}$"))) 1757 | S := Round(S, Max(1, d - InStr(S, ".") - 1)) 1758 | return S v 1759 | case "Integer": 1760 | return S 1761 | case "String": 1762 | S := StrReplace(S, "\", "\\") 1763 | S := StrReplace(S, "`t", "\t") 1764 | S := StrReplace(S, "`r", "\r") 1765 | S := StrReplace(S, "`n", "\n") 1766 | S := StrReplace(S, "`b", "\b") 1767 | S := StrReplace(S, "`f", "\f") 1768 | S := StrReplace(S, "`v", "\v") 1769 | S := StrReplace(S, '"', '\"') 1770 | return '"' S '"' 1771 | default: 1772 | return S == this.true ? "true" : S == this.false ? "false" : "null" 1773 | } 1774 | } 1775 | CL(i) { 1776 | Loop (s := "", space ? i - 1 : 0) 1777 | s .= space 1778 | return s 1779 | } 1780 | } 1781 | } 1782 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notify Class 2 | Simplifies the creation and display of notification GUIs. 3 | 4 | ![myImage](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExZ3J3Z3l6NHF5emNwdnJjeXVkcnptaWtmdmFlMjhrbjlyczhydjd6aSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/VN6bOGfLrx4q3zA0lA/giphy.gif) 5 | 6 | ![myImage](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmdvcTc0eHQzbzZjNWM1Z3o0ZW1pMXlseHNtZXpuOTRtZnE2NHh4YyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/yyuppxY3ORW9TwURDW/giphy.gif) 7 | 8 | ![myImage](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExODE0NWR5ZXIwZmgzdWJlc2pncjgzeHQ5OWUxc3VvbGU2a2t6cmZwMSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/T7FNY7q6nIxz0qSFUd/giphy.gif) 9 | 10 | ![myImage](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExemZjOGQ1dHhsc2xobzkxMmlueW8zdWtzNHM4ZmZoZ3lza2hyYnZmMiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/7ZpT18kqAtqH7zsWRN/giphy.gif) 11 | 12 | --- 13 | 14 | # Notify Creator 15 | Explore all customization settings of the Notify class: visualize themes, create new ones, generate ready-to-copy code snippets, and more. 16 | 17 | ![myImage](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExbjl6ZmNvZTB0ZTVlNXhtdWljeW51b3FrY3IzMGJ0d2p5NnI3MGp1MyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/qp1j8F8VfazI4LV5i5/giphy.gif) 18 | 19 | --- 20 | 21 | ## Requirements 22 | - AutoHotkey v2 23 | 24 | --- 25 | 26 | ## How to Get Started 27 | - **Feature List & Instructions:** Visit the [forum's first post](https://www.autohotkey.com/boards/viewtopic.php?f=83&t=129635) for a detailed feature list and instructions. 28 | - **Documentation:** Refer to the `Notify.ahk` file, which includes documentation in JSDoc format. 29 | - **Examples:** Check the `Examples.ahk` file for usage examples. 30 | 31 | --- 32 | 33 | ## Donate 34 | If you find my AHK code useful and would like to show your appreciation, a donation would be greatly appreciated. Thank you! 35 | 36 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/xmcqcx) 37 | 38 | --- 39 | 40 | ## License 41 | - **MIT License** 42 | --------------------------------------------------------------------------------