Skip to content

Commit

Permalink
Update ColorButton.ahk
Browse files Browse the repository at this point in the history
- AHK v2.0 is now supported. 
- A border frame has been added to highlight the button when focused.
- Added an effect when the button is pressed.
  • Loading branch information
nperovic authored May 1, 2024
1 parent 53a173d commit 1ba2ee4
Showing 1 changed file with 197 additions and 70 deletions.
267 changes: 197 additions & 70 deletions ColorButton.ahk
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,113 @@
* @version 1.1.0
***********************************************************************/
#Requires AutoHotkey v2.1-alpha.9
#Requires AutoHotkey v2
#SingleInstance
class RECT {
left: i32, top: i32, right: i32, bottom: i32
}
; ================================================================================
; #region For v2.1-alpha.9 or later.
; If you're NOT using v2.1-alpha.9 or later, delete the section BELOW.
; If you're NOT using v2.1-alpha.9 or later, delete the section BELOW.
; If you're NOT using v2.1-alpha.9 or later, delete the section BELOW.
class NMCUSTOMDRAWINFO {
hdr : NMCUSTOMDRAWINFO.NMHDR
dwDrawStage: u32
hdc : uptr
rc : RECT
rc : NMCUSTOMDRAWINFO.RECT
dwItemSpec : uptr
uItemState : i32
lItemlParam: iptr
class RECT {
left: i32, top: i32, right: i32, bottom: i32
}
class NMHDR {
hwndFrom: uptr
idFrom : uptr
code : i32
}
}
; If you're NOT using v2.1-alpha.9 or later, delete the section ABOVE.
; If you're NOT using v2.1-alpha.9 or later, delete the section ABOVE.
; If you're NOT using v2.1-alpha.9 or later, delete the section ABOVE.
; #endregion
; ================================================================================
; #region For v2.0 user
; If you're using v2.1-alpha.9 or later, delete the section BELOW.
; If you're using v2.1-alpha.9 or later, delete the section BELOW.
; If you're using v2.1-alpha.9 or later, delete the section BELOW.
StructFromPtr(StructClass, Address) => StructClass(Address)
class NMCUSTOMDRAWINFO
{
static Call(ptr)
{
return {
hdr: {
hwndFrom: NumGet(ptr, 0 ,"uptr"),
idFrom : NumGet(ptr, 8 ,"uptr"),
code : NumGet(ptr, 16 ,"int")
},
dwDrawStage: NumGet(ptr, 24, "uint"),
hdc : NumGet(ptr, 32, "uptr"),
rc : RECT(
NumGet(ptr, 40, "uint"),
NumGet(ptr, 44, "uint"),
NumGet(ptr, 48, "int"),
NumGet(ptr, 52, "int")
),
dwItemSpec : NumGet(ptr, 56, "uptr"),
uItemState : NumGet(ptr, 64, "int"),
lItemlParam: NumGet(ptr, 72, "iptr")
}
RECT(left := 0, top := 0, right := 0, bottom := 0)
{
static ofst := Map("left", 0, "top", 4, "right", 8, "bottom", 12)
NumPut("int", left, "int", top, "int", right, "int", bottom, buf := Buffer(16))
for k, v in ofst
buf.DefineProp(k, {Get: NumGet.Bind(, v, "int"), Set: IntPut.Bind(v)})
return buf
IntPut(ofst, _, v) => NumPut("int", v, _, ofst)
}
}
}
class _Gui extends Gui
{
static __New() => (super.Prototype.OnMessage := ObjBindMethod(this, "OnMessage"))
static OnMessage(obj, Msg, Callback, AddRemove?)
{
OnMessage(Msg, _callback, AddRemove?)
obj.OnEvent("Close", g => OnMessage(Msg, _callback, 0))
_callback(wParam, lParam, uMsg, hWnd)
{
if (uMsg = Msg && hWnd = obj.hwnd)
return Callback(obj, wParam, lParam, uMsg)
}
}
}
; If you're using v2.1-alpha.9 or later, delete the section ABOVE.
; If you're using v2.1-alpha.9 or later, delete the section ABOVE.
; If you're using v2.1-alpha.9 or later, delete the section ABOVE.
; #endregion
; ================================================================================
/**
* The extended class for the built-in `Gui.Button` class.
* @method SetBackColor Set the button's background color
Expand All @@ -43,125 +127,168 @@ class _BtnColor extends Gui.Button

/**
* @param {Gui.Button} myBtn omitted.
* @param {integer} btnBgColor Button's background color.
* @param {integer} [colorBehindBtn] The color of the button's surrounding area. If omitted, if will be the same as `myGui.BackColor`.
* @param {integer} btnBgColor Button's background color. (RGB)
* @param {integer} [colorBehindBtn] The color of the button's surrounding area. If omitted, if will be the same as `myGui.BackColor`. **(Usually let it be transparent looks better.)**
* @param {integer} [roundedCorner] Specifies the rounded corner preference for the button. If omitted, :
* > For Windows 11: Enabled. (value: 9)
* > For Windows 10: Disabled.
*/
static SetBackColor(myBtn, btnBgColor, colorBehindBtn?, roundedCorner?)
{
static BS_FLAT := 0x8000
static BS_BITMAP := 0x0080
static IS_WIN11 := (VerCompare(A_OSVersion, "10.0.22200") >= 0)
static WM_CTLCOLORBTN := 0x0135
static NM_CUSTOMDRAW := -12
static WM_DESTROY := 0x0002
static WS_EX_COMPOSITED := 0x02000000
static WS_CLIPCHILDREN := 0x02000000
static WS_CLIPSIBLINGS := 0x04000000

clr := (IsNumber(btnBgColor) ? btnBgColor : Number((!InStr(btnBgColor, "0x") ? "0x" : "") btnBgColor))
hoverColor := BrightenColor(clr, 10)
btnBkColr := ColorHex(colorBehindBtn ?? myBtn.Gui.BackColor)
hbrush := CreateSolidBrush(btnBkColr)

myBtn.Gui.Opt("+E" WS_EX_COMPOSITED )
myBtn.Gui.OnEvent("Close", (*) => (DeleteObject(hbrush), unset))

myBtn.Opt("+" WS_CLIPSIBLINGS)
SetWindowTheme(myBtn.hwnd, IsColorDark(clr) ? "DarkMode_Explorer" : "Explorer")
rcRgn := unset
clr := IsNumber(btnBgColor) ? btnBgColor : ColorHex(btnBgColor)
isDark := IsColorDark(clr)
hoverColor := RgbToBgr(BrightenColor(clr, isDark ? 15 : -15))
pushedColor := RgbToBgr(BrightenColor(clr, isDark ? -10 : 10))
clr := RgbToBgr(clr)
btnBkColr := (colorBehindBtn??0) && RgbToBgr(ColorHex(myBtn.Gui.BackColor))
hbrush := btnBkColr ? CreateSolidBrush(btnBkColr) : GetStockObject(5)

myBtn.Gui.Opt("+" WS_CLIPSIBLINGS)
myBtn.Gui.OnMessage(WM_CTLCOLORBTN, ON_WM_CTLCOLORBTN)

if btnBkColr
myBtn.Gui.OnEvent("Close", (*) => DeleteObject(hbrush))

myBtn.Opt("+" (WS_CLIPSIBLINGS | BS_FLAT | BS_BITMAP))
SetWindowTheme(myBtn.hwnd, isDark ? "DarkMode_Explorer" : "Explorer")
myBtn.OnNotify(NM_CUSTOMDRAW, ON_NM_CUSTOMDRAW)
myBtn.Redraw()

myBtn.Gui.OnMessage(WM_CTLCOLORBTN, (GuiObj, wParam, *) {
SetBkMode(wParam, 0)
SelectObject(wParam, hbrush)
SetBkColor(wParam, btnBkColr)
return hbrush
}, -1)

myBtn.OnNotify(NM_CUSTOMDRAW, (gCtrl, lParam) {
static CDDS_PREPAINT := 1
static CDDS_PREERASE := 0x3
static CDIS_HOT := 0x0040
static DC_BRUSH := 18
static DC_PEN := 19
ON_WM_CTLCOLORBTN(GuiObj, wParam, lParam, Msg)
{
Critical(-1)

if btnBkColr
SelectObject(wParam, hbrush),
SetBkMode(wParam, 0),
SetBkColor(wParam, btnBkColr)

return hbrush
}

ON_NM_CUSTOMDRAW(gCtrl, lParam)
{
static CDDS_PREPAINT := 0x1
static CDDS_PREERASE := 0x3
static CDIS_HOT := 0x40
static CDRF_NOTIFYPOSTPAINT := 0x10
static CDRF_SKIPPOSTPAINT := 0x100
static CDRF_SKIPDEFAULT := 0x4
static CDRF_NOTIFYPOSTERASE := 0x40
static CDRF_DODEFAULT := 0x0
static DC_BRUSH := GetStockObject(18)
static DC_PEN := GetStockObject(19)

/** @type {NMCUSTOMDRAWINFO} */
Critical(-1)

lpnmCD := StructFromPtr(NMCUSTOMDRAWINFO, lParam)
if (lpnmCD.hdr.code != NM_CUSTOMDRAW || lpnmCD.hdr.hwndFrom != gCtrl.hwnd || (lpnmCD.dwDrawStage != CDDS_PREPAINT))

if (lpnmCD.hdr.code != NM_CUSTOMDRAW || lpnmCD.hdr.hwndFrom != gCtrl.hwnd)
return

switch lpnmCD.dwDrawStage {
case CDDS_PREERASE:
{
if (roundedCorner ?? IS_WIN11) {
rcRgn := CreateRoundRectRgn(lpnmCD.rc.left, lpnmCD.rc.top, lpnmCD.rc.right, lpnmCD.rc.bottom, roundedCorner ?? 9, roundedCorner ?? 9)
SetWindowRgn(gCtrl.hwnd, rcRgn, 1)
}

Critical()
SetBkMode(lpnmCD.hdc, 0)
return CDRF_NOTIFYPOSTERASE
}
case CDDS_PREPAINT:
{
brushColor := (!(lpnmCD.uItemState & CDIS_HOT) ? clr : (GetKeyState("LButton", "P")) ? pushedColor : hoverColor)

brushColor := RgbToBgr(GetKeyState("LButton", "P") || !(lpnmCD.uItemState & CDIS_HOT) ? clr : hoverColor )
SelectObject(lpnmCD.hdc, DC_BRUSH)
SetDCBrushColor(lpnmCD.hdc, brushColor)

SelectObject(lpnmCD.hdc, DC_PEN)
SetDCPenColor(lpnmCD.hdc, gCtrl.Focused ? 0xFFFFFF : brushColor)

if (IS_WIN11 || IsSet(roundedCorner)) {
rcRgn := CreateRoundRectRgn(lpnmCD.rc.left, lpnmCD.rc.top, lpnmCD.rc.right, lpnmCD.rc.bottom, roundedCorner ?? 9, roundedCorner ?? 9)
SetWindowRgn(lpnmCD.hdr.hwndFrom, rcRgn, 0)
}
if gCtrl.Focused
DrawFocusRect(lpnmCD.hdc, lpnmCD.rc)

rounded := !!(rcRgn ?? 0)

SetBkMode(lpnmCD.hdc, 0)
SetDCBrushColor(lpnmCD.hdc, brushColor)
SetDCPenColor(lpnmCD.hdc, brushColor)
SelectObject(lpnmCD.hdc, bru := GetStockObject(DC_BRUSH))
SelectObject(lpnmCD.hdc, GetStockObject(DC_PEN))
FillRect(lpnmCD.hdc, lpnmCD.rc, bru)
RoundRect(lpnmCD.hdc, lpnmCD.rc.left, lpnmCD.rc.top, lpnmCD.rc.right - rounded, lpnmCD.rc.bottom - rounded, roundedCorner ?? 9, roundedCorner ?? 9)

if IsSet(rcRgn)
DeleteObject(rcRgn)
if rounded {
DeleteObject(rcRgn)
rcRgn := ""
}

return true
})
return CDRF_NOTIFYPOSTPAINT
}}

return CDRF_DODEFAULT
}

RgbToBgr(color) => (IsInteger(color) ? ((Color >> 16) & 0xFF) | (Color & 0x00FF00) | ((Color & 0xFF) << 16) : NUMBER(RegExReplace(STRING(color), "Si)c?(?:0x)?(?<R>\w{2})(?<G>\w{2})(?<B>\w{2})", "0x${B}${G}${R}")))
static RgbToBgr(color) => (IsInteger(color) ? ((Color >> 16) & 0xFF) | (Color & 0x00FF00) | ((Color & 0xFF) << 16) : NUMBER(RegExReplace(STRING(color), "Si)c?(?:0x)?(?<R>\w{2})(?<G>\w{2})(?<B>\w{2})", "0x${B}${G}${R}")))

CreateRoundRectRgn(nLeftRect, nTopRect, nRightRect, nBottomRect, nWidthEllipse, nHeightEllipse) => DllCall('Gdi32\CreateRoundRectRgn', 'int', nLeftRect, 'int', nTopRect, 'int', nRightRect, 'int', nBottomRect, 'int', nWidthEllipse, 'int', nHeightEllipse, 'ptr')
static CreateRoundRectRgn(nLeftRect, nTopRect, nRightRect, nBottomRect, nWidthEllipse, nHeightEllipse) => DllCall('Gdi32\CreateRoundRectRgn', 'int', nLeftRect, 'int', nTopRect, 'int', nRightRect, 'int', nBottomRect, 'int', nWidthEllipse, 'int', nHeightEllipse, 'ptr')

CreateSolidBrush(crColor) => DllCall('Gdi32\CreateSolidBrush', 'uint', crColor, 'ptr')
static CreateSolidBrush(crColor) => DllCall('Gdi32\CreateSolidBrush', 'uint', crColor, 'ptr')

ColorHex(clr) => Number((!InStr(clr, "0x") ? "0x" : "") clr)
static ColorHex(clr) => Number((!InStr(clr, "0x") ? "0x" : "") clr)

static DrawFocusRect(hDC, lprc) => DllCall("User32\DrawFocusRect", "ptr", hDC, "ptr", lprc, "int")

GetStockObject(fnObject) => DllCall('Gdi32\GetStockObject', 'int', fnObject, 'ptr')

SetDCPenColor(hdc, crColor) => DllCall('Gdi32\SetDCPenColor', 'ptr', hdc, 'uint', crColor, 'uint')
static SetDCPenColor(hdc, crColor) => DllCall('Gdi32\SetDCPenColor', 'ptr', hdc, 'uint', crColor, 'uint')

SetDCBrushColor(hdc, crColor) => DllCall('Gdi32\SetDCBrushColor', 'ptr', hdc, 'uint', crColor, 'uint')
static SetDCBrushColor(hdc, crColor) => DllCall('Gdi32\SetDCBrushColor', 'ptr', hdc, 'uint', crColor, 'uint')

SetWindowRgn(hWnd, hRgn, bRedraw) => DllCall("User32\SetWindowRgn", "ptr", hWnd, "ptr", hRgn, "int", bRedraw, "int")
static SetWindowRgn(hWnd, hRgn, bRedraw) => DllCall("User32\SetWindowRgn", "ptr", hWnd, "ptr", hRgn, "int", bRedraw, "int")

DeleteObject(hObject) => DllCall('Gdi32\DeleteObject', 'ptr', hObject, 'int')
static DeleteObject(hObject) {
DllCall('Gdi32\DeleteObject', 'ptr', hObject, 'int')
}

FillRect(hDC, lprc, hbr) => DllCall("User32\FillRect", "ptr", hDC, "ptr", lprc, "ptr", hbr, "int")
static FillRect(hDC, lprc, hbr) => DllCall("User32\FillRect", "ptr", hDC, "ptr", lprc, "ptr", hbr, "int")

IsColorDark(clr) =>
static IsColorDark(clr) =>
( (clr >> 16 & 0xFF) / 255 * 0.2126
+ (clr >> 8 & 0xFF) / 255 * 0.7152
+ (clr & 0xFF) / 255 * 0.0722 < 0.5 )

RGB(R := 255, G := 255, B := 255) => ((R << 16) | (G << 8) | B)
static RGB(R := 255, G := 255, B := 255) => ((R << 16) | (G << 8) | B)

BrightenColor(clr, perc := 5) => ((p := perc / 100 + 1), RGB(Round(Min(255, (clr >> 16 & 0xFF) * p)), Round(Min(255, (clr >> 8 & 0xFF) * p)), Round(Min(255, (clr & 0xFF) * p))))
static BrightenColor(clr, perc := 5) => ((p := perc / 100 + 1), RGB(Round(Min(255, (clr >> 16 & 0xFF) * p)), Round(Min(255, (clr >> 8 & 0xFF) * p)), Round(Min(255, (clr & 0xFF) * p))))

RoundRect(hdc, nLeftRect, nTopRect, nRightRect, nBottomRect, nWidth, nHeight) => DllCall('Gdi32\RoundRect', 'ptr', hdc, 'int', nLeftRect, 'int', nTopRect, 'int', nRightRect, 'int', nBottomRect, 'int', nWidth, 'int', nHeight, 'int')
static RoundRect(hdc, nLeftRect, nTopRect, nRightRect, nBottomRect, nWidth, nHeight) => DllCall('Gdi32\RoundRect', 'ptr', hdc, 'int', nLeftRect, 'int', nTopRect, 'int', nRightRect, 'int', nBottomRect, 'int', nWidth, 'int', nHeight, 'int')

SetTextColor(hdc, color) => DllCall("SetTextColor", "Ptr", hdc, "UInt", color)
static SetTextColor(hdc, color) => DllCall("SetTextColor", "Ptr", hdc, "UInt", color)

SetWindowTheme(hwnd, appName, subIdList?) => DllCall("uxtheme\SetWindowTheme", "ptr", hwnd, "ptr", StrPtr(appName), "ptr", subIdList ?? 0)
static SetWindowTheme(hwnd, appName, subIdList?) => DllCall("uxtheme\SetWindowTheme", "ptr", hwnd, "ptr", StrPtr(appName), "ptr", subIdList ?? 0)

SelectObject(hdc, hgdiobj) => DllCall('Gdi32\SelectObject', 'ptr', hdc, 'ptr', hgdiobj, 'ptr')
static SelectObject(hdc, hgdiobj) => DllCall('Gdi32\SelectObject', 'ptr', hdc, 'ptr', hgdiobj, 'ptr')

SetBkColor(hdc, crColor) => DllCall('Gdi32\SetBkColor', 'ptr', hdc, 'uint', crColor, 'uint')
static SetBkColor(hdc, crColor) => DllCall('Gdi32\SetBkColor', 'ptr', hdc, 'uint', crColor, 'uint')

SetBkMode(hdc, iBkMode) => DllCall('Gdi32\SetBkMode', 'ptr', hdc, 'int', iBkMode, 'int')
static SetBkMode(hdc, iBkMode) => DllCall('Gdi32\SetBkMode', 'ptr', hdc, 'int', iBkMode, 'int')
}
}

/*
; Example
/*
myGui := Gui()
myGui.SetFont("cWhite s24", "Segoe UI")
myGui.BackColor := 0x2c2c2c
btn := myGui.AddButton(, "SUPREME")
btn.SetBackColor(0xaa2031)
btn2 := myGui.AddButton(, "SUPREME")
btn2.SetBackColor(0xffd155)
myGui.Show("w300 h300")

0 comments on commit 1ba2ee4

Please sign in to comment.