Coded in PB5.60+
Code: Select all
DeclareModule SkinnableWindow
Global NewMap SkinColors.i()
Global NewMap SkinSizes.i()
Declare InitSkin()
Declare SkinWindow(Window, IsResizable=#True)
EndDeclareModule
Module SkinnableWindow
EnableExplicit
Prototype DrawCustomSkin(*skinData)
Structure SKIN_ITEM
name$
parentBorder.i
hit.q
;relative position for drawing
top.f
left.f
right.f
bottom.f
;absolute position for hit detection
absTop.f
absLeft.f
absRight.f
absBottom.f
EndStructure
Structure SKIN_DATA
window.i
isResizable.b
isActive.b
;border gadgets
activeBorder.i
upBorder.i
downBorder.i
leftBorder.i
rightBorder.i
;hit detection
currentHit.q
previousHit.q
;capture handler
captureHit.q
captX.i
captY.i
;drawing
drawCustomBorders.DrawCustomSkin
drawCustomBorderItems.DrawCustomSkin
List items.SKIN_ITEM()
EndStructure
Global NewList SkinItems.SKIN_ITEM()
Global NewMap SkinData.SKIN_DATA()
EnumerationBinary
#UP_BORDER
#DOWN_BORDER
#LEFT_BORDER
#RIGHT_BORDER
#CAPTION_BAR
#CAPTION_TITLE
#CAPTION_ICON
#BUTTON_CLOSE
#BUTTON_MIN
#BUTTON_MAX
;combined flags for checking hit
#ANY_BORDERS=#UP_BORDER | #DOWN_BORDER | #LEFT_BORDER | #RIGHT_BORDER
#ANY_CAPTION=#CAPTION_BAR | #CAPTION_ICON | #CAPTION_TITLE
EndEnumeration
Procedure AddBorderItem(border$, name$, top.f, left.f, right.f, bottom.f, customHit=0)
ResetList(SkinItems())
Protected *skinZone.SKIN_ITEM=InsertElement(SkinItems())
With *skinZone
\name$=name$
Select LCase(name$)
Case "caption"
\hit=#CAPTION_BAR
Case "title"
\hit=#CAPTION_TITLE
Case "icon"
\hit=#CAPTION_ICON
Case "close"
\hit=#BUTTON_CLOSE
Case "max"
\hit=#BUTTON_MAX
Case "min"
\hit=#BUTTON_MIN
Default
\hit=customHit
EndSelect
Select LCase(border$)
Case "up"
\parentBorder=#UP_BORDER
Case "down"
\parentBorder=#DOWN_BORDER
Case "left"
\parentBorder=#LEFT_BORDER
Case "right"
\parentBorder=#RIGHT_BORDER
Default
DebuggerError("border$ accepts one of following values: up, down, left, right")
EndSelect
\top=top
\left=left
\right=right
\bottom=bottom
EndWith
EndProcedure
Procedure DrawBorderItems(*skinData.SKIN_DATA)
With *skinData
Protected x, y, w, h, cBox, cIcon
Protected BorderSize=SkinSizes("border"), CornerSize=SkinSizes("corner"), CaptionSize=SkinSizes("caption")
StartVectorDrawing(CanvasVectorOutput(\upBorder))
;close
If \currentHit & #BUTTON_CLOSE
cBox=SkinColors("button_close")
cIcon=SkinColors("stroke_hover")
ElseIf \isActive
cBox=SkinColors("button")
cIcon=SkinColors("stroke")
Else
cBox=SkinColors("button_inactive")
cIcon=SkinColors("stroke_inactive")
EndIf
w=45 : h=CaptionSize : x=GadgetWidth(\upBorder)-BorderSize-w : y=0
ResetCoordinates()
TranslateCoordinates(x, y)
AddPathBox(0, 0, w, h)
VectorSourceColor(cBox)
FillPath()
VectorSourceColor(cIcon)
MovePathCursor(w / 2-5, h / 2 - 5)
AddPathLine(w / 2 + 4, h / 2 + 4)
StrokePath(1)
MovePathCursor(w / 2-5, h / 2 + 4)
AddPathLine(w / 2 + 4, h / 2-5)
StrokePath(1)
;maximize
If \currentHit & #BUTTON_MAX
cBox=SkinColors("button_hover")
cIcon=SkinColors("stroke_hover")
ElseIf \isActive
cBox=SkinColors("button")
cIcon=SkinColors("stroke")
Else
cBox=SkinColors("button_inactive")
cIcon=SkinColors("stroke_inactive")
EndIf
ResetCoordinates()
TranslateCoordinates(x-w, y)
AddPathBox(0, 0, w, h)
VectorSourceColor(cBox)
FillPath()
VectorSourceColor(cIcon)
AddPathBox(w / 2-5, h / 2-5, 10, 10)
StrokePath(1)
;minimize
If \currentHit & #BUTTON_MIN
cBox=SkinColors("button_hover")
cIcon=SkinColors("stroke_hover")
ElseIf \isActive
cBox=SkinColors("button")
cIcon=SkinColors("stroke")
Else
cBox=SkinColors("button_inactive")
cIcon=SkinColors("stroke_inactive")
EndIf
ResetCoordinates()
TranslateCoordinates(x-w*2, y)
AddPathBox(0, 0, w, h)
VectorSourceColor(cBox)
FillPath()
VectorSourceColor(cIcon)
MovePathCursor(w / 2-5, h / 2)
AddPathLine(w / 2 + 4, h / 2)
StrokePath(1)
StopVectorDrawing()
EndWith
EndProcedure
Procedure DrawBorders(*skinData.SKIN_DATA)
With *skinData
If \DrawCustomBorders
\DrawCustomBorders(*skinData)
Else
; Draw 4 borders
If \isActive : Protected BorderColor=SkinColors("border") : Else : BorderColor=SkinColors("border_inactive") : EndIf
StartDrawing(CanvasOutput(\upBorder))
Box(0, 0, OutputWidth(), OutputHeight(), BorderColor)
StopDrawing()
StartDrawing(CanvasOutput(\leftBorder))
Box(0, 0, OutputWidth(), OutputHeight(), BorderColor)
StopDrawing()
StartDrawing(CanvasOutput(\downBorder))
Box(0, 0, OutputWidth(), OutputHeight(), BorderColor)
StopDrawing()
StartDrawing(CanvasOutput(\rightBorder))
Box(0, 0, OutputWidth(), OutputHeight(), BorderColor)
StopDrawing()
EndIf
If \DrawCustomBorderItems
\DrawCustomBorderItems(*skinData)
Else
; Draw border items
DrawBorderItems(*skinData.SKIN_DATA)
EndIf
EndWith
EndProcedure
Procedure DrawShadow(*skinData.SKIN_DATA, IsEnabled=#True, TransparencyColor=#Cyan)
CompilerIf #PB_Compiler_OS=#PB_OS_Windows
Static *dwmapi
Static *DwmExtendFrameIntoClientArea
Static *DwmSetWindowAttribute
Protected hWnd=WindowID(*skinData\window)
If Not *dwmapi
*dwmapi=GetModuleHandle_("dwmapi")
If *dwmapi And Not *DwmExtendFrameIntoClientArea And Not *DwmSetWindowAttribute
*DwmExtendFrameIntoClientArea=GetProcAddress_(*dwmapi, Ascii("DwmExtendFrameIntoClientArea"))
*DwmSetWindowAttribute=GetProcAddress_(*dwmapi, Ascii("DwmSetWindowAttribute"))
EndIf
EndIf
If *DwmExtendFrameIntoClientArea And *DwmSetWindowAttribute
CompilerIf Not Defined(DWMWA_NCRENDERING_POLICY, #PB_Constant) : #DWMWA_NCRENDERING_POLICY=2 : CompilerEndIf
CompilerIf Not Defined(DWMNCRP_ENABLED, #PB_Constant) : #DWMNCRP_ENABLED=2 : CompilerEndIf
If IsEnabled
;transform into opaque layered window
SetWindowLongPtr_(hWnd, #GWL_EXSTYLE, GetWindowLongPtr_(hWnd, #GWL_EXSTYLE) | #WS_EX_LAYERED)
SetLayeredWindowAttributes_(hWnd, TransparencyColor, 255, #LWA_COLORKEY)
Else
SetWindowLongPtr_(hWnd, #GWL_EXSTYLE, GetWindowLongPtr_(hWnd, #GWL_EXSTYLE) & ~#WS_EX_LAYERED)
EndIf
;enable drop shadow
Protected *Margins=AllocateMemory(4*SizeOf(Long))
FillMemory(*Margins, 4*SizeOf(Long), IsEnabled, #PB_Long)
Protected value.Integer\i=#DWMNCRP_ENABLED
CallFunctionFast(*DwmSetWindowAttribute, hWnd, #DWMWA_NCRENDERING_POLICY, value, SizeOf(value))
CallFunctionFast(*DwmExtendFrameIntoClientArea, hWnd, *Margins)
EndIf
ProcedureReturn -1
CompilerEndIf
EndProcedure
Procedure ResizeBorderItems(*skinData.SKIN_DATA)
With *skinData
Protected win=\window, parentBorder
ClearList(\items())
ForEach SkinItems()
Select SkinItems()\parentBorder
Case #UP_BORDER : parentBorder=\upBorder
Case #DOWN_BORDER : parentBorder=\downBorder
Case #LEFT_BORDER : parentBorder=\leftBorder
Case #RIGHT_BORDER : parentBorder=\rightBorder
EndSelect
Protected top=SkinItems()\top + Bool(Sign(SkinItems()\top)<0) * GadgetHeight(parentBorder)
Protected left=SkinItems()\left + Bool(Sign(SkinItems()\left)<0) *GadgetWidth(parentBorder)
Protected right=SkinItems()\right + Bool(Sign(SkinItems()\right)<0) *GadgetWidth(parentBorder)
Protected bottom=SkinItems()\bottom + Bool(Sign(SkinItems()\bottom)<0) * GadgetHeight(parentBorder)
AddElement(\items())
\items()=SkinItems()
\items()\parentBorder=parentBorder
\items()\top=top
\items()\left=left
\items()\right=right
\items()\bottom=bottom
\items()\absTop=top + GadgetY(parentBorder)
\items()\absLeft=left + GadgetX(parentBorder)
\items()\absRight=right + GadgetX(parentBorder)
\items()\absBottom=bottom + GadgetY(parentBorder)
Next
EndWith
EndProcedure
Procedure ResizeBorders(*skinData.SKIN_DATA)
With *skinData
Protected win=\window
Protected BorderSize=SkinSizes("border"), CornerSize=SkinSizes("corner"), CaptionSize=SkinSizes("caption")
;resize border gadgets
ResizeGadget(\upBorder, 0, 0, WindowWidth(win), CaptionSize)
ResizeGadget(\leftBorder, 0, CaptionSize, BorderSize, WindowHeight(win) - BorderSize - CaptionSize)
ResizeGadget(\rightBorder, WindowWidth(win) - BorderSize, CaptionSize, BorderSize, WindowHeight(win) - BorderSize - CaptionSize)
ResizeGadget(\downBorder, 0, WindowHeight(win) - BorderSize, WindowWidth(win), BorderSize)
;calculate position of border items
ResizeBorderItems(*skinData.SKIN_DATA)
EndWith
EndProcedure
Procedure.i HitBorders(*skinData.SKIN_DATA)
With *skinData
Protected win=\window
Protected gadget=\activeBorder
Protected mouseX=WindowMouseX(win)
Protected mouseY=WindowMouseY(win)
Protected BorderSize=SkinSizes("border"), CornerSize=SkinSizes("corner"), CaptionSize=SkinSizes("caption")
\currentHit=0
; Detect borders
If \isResizable
If (mouseX>-1 And mouseX<CornerSize)
\currentHit | #LEFT_BORDER
EndIf
If (mouseY>-1 And mouseY<CornerSize)
\currentHit | #UP_BORDER
EndIf
If (mouseX>=WindowWidth(win) - CornerSize)
\currentHit | #RIGHT_BORDER
EndIf
If (mouseY>=WindowHeight(win) - CornerSize)
\currentHit | #DOWN_BORDER
EndIf
EndIf
; Change cursor
Select \currentHit
Case 0
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_Default)
Case #UP_BORDER, #DOWN_BORDER
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_UpDown)
Case #LEFT_BORDER, #RIGHT_BORDER
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_LeftRight)
CompilerIf #PB_Compiler_OS=#PB_OS_Windows
Case #LEFT_BORDER | #UP_BORDER, #RIGHT_BORDER | #DOWN_BORDER
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_LeftUpRightDown)
Case #LEFT_BORDER | #DOWN_BORDER, #RIGHT_BORDER | #UP_BORDER
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_LeftDownRightUp)
CompilerElse
Default
SetGadgetAttribute(gadget, #PB_Canvas_Cursor, #PB_Cursor_Cross)
CompilerEndIf
EndSelect
; Detect zones
ForEach \items()
If mouseX>=\items()\absLeft And mouseX<=\items()\absRight And mouseY>=\items()\absTop And mouseY<=\items()\absBottom
\currentHit | \items()\hit
Break
EndIf
Next
ProcedureReturn \currentHit
EndWith
EndProcedure
Procedure SkinEvents()
Static rect.RECT
Protected event=Event()
Protected eventType=EventType()
Protected gadget=EventGadget()
Protected win=EventWindow()
Protected *skinData.SKIN_DATA=SkinData("" + win)
With *skinData
If event=#PB_Event_ActivateWindow
\isActive=#True
DrawBorders(*skinData)
ElseIf event=#PB_Event_DeactivateWindow
\isActive=#False
DrawBorders(*skinData)
ElseIf event=#PB_Event_Gadget
\activeBorder=gadget
Select eventType
Case #PB_EventType_MouseEnter, #PB_EventType_MouseLeave
HitBorders(*skinData)
DrawBorders(*skinData)
Case #PB_EventType_LeftButtonUp
If \captureHit
\captureHit=0
DrawShadow(*skinData, 1)
EndIf
Case #PB_EventType_LeftButtonDown
If HitBorders(*skinData)
\captureHit=\currentHit
\captX=WindowMouseX(win)
\captY=WindowMouseY(win)
rect\left=WindowX(win)
rect\top=WindowY(win)
rect\right=rect\left + WindowWidth(win)
rect\bottom=rect\top + WindowHeight(win)
EndIf
Case #PB_EventType_MouseMove
If GetGadgetAttribute(gadget, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton
If \captureHit & #ANY_BORDERS
Protected x=#PB_Ignore, y=#PB_Ignore, w=#PB_Ignore, h=#PB_Ignore
If \captureHit & #LEFT_BORDER
rect\left=DesktopMouseX() - \captX : w=rect\right-rect\left
x=rect\left
EndIf
If \captureHit & #RIGHT_BORDER
w=(rect\right - rect\left) + (DesktopMouseX() - \captX-rect\left)
EndIf
If \captureHit & #UP_BORDER
rect\top=DesktopMouseY() - \captY : h=rect\bottom-rect\top
y=rect\top
EndIf
If \captureHit & #DOWN_BORDER
h=(rect\bottom-rect\top) + (DesktopMouseY() - \captY-rect\top)
EndIf
DrawShadow(*skinData, 0)
ResizeWindow(win, x, y, w, h)
ResizeBorders(*skinData)
DrawBorders(*skinData)
ProcedureReturn
ElseIf \captureHit & #ANY_CAPTION
ResizeWindow(win, DesktopMouseX() - \captX, DesktopMouseY() - \captY, #PB_Ignore, #PB_Ignore)
ProcedureReturn
EndIf
EndIf
HitBorders(*skinData)
If \currentHit<>\previousHit
\previousHit=\currentHit
DrawBorders(*skinData)
EndIf
EndSelect
EndIf
EndWith
EndProcedure
Procedure InitSkin()
; default colors
SkinColors("background")=$7C7C7C
SkinColors("border")=$6C6C6C
SkinColors("border_inactive")=$6C6C6C
SkinColors("button_close")=RGBA(230, 0, 0, 255)
SkinColors("button")=RGBA(0, 0, 0, 0)
SkinColors("button_inactive")=RGBA(0, 0, 0, 0)
SkinColors("button_hover")=RGBA(0, 0, 0, 30)
SkinColors("button_click")=RGBA(0, 0, 0, 50)
SkinColors("stroke")=RGBA(0, 0, 0, 255)
SkinColors("stroke_inactive")=RGBA(0, 0, 0, 100)
SkinColors("stroke_hover")=RGBA(255, 255, 255, 255)
SkinColors("stroke_click")=RGBA(255, 255, 255, 255)
; default sizes
SkinSizes("border")=10
SkinSizes("corner")=8
SkinSizes("caption")=30
; default border items
Protected BorderSize=SkinSizes("border"), CornerSize=SkinSizes("corner"), CaptionSize=SkinSizes("caption")
AddBorderItem("up", "caption", 0, BorderSize, -BorderSize-1, CaptionSize-1)
AddBorderItem("up", "title", 0, BorderSize, -45*3-BorderSize-5, CaptionSize-1)
AddBorderItem("up", "close", 0, -45-BorderSize, -BorderSize-1, CaptionSize-1)
AddBorderItem("up", "max", 0, -45*2-BorderSize, -45-BorderSize-1, CaptionSize-1)
AddBorderItem("up", "min", 0, -45*3-BorderSize, -45*2-BorderSize-1, CaptionSize-1)
EndProcedure
Procedure SkinWindow(Window, IsResizable=#True)
AddMapElement(SkinData(), "" + Window)
With SkinData()
\window=Window
\isResizable=IsResizable
\isActive=#True
; Skin borders
\upBorder=CanvasGadget(#PB_Any, 0, 0, 0, 0)
\leftBorder=CanvasGadget(#PB_Any, 0, 0, 0, 0)
\rightBorder=CanvasGadget(#PB_Any, 0, 0, 0, 0)
\downBorder=CanvasGadget(#PB_Any, 0, 0, 0, 0)
ResizeBorders(SkinData())
; Skin events
BindEvent(#PB_Event_ActivateWindow, @SkinEvents(), Window)
BindEvent(#PB_Event_DeactivateWindow, @SkinEvents(), Window)
BindEvent(#PB_Event_Gadget, @SkinEvents(), Window, \upBorder)
BindEvent(#PB_Event_Gadget, @SkinEvents(), Window, \downBorder)
BindEvent(#PB_Event_Gadget, @SkinEvents(), Window, \leftBorder)
BindEvent(#PB_Event_Gadget, @SkinEvents(), Window, \rightBorder)
; Skin drawing (background, borders and shadow)
SetWindowColor(Window, SkinColors("background"))
DrawBorders(SkinData())
DrawShadow(SkinData())
EndWith
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
; ********************
; EXAMPLE
; ********************
UseModule SkinnableWindow
InitSkin()
If OpenWindow(10, 600, 600, 320, 240, "Borderless Window", #PB_Window_BorderLess)
SkinWindow(10, #True)
ButtonGadget(100, 20, 50, 50, 20, "Exit")
ButtonGadget(200, 80, 50, 50, 20, "Min")
OpenWindow(11, 300, 300, 320, 240, "Borderless Window2", #PB_Window_BorderLess)
SkinWindow(11, #True)
Repeat
Define event=WaitWindowEvent()
If event=#PB_Event_Gadget
Select EventGadget()
Case 100
Break
Case 200
SetWindowState(10, #PB_Window_Minimize)
EndSelect
EndIf
Until event=#PB_Event_CloseWindow
EndIf
CompilerEndIf