I have been using this for a long time and decided to dust it down and address some of the issues that have come to the surface in recent years. Here is version 2.0... the changes are in the Release Info section at the start of the file. I would be keen to hear of any problems... or otherwise!
RichardL
Code: Select all
; ================================
; Name: KnobGadget
; Version: 2.0
; Author: RichardL
; Date: 22nd Feb 2016
; OS: Windows
; PB ver.: 5.41
; License: Free
; ====================================
;{ Release info...
; 0.90 First release version
; 0.91 Added poor man's anti-aliasing to backdrop
; Improved Font specification
; Improved radial markers... rather nice!
; Added flag bit to control 'heat ring'
; Demo includes slaving two knobs.
; 0.92 4th June 2013
; Added 'jazzy' knob top with control bit in flag
; Added frame option with control bit in flag
; Added frame border options
; Improved scaling and caption positioning4
; 1.00 5th June 2013
; Modified control range. Knob 300 degrees = 16383 'clicks'
; Demo shows how to apply small steps with MouseWheel
; Mouse wheel UP - Fine adjust 1 clicks per mouse increment
; Mouse wheel DOWN - COARSE adjust 20 clicks per mouse increment
; Now code is an IncludeFile() (=.PBI) but the demo code is automatically
; compiled if this file is compiled on its own for testing, thanks to
; <<< CompilerIf #PB_Compiler_IsMainFile >>>
; 1.01 23rd June 2013 (505)
; Fixed benign bug with \KnobRate
; Added flag bit and code to create a borderless version
; Modified demo to show all flag options
; Code version 05 as I'm changing the input syntax and other things
; to allow Max and Min like a ScrollBar().
; Added #MultiTurn1 flag. Knob goes round with mouse but 'span'
; is spread over 'N' revolutions.
; 1.02 8th July 2013 (542)
; Added #SWITCHKNOB - Knob clicks to one of 'N' positions.
; Added #PLAINKKNOB - No Graduations or RING_Heat... good for SWITCH version.
; 1.03
; *** WARNING *** CHANGE MAY BE REQUIRED TO USER CODE
; Changed 'wParam' value sent to callback, it was a WORD giving the knob's position.
; It is now the index into knob's data. Get knob position with Knob(wParam)\Position.
; Modification allows MIN, MAX and returned values to be INTEGER values.
; Added option to show a LED indicator top left and switch it On when value
; exceeds knob range / 64. State of LED reported by flag bit. Bit UNTIDY at present.
; 2.0 Major hack.
; DONE Now uses BindGadgetEvent() to invoke servicing of Gadget,
; so removed local event manager for dragging knob.... now more system friendly.
; Now only reports when knob changes.
; DONE New feature. Knobs now report different values if SHIFT is pressed when selected.
; Check for shift status knob is using by checking \ShiftFlag for selected knob.
; Knob position is in \Position and \PositionShift.
; DONE C/CanvasID/CanvasNum/A to avoid confusion with PB conventions for 'ID'
; DONE Added syntax info to KnobGaqdget()
; DONE Define rates for mouse wheel up / down for each Knob(). See SetKnobWheelRates(SloR.i,FstR.i)
; DONE Reduce Knob() creep when selected many times...
; TODO EnableExplicit ... Soon folks!
; Caption update so knob value can be shown... in user units.
; BUGS - SetKnobState() sometimes wrong on multi-turn pots... I think.
;}
;{ Useful references
; http://msdn.microsoft.com/en-us/library/windows/desktop/ms644931(v=vs.85).aspx
; http://www.purebasic.fr/english/viewtopic.php?f=12&T=54117&p=409390&hilit=Meter+Gadget#p409390
;}
;{ Procedure declarations
Declare SetKnobState(gadNum,Value.f=0)
Declare GetKnobState(gadNum)
Declare SetKnobWheelRates(SloR.i,FstR.i)
;}
;{ Structures, constants etc
;- KnobGadget() types and option flags
EnumerationBinary
; Knob types... call KnobGadget() with Flags including just ONE of these.
#POTKNOB
#MULTITURN1
#MULTITURN2
#SWITCHKNOB
#PLAINKKNOB
; Additional flags to control optional features...
#RING_Heat
#FRAME_Switch
#JAZZ_top
#BORDER
#LED_Switch
EndEnumeration
; Where N is the 'gearing rate'
; ** MULTITURN1 - Knob tracks mouse and value increase by 1/Number of turns
; MULTITURN2 - Knob rotates at Rate/N
Structure KNOBTYPE
width.i ; Knob gadget width - INSIDE FRAME
height.i ; knob height
KnobImage.i ; Image that has the knob background.. NOT the pointer
CanvasNum.i ; Gadget number
CentreX.i ; Knob co-ords within the gadget
CentreY.i
BackColour.i
FrameColourInner.i
FrameColourOuter.i
KnobRad.i ; Radius of knob
KnobColour.i
ScaleColour.i ; Radial lines around knob
Caption.s ; Knob caption
CaptionColour.i ; and the colour
DotColour.i ; The 'pointer' dot and line on top of knob
LastDotX.i ; Position of dot. SHIFT key up
LastDotY.i
LastShiftDotX.i ; Position of dot. SHIFT key down
LastShiftDotY.i
Position.f ; Knob value, returned with GetKnobState(GadNum) or via callback
PositionShift.f
ShiftFlag.i
Gate.i
KnobMin.i
KnobMax.i
KnobClick.i
KnobRate.f
KnobTurns.i
LastF.f
flags.i ; Bitwise option flags...
CaptionFont.s ; Use this font for knob caption.
CaptionFontHt.i ; with this height.
WheelRateSlow.i ; Knob delta per wheel click - Wheel up.
WheelRateFast.i ; Knob delta per wheel click - Wheel down.
EndStructure
#PBM_KNOB = #WM_APP + 1 ; Application specific callback value
#Scale = 3 ; Scaling factor for the 'poor man's anti-aliasing)
Global KnobDefaults.KNOBTYPE ; KnobDefaults
Global Dim Knob.KNOBTYPE(1)
Global NewList KnobList.i()
; Default knob parameters...
; SOME may be adjusted BEFORE a knob is created.
With KnobDefaults
; These values can be programmed directly or with the procedures provided.
\BackColour = GetSysColor_(#COLOR_BTNFACE) ; Background
\ScaleColour = #Black ; Scale
\KnobColour = #Black ; Knob body
\DotColour = #Red ; Dot and line
\CaptionColour = #Cyan ; Caption
\FrameColourInner = #Green ; Frame 1
\FrameColourOuter = #White ; Frame 2
\CaptionFont = "CourierNew" ; Caption font
\CaptionFontHt = 12 ; Caption font size
\KnobTurns = 1 ; Rotations for for multi-turn
\Caption = "Test Knob"
\WheelRateSlow = 1
\WheelRateFast = 50
; Leave alone...
\KnobMin = 0
\KnobMax = 16383
\KnobRate = (\KnobMax - \KnobMin)/300
EndWith
;}
Procedure SetGadgetMouseXY(Win,Gadget,MX,MY) ; Position mouse pointer at specified co-ordinated within a gadget
MX + WindowX(Win,#PB_Window_InnerCoordinate) + GadgetX(Gadget)
MY + WindowY(Win,#PB_Window_InnerCoordinate) + GadgetY(Gadget)
SetCursorPos_(MX,MY)
EndProcedure
; Use these calls to adjust the defualt values for your next KnobGadget() --- BEFORE it is created.
; (You are ALSO free to adjust values in KnobDefaults\xxxx)
Procedure SetKnobBackgroundColour(C = $808080)
KnobDefaults\BackColour = C
EndProcedure
Procedure SetKnobScaleColour(C = #Black)
KnobDefaults\ScaleColour = C
EndProcedure
Procedure SetKnobColour(C = #Black)
Shared KnobDefaults
KnobDefaults\KnobColour = C
EndProcedure
Procedure SetKnobDotColour(C = #Red)
KnobDefaults\DotColour = C
EndProcedure
Procedure SetKnobCaptionColour(C = #Cyan)
KnobDefaults\CaptionColour = C
EndProcedure
Procedure SetKnobCaptionFont(S.s,ch.i)
KnobDefaults\CaptionFont = S
KnobDefaults\CaptionFontHt = ch
EndProcedure
Procedure SetKnobFrameColours(i.i,o.i)
KnobDefaults\FrameColourInner = i
KnobDefaults\FrameColourOuter = o
EndProcedure
Procedure SetKnobMultiTurn(i.i)
KnobDefaults\KnobTurns = i
EndProcedure
Procedure SetKnobWheelRates(SloR.i,FstR.i)
KnobDefaults\WheelRateSlow = SloR
KnobDefaults\WheelRateFast = FstR
EndProcedure
; Modify knob parameters
Procedure SetKnobState(gadNum,Value.f=0) ;- Set KnobGadget() to value in range KnobMin to KnobMax
Protected n,T,X,Y,Index.i = 0, Result.i = 0
; Locate caller's knob in KnobList
n=1
Index = 0
ForEach KnobList()
If KnobList() = gadNum
Index = n ; 1...
Break
EndIf
n+1
Next
; Quit if not found
If Index = 0
ProcedureReturn #False
EndIf
With Knob(Index)
; Check for out-of-range values. Limit if over-range.
If Value < \KnobMin : Value = \KnobMin : EndIf
If Value > \KnobMax : Value = \KnobMax : EndIf
; Save the caller's provided value for this knob
If \ShiftFlag
\PositionShift = Value
Else
\Position = Value
EndIf
; Send Windows callback message
SendMessage_(WindowID(GetActiveWindow()),#PBM_KNOB,Index,GadgetID(gadNum))
; Re-draw the knob cap and pointer.
; Convert user value to first circle degrees
; (This works for single and multi-turn pots.)
Value - \KnobMin
Value / \KnobRate ; 0..3x0 degrees
While Value > 360 : Value - 360 : Wend ; '%' cannot be used with FLOAT values
; Angle of pointer
If \flags & #MULTITURN1
T = Value + 180
T = 360 - T
Else
T = -(Value + 30)
EndIf
; Calc co-ords of end of pointer
X = \CentreX + (\KnobRad * Sin(Radian(T))) ; End of pointer X
Y = \CentreY + (\KnobRad * Cos(Radian(T))) ; End of pointer Y
If StartDrawing(CanvasOutput(\CanvasNum))
DrawImage(ImageID(\KnobImage),0,0) ; Draw the knob backdrop
;{ Draw the knob and pointer feature
If \flags & #JAZZ_top
; Draw jazzy knob top and pointer
DrawingMode(#PB_2DDrawing_Gradient)
BackColor(\KnobColour)
FrontColor(\DotColour)
ConicalGradient(\CentreX,\CentreY, T-90)
Circle(\CentreX,\CentreY,\KnobRad)
Circle(X,Y,\KnobRad>>3,\DotColour) ; Pointer dot
Else
; Draw standard knob top with line and pointer dot
LineXY(\CentreX,\CentreY,X,Y,\DotColour)
Circle(X,Y,\KnobRad>>3,\DotColour)
EndIf
;}
;{ Draw dynamic caption on SWITCH...
; (Unfinished: Will need FONT data)
If \flags & #SWITCHKNOB
If FindString(\Caption,"|")
DrawingMode(#PB_2DDrawing_Transparent)
k$ = StringField(\Caption,\Position-\KnobMin+1,"|")
DrawText((\width-TextWidth(k$))/2,\width-8,k$,\CaptionColour)
EndIf
EndIf
;}
;{ Draw LED Indicator and set/clear KnobClick flag
If \flags & #LED_Switch
DrawingMode(#PB_2DDrawing_Default)
C = #Black
\KnobClick = #False
If (\Position - \KnobMin) > (\KnobMax-\KnobMin)/ 64
C = #Red
\KnobClick = #True
EndIf
Circle(14,14,4,C)
EndIf
;}
; Keep pointer position etc for readout / next time
If \ShiftFlag
\LastShiftDotX = X
\LastShiftDotY = Y
Else
\LastDotX = X
\LastDotY = Y
EndIf
Result = #True
StopDrawing()
EndIf
EndWith
ProcedureReturn Result
EndProcedure
Procedure GetKnobState(gadNum) ;- Get current position of a KnobGadget()
Protected n,Result
n=1
ForEach KnobList()
If KnobList() = gadNum
Result = Knob(n)\Position
Break
EndIf
n+1
Next
ProcedureReturn Result
EndProcedure
Procedure KnobSetRange(gadNum.i,KnobMin.i, KnobMax.i,KnobTurns.i=1)
; Modify the range and number of turns** of an existing KnobGadget
; ** MULTITURN1 knobs only.
; Search list for the KnobGadget()
n=1 : Index = 0
ForEach KnobList()
If KnobList() = gadNum
Index = n ; 1...
Break
EndIf
n+1
Next
If Index = 0
ProcedureReturn #False
EndIf
With Knob(Index)
\KnobMin = KnobMin
\KnobMax = KnobMax
\Position = KnobMin
If \flags & #MULTITURN1
\KnobTurns = KnobTurns
\KnobRate = (\KnobMax - \KnobMin) / (360 * \KnobTurns) ; 'Clicks' per degree
ElseIf \flags & #SWITCHKNOB
\KnobTurns = 1
\KnobRate = (\KnobMax - \KnobMin) / 300 ; 'Clicks' per SWITCH STEP
Else
\KnobTurns = 1
\KnobRate = (\KnobMax - \KnobMin) / 300
EndIf
EndWith
EndProcedure
Procedure KnobService() ;- Called when a KnobGadget() is active
Protected n.i, Index.i, X.i, Y.i, F.f, dx.f, dy.f,T.i, dF.f, Span.i, MyFont
Static S.f,Dragging.i, WheelRate.i,LastGad.i = -1, MoveX.i = -1, MoveY.i = -1
; Which KnobGadget() called?
Gad = EventGadget()
; Find gadget in table of knobs
n=1
Index = 0
ForEach KnobList()
If KnobList() = Gad
Index = n ; 1...
Break
EndIf
n+1
Next
If Index = 0
ProcedureReturn #False
EndIf
; If knob has changed default the wheel rate to 1
If Gad <> LastGad : WheelRate = 1 : EndIf
LastGad = Gad
With Knob(Index)
Select EventType()
Case #PB_EventType_KeyDown
; If Dragging : Beep_(2000,55) : EndIf
\ShiftFlag = GetGadgetAttribute(Gad,#PB_Canvas_Modifiers) & 1
Case #PB_EventType_KeyUp
; If Dragging : Beep_(1000,55) : EndIf
\ShiftFlag = GetGadgetAttribute(Gad,#PB_Canvas_Modifiers) & 1
Case #PB_EventType_LeftButtonDown ;{ User selects a Knob with left mouse button
; Move mouse pointer to last position used.
If \ShiftFlag
SetKnobState(Gad,\PositionShift)
SetGadgetMouseXY(GetActiveWindow(),\CanvasNum,\LastShiftDotX,\LastShiftDotY)
Else
SetKnobState(Gad,\Position)
SetGadgetMouseXY(GetActiveWindow(),\CanvasNum,\LastDotX,\LastDotY)
EndIf
Dragging = Index
;}
Case #PB_EventType_LeftButtonUp ;{ Finish when user releases left mouse button
Dragging = 0
;}
Case #PB_EventType_MouseWheel ;{ Move pointer via mouse wheel
T = GetGadgetAttribute(Gad,#PB_Canvas_WheelDelta) ; Get mouse delta
SetKnobState(Gad,GetKnobState(Gad)+(T * WheelRate)) ; Apply it to Knob
;}
Case #PB_EventType_MiddleButtonDown ;{ Adjust wheel rate...
WheelRate = \WheelRateFast
Case #PB_EventType_MiddleButtonUp
WheelRate = \WheelRateSlow
;}
EndSelect
If Dragging ;{ Moving Potentiometer and Switch knobs
; Get mouse position over Canvas()
X = GetGadgetAttribute(Gad,#PB_Canvas_MouseX)
Y = GetGadgetAttribute(Gad,#PB_Canvas_MouseY)
; If mouse has moved from previous position...
If (X <> MoveX) Or (Y <> MoveY)
MoveX = X : MoveY = Y
If X
; Adjust X and Y for framed version of knob
If (\flags & #BORDER)
Y + 2 : X + 2
EndIf
; Calculate angle of mouse relative to knob centre in Degrees
dx = X - \CentreX
dy = Y - \CentreY
F = Degree(ATan2(dx,dy))
; Experiment : Make mouse track knob periphery... (Not finished.)
; SetGadgetMouseXY(GetActiveWindow(),Gad, \CentreX + (\KnobRad * Cos(Radian(F))), \CentreY + (\KnobRad * Sin(Radian(F))) )
If \flags & (#POTKNOB | #SWITCHKNOB);{
If F < 0 : F = 360 + F : EndIf
F + 240 ; Offset because pot's '0' is not at cardinal point
If F>360 : F-360 : EndIf ; Circular wrap around?
If F>302 : F=0 : EndIf ; Potentiometers have 300 degree travel.
; Convert degrees of rotation to User value
F * \KnobRate
F + \KnobMin
; Limit the range
If F < \KnobMin : F = \KnobMin : EndIf
If F > \KnobMax : F = \KnobMax : EndIf
; Limit steps size and also forces dead-band at bottom. Neat :-)
If \ShiftFlag
If Abs(F - \PositionShift) < (60 * \KnobRate)
SetKnobState(Gad, Round (F,#PB_Round_Nearest))
EndIf
Else
If Abs(F - \Position) < (60 * \KnobRate)
SetKnobState(Gad, Round (F,#PB_Round_Nearest))
EndIf
EndIf
;}
ElseIf \flags & #MULTITURN1 ;{ MULTITURN1 - Knob tracks mouse and value increase by 1/\Turns
; (There must be a neater way!!!)
F + 90 ; Move '0' to top.
If F < 0 : F = 360 + F : EndIf ; Roll-over correction
; Calculate knob revolution... so far
Span = (\KnobMax - \KnobMin) / \KnobTurns ; Steps per revolution
If \ShiftFlag
Turn = Int((\PositionShift - \KnobMin)/Span) ; Completed turns of knob
Else
Turn = Int((\Position - \KnobMin)/Span)
EndIf
S = F - \LastF ; Movement since last time
\LastF = F
; Detect mouse passing through 'Top Dead Centre' (=TDC) and adjust 'Turns'
If Abs(S) > 320 ; 0=>360 or 360=>0
; Manage TDC transitions for first, last and in-between turns.
; (\Gate: 0 = free to move up or down, -1 = Jammed at min, +1 = Jammed at max)
Select \Gate
Case 1 ; At top, can only decrease...
If Sign(S) <> -1
Turn = \KnobTurns - 1
F = 359
\Gate = 0
EndIf
Case -1 ; At bottom, can only increase...
If Sign(S) <> 1
Turn = 0
F = 0
\Gate = 0
EndIf
Case 0 ; Can move up or down...
If Sign(S) = 1 And Turn = 0 :\Gate = -1: EndIf ; Reached min?
If Sign(S) = -1 And Turn = \KnobTurns-1 :\Gate = 1: EndIf ; Reached max?
If \Gate = 0 : Turn - Sign(S) : EndIf ; Free to move...
EndSelect
EndIf
; Convert knob angle to control clicks and update display
If \Gate = 0 ; Free to move, so
F * \KnobRate ; convert knob angle degrees to 'Clicks'
F + (Turn * Span) ; add 'Clicks' for completed turns,
F + \KnobMin ; add control's minimum value and
SetKnobState(Gad,Round(F,#PB_Round_Nearest)) ; move the control.
EndIf
;}
EndIf
EndIf
EndIf
;}
EndIf
EndWith
EndProcedure
Procedure KnobGadget(gadNum.i, X.i, Y.i, Size.i,Caption.s = "", KnobMin.i= 0, KnobMax.i = 10000, flags.i = #POTKNOB) ; Create a new Knob()
; Create a Circular Control Knob looking like a traditional radio receiver or hi-fi control.
; gadNum = Number of gadget to be created, else #PB_Any and this procedure returns number assigned by PB.
; X, Y = Gadget co-ordinates
; Size = Width of knob and backdrop.
; Height = Width + height of caption (if specified)
; Caption = Name of control. For SWITCHKNOB specify one name for each position, with '|' separators.
; KnobMin = Start of range of values returned by control.
; KnobMax = End of range of values returned by control.
; Flags = Specify the knob type and various options.
; Knob types... call KnobGadget() with Flags including just ONE of these.
; #POTKNOB An emulation of a standard potentiometer (eg: Radio volume control) DEFAULT
; #MULTITURN1 A multi turn knob to provide finer granularity. **
; #MULTITURN2 For future use **
; #SWITCHKNOB An emulation of a rotary switch with four or more positions.
; #PLAINKKNOB A very simple knob with no space for embelishments
; Additional flags to control optional features...
; #RING_Heat Put gradient coloured ring around knob, black through to yellow via red.
; #FRAME_Switch Place standard PB/Windows frame around KnobGadget
; #JAZZ_top Replace Line+Dot on top of knob with colourful position indicator
; #BORDER Place a coloured border around the knob. See SetKnobFrameColours()
; #LED_Switch Provide a Black/Red 'LED', red when value is greater than range/64
; USAGE
; Read position of a knob with GetKnobState(KnobNum) or with with WinCallback()
; Set position of a knob with SetKnobState(KnobNum,Value)
Protected Result.i, w.i, h.i, dx.f, dy.f, cx.f, cy.f
Static KnobCount = 1
; Set the 'h' and 'w' from caller's values, adjust height for caption.
Size | 1 ; Size must be odd, so there is a pixel at the centre
w = Size : h = Size
If Caption
h + KnobDefaults\CaptionFontHt + 2
EndIf
; Create flags and the CanvasGadget()
T = #PB_Canvas_ClipMouse|#PB_Canvas_Keyboard|#PB_Canvas_DrawFocus
If (flags & #BORDER)
T | #PB_Canvas_Border
EndIf
Result = CanvasGadget(gadNum, X, Y, w, h,T)
If Result <> 0
; Assign gadget number
If gadNum = #PB_Any : gadNum = Result : EndIf
; Extend storage for another set of knob definitions
If KnobCount > 1 : Redim Knob(KnobCount) : EndIf
With Knob(KnobCount)
;{ Complete the knob definitions
\CanvasNum = gadNum
\width = w
\height = h
If (flags & #BORDER)
\width - 4 : \height - 4
EndIf
\KnobImage = CreateImage(#PB_Any,\width*#Scale,\height*#Scale)
\CentreX = \width >> 1
\CentreY = \width >> 1
\KnobRad = \width >> 2
If flags & #PLAINKKNOB
\KnobRad = \width>>1 - \width>>3
EndIf
\Caption = Caption
\flags = flags
\KnobMin = KnobMin
\KnobMax = KnobMax
\Position = KnobMin
If \flags & #MULTITURN1
\KnobTurns = KnobDefaults\KnobTurns
\KnobRate = (\KnobMax - \KnobMin) / (360 * \KnobTurns) ; 'Clicks' per degree
ElseIf \flags & #SWITCHKNOB
\KnobTurns = 1
\KnobRate = (\KnobMax - \KnobMin) / 300 ; 'Clicks' per SWITCH STEP
Else
\KnobTurns = 1 ; Pots and switches always 1
\KnobRate = (\KnobMax - \KnobMin) / 300
EndIf
\KnobColour = KnobDefaults\KnobColour
\BackColour = KnobDefaults\BackColour
\ScaleColour = KnobDefaults\ScaleColour
\CaptionColour = KnobDefaults\CaptionColour
\CaptionFont = KnobDefaults\CaptionFont
\CaptionFontHt = KnobDefaults\CaptionFontHt
\DotColour = KnobDefaults\DotColour
\FrameColourInner = KnobDefaults\FrameColourInner
\FrameColourOuter = KnobDefaults\FrameColourOuter
\WheelRateSlow = KnobDefaults\WheelRateSlow
\WheelRateFast = KnobDefaults\WheelRateFast
;}
; Create SCALED UP version of image of knob's fixed features
MyFont = LoadFont(#PB_Any,\CaptionFont,\CaptionFontHt * #Scale)
StartDrawing(ImageOutput(\KnobImage))
DrawingFont(FontID(MyFont))
;{ Backdrop
Box(0,0,\width*#Scale,\height*#Scale,\BackColour)
;}
;{ Optional coloured border with rounded corners
If flags & #FRAME_Switch
n = 1 : R = 12
RoundBox(n*#Scale, n*#Scale,(\width-(2*n))*#Scale,(\height-(2*n))*#Scale,R*#Scale,R*#Scale,\FrameColourInner)
n = 4 : R = 09
RoundBox(n*#Scale, n*#Scale,(\width-(2*n))*#Scale,(\height-(2*n))*#Scale,R*#Scale,R*#Scale,\FrameColourOuter)
n = 7 : R = 06
RoundBox(n*#Scale, n*#Scale,(\width-(2*n))*#Scale,(\height-(2*n))*#Scale,R*#Scale,R*#Scale,\BackColour)
EndIf
;}
;{ Radial markers
If Not(flags & #PLAINKKNOB)
; Calculate start angle, end and step size
R = (\width>>1 - \width>>4 ) * #Scale ; Radius of marker lines
A = 30 ; Start angle (degrees)
B = 330 ; End angle
S = 30 ; Step size
If flags & #MULTITURN1 : B = 360 : EndIf
If flags & #SWITCHKNOB
A = 30
B = 330
S = (B - A) / (\KnobMax - \KnobMin)
EndIf
; Draw the markers
T = A
Repeat
X = \CentreX*#Scale + (R*Sin(Radian(T))) ; End of pointer X
Y = \CentreY*#Scale + (R*Cos(Radian(T))) ; End of pointer Y
cx = \CentreX*#Scale
cy = \CentreY*#Scale
; Draw several adjacent lines to create a single thick one.
For n = -5 To 5
dx = n * Cos(Radian(T)) : dy = n * Sin(Radian(T))
LineXY(cx+dx, cy-dy, X+dx, Y-dy, \ScaleColour)
Next
T + S
Until T > B
EndIf
;}
;{ Optional coloured ring around the knob
If flags & #RING_Heat
DrawingMode(#PB_2DDrawing_Gradient)
BackColor( $000000)
GradientColor(0.2,#Black)
GradientColor(0.5, #Yellow);$00FFFF)
FrontColor($0000FF)
ConicalGradient(\CentreX*#Scale,\CentreY*#Scale, 300)
Circle(\CentreX*#Scale,\CentreY*#Scale,(\KnobRad+\width>>4-1)*#Scale)
DrawingMode(#PB_2DDrawing_Default )
Else
; Normal - just clear a space
Circle(\CentreX*#Scale,\CentreY*#Scale,(\KnobRad+\width>>4-1)*#Scale,\BackColour)
EndIf
;}
;{ Optional 'LED'
If flags & #LED_Switch
T = 14 * #Scale
Circle(T,T,6*#Scale,#Black)
EndIf
;}
;{ Draw the knob top
Circle(\CentreX*#Scale,\CentreY*#Scale,\KnobRad*#Scale,\KnobColour)
;}
;{ Draw the caption
DrawingFont(FontID(MyFont))
BackColor(\BackColour)
DrawingMode(#PB_2DDrawing_Default)
If Not(\flags & #SWITCHKNOB And CountString(\Caption,"|"))
X = (#Scale*\width - TextWidth(\Caption))/2
DrawText(X,#Scale*(\width-12),\Caption,\CaptionColour)
EndIf
;}
StopDrawing()
FreeFont(MyFont)
; Reduce to wanted size and draw on CanvasGadget()
ResizeImage(\KnobImage,\width,\height)
StartDrawing(CanvasOutput(\CanvasNum))
DrawImage(ImageID(\KnobImage),0,0)
StopDrawing()
SetGadgetAttribute(\CanvasNum,#PB_Canvas_Cursor,#PB_Cursor_Hand)
; Add the new knob gadget number to the list
AddElement(KnobList())
KnobList() = gadNum
SetKnobState(gadNum,0)
\LastShiftDotX = \LastDotX
\LastShiftDotY = \LastDotY
\PositionShift = \Position
EndWith
BindGadgetEvent(gadNum,@KnobService())
KnobCount + 1 ; Ready for next Knob!
EndIf
ProcedureReturn gadNum
EndProcedure
CompilerIf #PB_Compiler_IsMainFile
; *************************************
; Test code
;{ *************************************
Enumeration 1000 ; Define Window, gadget numbers etc...
#Win_KMain
#Gad_Knob1 ; It is helpful to have the knobes grouped
#Gad_Knob2 ; together
#Gad_Knob3
#Gad_Knob4
#Gad_Knob5
#Gad_Knob6
#Gad_Knob7
#Gad_Knob8
#Gad_Knob9
#Gad_Knob10
#Gad_Knob11
#Gad_Knob12
#Gad_Knob13
#Gad_Knob14
#Gad_Text1
#Gad_Text2
#Gad_Text3
#Gad_Text4
#Gad_Text5
; More here
EndEnumeration
Procedure WinCallback(hwnd, uMsg, wParam, lParam) ; Standard PB Windows callback...
Select uMsg
Case #PBM_KNOB ; Receiving Knob() messages in 'real time'
Select lParam
Case GadgetID(#Gad_Knob1) : SetGadgetText(#Gad_Text1, "Knob1 = "+Str(Knob(wParam)\Position))
Case GadgetID(#Gad_Knob2) : SetGadgetText(#Gad_Text2, "Knob2 = "+Str(Knob(wParam)\Position))
Case GadgetID(#Gad_Knob3) : SetGadgetText(#Gad_Text3, "Knob3 = "+Str(Knob(wParam)\Position))
Case GadgetID(#Gad_Knob4) : SetKnobState(#Gad_Knob8, GetKnobState(#Gad_Knob4)) ; Slave two knobs... one way, 4=>8
Case GadgetID(#Gad_Knob12): SetGadgetText(#Gad_Text4, "Knob12 = "+Str(Knob(wParam)\Position))
Case GadgetID(#Gad_Knob14): SetGadgetText(#Gad_Text5, "Sw 14 = "+Str(Knob(wParam)\Position))
; Good place to check Get/SetKnobState()
EndSelect
; etc
; etc
EndSelect
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure
OpenWindow(#Win_KMain,0,0,900,450,"Knob test Rev 1.02",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
; User defined default values
SetKnobScaleColour(#White)
SetKnobColour(#Black)
SetKnobDotColour(#Red)
SetKnobCaptionColour(#White)
SetKnobCaptionFont("CourierNew",18)
; *************** Create Knobs ****************
; #RING_Heat
; #FRAME_Switch
; #JAZZ_top
; #BORDER
; #LED_Switch
; Change knob default values BEFORE creating a Knob
SetKnobDotColour(#Red)
KnobGadget(#Gad_Knob1,40,20,150,"TREBLE",0,1000,#POTKNOB | #RING_Heat | #FRAME_Switch | #LED_Switch) ; Frame and HeatRing
SetKnobState(#Gad_Knob1,50)
SetKnobScaleColour(#Black)
SetKnobDotColour(#White)
SetKnobCaptionColour(#Blue)
KnobGadget(#Gad_Knob2,195,20,150,"BASS",0,10000,#POTKNOB | #BORDER | #JAZZ_top)
SetKnobScaleColour(#Yellow)
SetKnobDotColour(#Blue)
SetKnobColour(#Cyan)
SetKnobCaptionColour(#Red)
KnobGadget(#Gad_Knob3,350,20,150,"VOLUME",1,42,#POTKNOB |#RING_Heat |#JAZZ_top | #FRAME_Switch)
SetKnobScaleColour(#Black)
SetKnobDotColour(#White)
SetKnobColour(#Black)
SetKnobMultiTurn(5)
KnobGadget(#Gad_Knob12,510,20,200,"MultiTurn (5T)",0,3000,#MULTITURN1 |#JAZZ_top | #FRAME_Switch | #LED_Switch)
SetKnobState(#Gad_Knob12,1000)
SetKnobDotColour(#Yellow)
SetKnobCaptionColour(#Blue)
SetKnobWheelRates(1,1)
KnobGadget(#Gad_Knob14,720,20,150,"160M|80M|40M|20M|15M|10M|6M|x7|x8|x9|",10,19,#SWITCHKNOB | #JAZZ_top)
SetKnobDotColour(#White)
SetKnobCaptionFont("CourierNew",12)
SetKnobBackgroundColour($FFAA55)
SetKnobWheelRates(1,50)
SetKnobCaptionColour(#White)
KnobGadget(#Gad_Knob4, 10 ,280,100,"Channel 1",0,10000,#POTKNOB | 8<<5)
KnobGadget(#Gad_Knob5, 110,280,100,"Channel 2",0,10000,#POTKNOB | 9<<5)
KnobGadget(#Gad_Knob6, 220,280,100,"Channel 3",0,10000,#POTKNOB | 2<<5)
KnobGadget(#Gad_Knob7, 320,280,100,"Channel 4",0,10000,#POTKNOB | 3<<5)
SetKnobScaleColour(#Black)
KnobGadget(#Gad_Knob8, 430,280,100,"Channel 5",0,10000,#POTKNOB | 4<<5)
KnobGadget(#Gad_Knob9, 530,280,100,"Channel 6",0,10000,#POTKNOB | 5<<5)
KnobGadget(#Gad_Knob10,640,280,100,"Channel 7",0,10000,#POTKNOB | 6<<5)
KnobGadget(#Gad_Knob11,740,280,100,"Channel 8",0,10000,#POTKNOB | 7<<5)
DisableGadget(#Gad_Knob8,#True)
; ********************************************
; To show knob values returned from Window callback
TextGadget(#Gad_Text1,40, 200,150,20,"",#PB_Text_Center)
TextGadget(#Gad_Text2,190,200,150,20,"",#PB_Text_Center)
TextGadget(#Gad_Text3,340,200,160,20,"",#PB_Text_Center)
TextGadget(#Gad_Text4,510,250,200,20,"",#PB_Text_Center)
TextGadget(#Gad_Text5,720,195,150,15,"",#PB_Text_Center)
; Optional: Callabck to receive 'real-time' knob messages
SetWindowCallback(@WinCallback())
KnobSetRange(#Gad_Knob1,42,420,1) : SetGadgetState(#Gad_Knob1,42)
; Dispatch
WheelRate.i = 1
Repeat
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Break
Case #PB_Event_Gadget
Select EventGadget()
; All usual stuff here....
EndSelect
EndSelect
ForEver
;}
CompilerEndIf