Page 1 of 2

AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 12:08 pm
by STARGÅTE
features:
  • orientation
  • open or closed scalar
  • keyboard and mouse functions already integrated (focus, tab, up, down, scroll wheel, ...)
  • user-definable labels
  • automatic alignment (with caption) for the optimal use of space
Image

Code: Select all

EnableExplicit
;- Include

Prototype.s AngleGadgetLabel(State.i)

Structure AngleGadgetData
	State.i
	Min.i
	Max.i
	Flags.i
	Ticks.i
	NullTick.f
	Range.f
	MinorTicks.i
	MajorTicks.i
	Change.i
	CenterX.i
	CenterY.i
	Radius.i
	MajorLabel.AngleGadgetLabel
EndStructure


Structure AngleGadgetConstant
	FaceColor.l
	TextColor.l
	ShadowColor.l
	HighlightColor.l
	FontID.i
EndStructure

Global AngleGadgetConstant.AngleGadgetConstant

With AngleGadgetConstant
	CompilerIf #PB_Compiler_OS = #PB_OS_Windows
		\FaceColor      = GetSysColor_(#COLOR_3DFACE)|$FF<<24
		\TextColor      = GetSysColor_(#COLOR_WINDOWTEXT)|$FF<<24
		\ShadowColor    = GetSysColor_(#COLOR_3DSHADOW)|$FF<<24
		\HighlightColor = GetSysColor_(#COLOR_3DHIGHLIGHT)|$FF<<24
	CompilerElse
		\FaceColor      = $FFD0D0D0
		\TextColor      = $FF000000
		\ShadowColor    = $FF808080
		\HighlightColor = $FFFFFFFF
	CompilerEndIf
	\FontID         = FontID(LoadFont(#PB_Any, "Arial", 8))
EndWith


CompilerIf Defined(PB_DefaultColor, #PB_Constant) = #False
	#PB_DefaultColor = -$100000000
CompilerEndIf
CompilerIf Defined(TwoPI, #PB_Constant) = #False
	#TwoPI = #PI*2.0
CompilerEndIf
CompilerIf Defined(HalfPI, #PB_Constant) = #False
	#HalfPI = #PI*0.5
CompilerEndIf


Enumeration #True
	#AngleGadget_MinorTicks
	#AngleGadget_MajorTicks
	#AngleGadget_Orientation
	#AngleGadget_Length
	#AngleGadget_MajorLabel
EndEnumeration


Procedure.i AngleGadget_Normalize(Value.i, Min.i, Max.i)
	Value - Min : Value % (Max-Min)
	If Value < 0 : ProcedureReturn Value+Max : Else : ProcedureReturn Value+Min : EndIf
EndProcedure

Procedure.f AngleGadget_NormalizeF(Value.f, Max.f)
	Value = Mod(Value, Max)
	If Value < 0 : ProcedureReturn Value+Max : Else : ProcedureReturn Value : EndIf
EndProcedure

Procedure AngleGadget_LineRA(X.i, Y.i, Radius1.f, Angle1.f, Radius2.f, Angle2.f, Color.q=#PB_DefaultColor)
	If Color = #PB_DefaultColor
		LineXY(Cos(Angle1)*Radius1+X, Sin(Angle1)*Radius1+Y, Cos(Angle2)*Radius2+X, Sin(Angle2)*Radius2+Y)
	Else
		LineXY(Cos(Angle1)*Radius1+X, Sin(Angle1)*Radius1+Y, Cos(Angle2)*Radius2+X, Sin(Angle2)*Radius2+Y, Color)
	EndIf
EndProcedure


Procedure DrawAngleGadget(Gadget.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	Protected Tick.i, Angle.f, Radius.i, Text.s, X.i, Y.i, State.i
	Protected DeltaAngle.f = #PI*0.75, Color.i, MinorTicks.i
	Protected TextX.i, TextWidth.i, MaxTextWidth.i, MinTextWidth.i
	Protected TextY.i, TextHeight.i, MaxTextHeight.i, MinTextHeight.i
	Protected MinRadiusX.f=Infinity(), MaxRadiusX.f=-Infinity(), RadiusX.f
	Protected MinRadiusY.f=Infinity(), MaxRadiusY.f=-Infinity(), RadiusY.f
	With *AngleGadgetData
		If StartDrawing(CanvasOutput(Gadget))
			DrawingMode(#PB_2DDrawing_Transparent)
			DrawingFont(AngleGadgetConstant\FontID)
			If \Range = #TwoPI
				MinorTicks = \MinorTicks-1
			Else
				MinorTicks = \MinorTicks
			EndIf
			For Tick = 0 To MinorTicks
				Angle = Tick / \MinorTicks * \Range + \NullTick
				If \MajorLabel
					State = Tick*\Ticks/\MinorTicks+\Min
					If (Tick*\MajorTicks)%\MinorTicks = 0
						TextWidth = TextWidth(\MajorLabel(State))
						TextHeight = TextHeight(\MajorLabel(State))
						TextX = -TextWidth*(1-Cos(Angle))*0.5
						TextY = -TextHeight*(1-Sin(Angle))*0.5
						If TextX+TextWidth > MaxTextWidth
							MaxTextWidth = TextX+TextWidth
						EndIf
						If TextX < MinTextWidth
							MinTextWidth = TextX
						EndIf	
						If TextY+TextHeight > MaxTextHeight
							MaxTextHeight = TextY+TextHeight
						EndIf
						If TextY < MinTextHeight
							MinTextHeight = TextY
						EndIf	
					EndIf
				EndIf
				RadiusX = Cos(Angle)
				RadiusY = Sin(Angle)
				If RadiusX > MaxRadiusX
					MaxRadiusX = RadiusX
				EndIf
				If RadiusX < MinRadiusX
					MinRadiusX = RadiusX
				EndIf	
				If RadiusY > MaxRadiusY
					MaxRadiusY = RadiusY
				EndIf
				If RadiusY < MinRadiusY
					MinRadiusY = RadiusY
				EndIf	
			Next
			RadiusX = (OutputWidth()-8+MinTextWidth-MaxTextWidth)/(MaxRadiusX-MinRadiusX)
			RadiusY = (OutputHeight()-8+MinTextHeight-MaxTextHeight)/(MaxRadiusY-MinRadiusY)
			If RadiusX < RadiusY : Radius = RadiusX : Else : Radius = RadiusY : EndIf
			\Radius = Radius
			\CenterX = (OutputWidth()-MinTextWidth-MaxTextWidth-(MinRadiusX+MaxRadiusX)*Radius)/2
			\CenterY = (OutputHeight()-MinTextHeight-MaxTextHeight-(MinRadiusY+MaxRadiusY)*Radius)/2
			Box(0, 0, OutputWidth(), OutputHeight(), AngleGadgetConstant\FaceColor)
			For Tick = 0 To MinorTicks
				Angle = Tick / \MinorTicks * \Range + \NullTick
				State = Tick*\Ticks/\MinorTicks+\Min
				If (Tick*\MajorTicks)%\MinorTicks = 0
					AngleGadget_LineRA(\CenterX, \CenterY, Radius-2, Angle, Radius-11, Angle, AngleGadgetConstant\TextColor)
					If \MajorLabel
						Text = \MajorLabel(State)
						X = \CenterX + Cos(Angle)*Radius - TextWidth(Text)*(1-Cos(Angle))*0.5
						Y = \CenterY + Sin(Angle)*Radius - TextHeight(Text)*(1-Sin(Angle))*0.5
						DrawText(X, Y, Text, AngleGadgetConstant\TextColor)
					EndIf
				Else
					AngleGadget_LineRA(\CenterX, \CenterY, Radius-2, Angle, Radius-5, Angle, AngleGadgetConstant\TextColor)
				EndIf
			Next
			Angle = (\State-\Min) / \Ticks * \Range + \NullTick
			If Angle < 0 Or Angle => #PI
				Color = AngleGadgetConstant\ShadowColor
			Else
				Color = AngleGadgetConstant\HighlightColor
			EndIf
			AngleGadget_LineRA(\CenterX, \CenterY, Radius*0.3, Angle-DeltaAngle, Radius*0.3, Angle+DeltaAngle, Color)
			If Angle < #HalfPI Or Angle => #PI+#HalfPI
				Color = AngleGadgetConstant\ShadowColor
			Else
				Color = AngleGadgetConstant\HighlightColor
			EndIf
			AngleGadget_LineRA(\CenterX, \CenterY, Radius-14, Angle, Radius*0.3, Angle+DeltaAngle, Color)
			If Angle < #TwoPI-#HalfPI And Angle => #PI-#HalfPI
				Color = AngleGadgetConstant\ShadowColor
			Else
				Color = AngleGadgetConstant\HighlightColor
			EndIf
			AngleGadget_LineRA(\CenterX, \CenterY, Radius-14, Angle, Radius*0.3, Angle-DeltaAngle, Color)
			StopDrawing()
		EndIf
	EndWith
EndProcedure

Procedure AngleGadget(Gadget.i, X.i, Y.i, Width.i, Height.i, Min.i, Max.i)
	Protected GadgetFlags = #PB_Canvas_GrabMouse|#PB_Canvas_Keyboard|#PB_Canvas_DrawFocus
	Protected *AngleGadgetData.AngleGadgetData = AllocateMemory(SizeOf(AngleGadgetData))
	If IsGadget(Gadget) And GetGadgetData(Gadget)
		FreeMemory(GetGadgetData(Gadget))
	EndIf
	If Gadget = #PB_Any
		Gadget = CanvasGadget(Gadget, X, Y, Width, Height, GadgetFlags)
	Else
		CanvasGadget(Gadget, X, Y, Width, Height, GadgetFlags)
	EndIf
	SetGadgetData(Gadget, *AngleGadgetData)
	With *AngleGadgetData
		\Min        = Min
		\Max        = Max
		\Ticks      = \Max-\Min
		\Range      = #TwoPI
		\NullTick   = \Min/(\Max-\Min)*\Range
		\MinorTicks = \Ticks
		\MajorTicks = 1
	EndWith
	DrawAngleGadget(Gadget)
	ProcedureReturn Gadget
EndProcedure

Procedure.i AngleGadgetEvent(Gadget.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	Protected X.i, Y.i, Angle.f, State.i
	Protected EventType = EventType()
	With *AngleGadgetData
		State = \State
		Select EventType
			Case #PB_EventType_MouseWheel
				If \Range = #TwoPI
					\State = AngleGadget_Normalize(\State+GetGadgetAttribute(Gadget, #PB_Canvas_WheelDelta), \Min, \Max)
				Else
					\State + GetGadgetAttribute(Gadget, #PB_Canvas_WheelDelta)
					If \State < \Min : \State = \Min : ElseIf \State > \Max : \State = \Max : EndIf
				EndIf
				DrawAngleGadget(Gadget)
			Case #PB_EventType_KeyDown 
				Select GetGadgetAttribute(Gadget, #PB_Canvas_Key)
					Case #PB_Shortcut_Up, #PB_Shortcut_Right
						If \Range = #TwoPI
							\State = AngleGadget_Normalize(\State+1, \Min, \Max)
						Else
							\State + 1
							If \State > \Max : \State = \Max : EndIf
						EndIf
						DrawAngleGadget(Gadget)
					Case #PB_Shortcut_Down, #PB_Shortcut_Left
						If \Range = #TwoPI
							\State = AngleGadget_Normalize(\State-1, \Min, \Max)
						Else
							\State - 1
							If \State < \Min : \State = \Min : EndIf
						EndIf
						DrawAngleGadget(Gadget)
				EndSelect
			Case #PB_EventType_LeftButtonDown
				X = GetGadgetAttribute(Gadget, #PB_Canvas_MouseX) - \CenterX
				Y = GetGadgetAttribute(Gadget, #PB_Canvas_MouseY) - \CenterY
				If Sqr(X*X+Y*Y) < \Radius
					\Change = #True
				EndIf
			Case #PB_EventType_LeftButtonUp
				\Change = #False
		EndSelect
		If \Change
			Select EventType
				Case #PB_EventType_LeftButtonDown, #PB_EventType_MouseMove
					X = GetGadgetAttribute(Gadget, #PB_Canvas_MouseX) - \CenterX
					Y = GetGadgetAttribute(Gadget, #PB_Canvas_MouseY) - \CenterY
					Angle = ATan2(X, Y)
					If \Range = #TwoPI
						\State = AngleGadget_Normalize((Angle-\NullTick)*\Ticks/\Range+\Min, \Min, \Max)
					Else
						Angle = AngleGadget_NormalizeF(Angle-\NullTick+(#TwoPI-\Range)*0.5, #TwoPI)-(#TwoPI-\Range)*0.5
						\State = Angle*\Ticks/\Range + \Min
 						If \State < \Min : \State = \Min : ElseIf \State > \Max : \State = \Max : EndIf
					EndIf
			EndSelect
		EndIf
		If State <> \State
			DrawAngleGadget(Gadget)
			ProcedureReturn #True
		EndIf
	EndWith
EndProcedure

Procedure SetAngleGadgetAttribute(Gadget.i, Attribute.i, Value.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	Protected OldNullTick.f
	With *AngleGadgetData
		Select Attribute
			Case #AngleGadget_MinorTicks
				\MinorTicks = Value
			Case #AngleGadget_MajorTicks
				\MajorTicks = Value
			Case #AngleGadget_Orientation
				\NullTick = Radian(Value)
			Case #AngleGadget_Length
				\Range = Radian(Value)
			Case #AngleGadget_MajorLabel
				\MajorLabel = Value
		EndSelect
	EndWith
	DrawAngleGadget(Gadget)
EndProcedure

Procedure SetAngleGadgetState(Gadget.i, State.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	With *AngleGadgetData
		\State = AngleGadget_Normalize(State, \Min, \Max)
	EndWith
	DrawAngleGadget(Gadget)
EndProcedure

Procedure GetAngleGadgetState(Gadget.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	With *AngleGadgetData
		ProcedureReturn \State
	EndWith
EndProcedure

Procedure FreeAngleGadget(Gadget.i)
	Protected *AngleGadgetData.AngleGadgetData = GetGadgetData(Gadget)
	FreeMemory(*AngleGadgetData)
	FreeGadget(Gadget)
EndProcedure




;- Example / Beispiel


Enumeration
	#Window
	#AngleGadget0
	#AngleGadget1
	#AngleGadget2
	#AngleGadget3
	#AngleGadget4
	#AngleGadget5
EndEnumeration


Procedure.s MyLabel2(State.i)
	ProcedureReturn " "+Str(State*5)+"°"
EndProcedure

Procedure.s MyLabel3(State.i)
	ProcedureReturn Str(State*5)+"%"
EndProcedure

Procedure.s MyLabel4(State.i)
	ProcedureReturn Str(State*5)
EndProcedure

Procedure.s MyLabel5(State.i)
	Select State
		Case -100 : ProcedureReturn "kalt"
		Case 100 : ProcedureReturn "warm"
	EndSelect
EndProcedure


OpenWindow(#Window, 0, 0, 640, 480, "ColorGadget", #PB_Window_MinimizeGadget|#PB_Window_ScreenCentered)

Frame3DGadget(#PB_Any, 5, 5, 140, 150, "default (closed)")
AngleGadget(#AngleGadget1, 10, 20, 130, 130, 0, 24)

Frame3DGadget(#PB_Any, 155, 5, 210, 220, "closed + label + rotation")
AngleGadget(#AngleGadget2, 160, 20, 200, 200, 0, 72)
SetAngleGadgetAttribute(#AngleGadget2, #AngleGadget_Orientation, -90)
SetAngleGadgetAttribute(#AngleGadget2, #AngleGadget_MajorTicks, 12)
SetAngleGadgetAttribute(#AngleGadget2, #AngleGadget_MajorLabel, @MyLabel2())

Frame3DGadget(#PB_Any, 375, 5, 160, 170, "opened + label + rotation")
AngleGadget(#AngleGadget3, 380, 20, 150, 150, 0, 20)
SetAngleGadgetAttribute(#AngleGadget3, #AngleGadget_Orientation, 120)
SetAngleGadgetAttribute(#AngleGadget3, #AngleGadget_Length, 300)
SetAngleGadgetAttribute(#AngleGadget3, #AngleGadget_MajorTicks, 5)
SetAngleGadgetAttribute(#AngleGadget3, #AngleGadget_MajorLabel, @MyLabel3())

Frame3DGadget(#PB_Any, 155, 235, 410, 180, "the space is always optimally exploits")
AngleGadget(#AngleGadget4, 160, 250, 400, 160, -20,  20)
SetAngleGadgetAttribute(#AngleGadget4, #AngleGadget_Length, 90)
SetAngleGadgetAttribute(#AngleGadget4, #AngleGadget_Orientation, 225)
SetAngleGadgetAttribute(#AngleGadget4, #AngleGadget_MajorTicks, 8)
SetAngleGadgetAttribute(#AngleGadget4, #AngleGadget_MajorLabel, @MyLabel4())

Frame3DGadget(#PB_Any, 5, 165, 140, 250, "another example")
AngleGadget(#AngleGadget5, 10, 180, 130, 230, -100, 100)
SetAngleGadgetAttribute(#AngleGadget5, #AngleGadget_Orientation, 60)
SetAngleGadgetAttribute(#AngleGadget5, #AngleGadget_Length, -120)
SetAngleGadgetAttribute(#AngleGadget5, #AngleGadget_MinorTicks, 20)
SetAngleGadgetAttribute(#AngleGadget5, #AngleGadget_MajorTicks, 2)
SetAngleGadgetAttribute(#AngleGadget5, #AngleGadget_MajorLabel, @MyLabel5())

Repeat
		
	Select WaitWindowEvent()
			
		Case #PB_Event_CloseWindow
			End
			
		Case #PB_Event_Gadget
			Select EventGadget()
				Case #AngleGadget0, #AngleGadget1,#AngleGadget2,#AngleGadget3,#AngleGadget4,#AngleGadget5
					If AngleGadgetEvent(EventGadget())
						Debug GetAngleGadgetState(EventGadget())
					EndIf
			EndSelect
			
	EndSelect
	
ForEver

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 12:43 pm
by luis
Very nice, can you try it under 64 bit ?

For some reason the "the space is always optimally exploits" and "another example" don't work correctly with the mouse (64 bit only).

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 12:54 pm
by STARGÅTE
>> Very nice, can you try it under 64 bit ?
no I have not, but I was able to reproduce the error with an .q instead of .i
i will fix it.

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 1:14 pm
by STARGÅTE
Oke, bug will fix.
Please test it again.

PS:
I do not understand it, but I had to write instead of:
State = Ticks*Angle/Range
State = Angle*Ticks/Range

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 1:31 pm
by luis
I'm sorry, same behavior ....

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 1:51 pm
by luis
It seem some kind of bug with the compiler, changing your code this way:

Code: Select all

  Protected State_
  State_ = Angle * \Ticks / \Range + \Min
  \State = State_

make it work...

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 2:01 pm
by luis
This works also:
\State = Int(Angle * \Ticks / \Range + \Min)
For what we all saw in the past, is never a good idea mix floats and integers with PB, there always some pitfall hidden...
Better go in little steps and assign int to int, float to float etc.
Explicit casting would be helpful.

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 2:13 pm
by luis
Same problem mixing integer and floats

change this:

Code: Select all

            If Sqr(X*X+Y*Y) < Int(\Radius)
               \Change = #True
            EndIf
adding the int.

With the two modifications above, all seem to work under 64 bit too.

Anyway all this shouldn't happen IMHO.



EDIT: no, it's not enough, it's working just by chance.... I just noticed you also assign \CenterX and \CenterY in operations involving floats and and integers, so these values are garbage too under 64 bit (and consequently the X and Y in the code above).

This will need to be rewrote eliminating all implicit conversions made by the compiler and without mixing liberally floats and integers in the calculations. Under PB64 all this float/integer mix seem deadly. Something to keep WELL in mind.

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 3:10 pm
by STARGÅTE

Code: Select all

Sqr(X*X+Y*Y) < Int(\Radius)
?? Sqr() returns float. why Int(\Radius)

PureBasic itself should also be able to perform a type conversion.

Code: Select all

\State = Int(Angle * \Ticks / \Range + \Min)
is not right, because 0.7 must be 1 and not 0

The Problem is only in 64BIT:

Code: Select all

l.l = 0.8
Debug l
q.q = 0.8
Debug q
bug with quad?

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 3:39 pm
by luis
STARGÅTE wrote:

Code: Select all

\State = Int(Angle * \Ticks / \Range + \Min)
is not right, because 0.7 must be 1 and not 0
OK, I didn't try to understand how the code work, add a 0.5 to the float calc and then Int().
Should be ok ?

STARGÅTE wrote: The Problem is only in 64BIT:

Code: Select all

l.l = 0.8
Debug l
q.q = 0.8
Debug q
bug with quad?
Under 32 bit q.q is 0 and under 64 bit is 1. Shouldn't be 1 ? Seem the 32 bit version to be wrong in this case to me. What a mess !

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 3:46 pm
by netmaestro
Well, something's odd and not just in 64bit:

Code: Select all

l.i = 0.6 * 2
Debug l ; 1 (1.2 rounded down, apparently)

l.i = 0.6
Debug l*2 ; 2 (same result, 1.2 but now it's rounded up?)

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 3:54 pm
by luis
netmaestro wrote:Well, something's odd and not just in 64bit:

Code: Select all

l.i = 0.6 * 2
Debug l ; 1 (1.2 rounded down, apparently)

l.i = 0.6
Debug l*2 ; 2 (same result, 1.2 but now it's rounded up?)

In the first case the float (0.6) is mult by 2, hence = 1.2 then assigned to the int resulting in 1

In the second case 0.6 is assigned to a int var, hence rounded to 1 and then you multiply 1 * 2 resulting in 2.

Looks ok, but it's the only thing looking good !

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 3:58 pm
by netmaestro
Have we always had this rounding? iirc it used to be that an integer receiving a float value just had the decimal portion chopped off. Actually I think that would be best, did they change it?

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 4:00 pm
by skywalk

Code: Select all

Define.i i, k = 0.6
For i = 0 To k * 2
  Debug i   
Next i
Edit: :oops:

Re: AngleGadget - circular trackbar + user-defined labels

Posted: Mon Jul 11, 2011 4:03 pm
by luis
Good question, I don't know. I simply noticed it's so.

I would prefer that way too, if I'm not mistaken is what C does for example. Cut the decimal part away.

That's the kind of thing would be nice to have in a real "reference of the language" detailing the core of the expressions and assignments behavior. Implicit casting, mixed expressions, and so on.

This is missing.

All these gray areas don't help.