ProgressCircle (for Linux/Windows & Mac.. I guess))

Share your advanced PureBasic knowledge/code with the community.
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

@superadmin:
What is the advantage of showing a gif animation in a webgadget ? (that's what you suggest at least)
Where's the flexibility of such a thing? "skinning"?

we have: a resizeable gadget with flexible amount of segments which can be colored as you like.... not consuming much memory ... with an respectable optical effect which could take every status at every time DEVELOPED BY at least 3 Community members (so far).... that it has it's glitches.. no question... but together we can get it polished....

if you think, you're way is more flexible and less consuming memory...... show us how do you want to do that! but..: how many pictures do you want to create and deliver to satisfy all possible needs? ... and there are much more questions I could ask...
anyway.....

as Demivec wrote: I'd invited EVERYBODY to enhance this gadget... if you want to, code a procedure which shows only images and could be called with a (additional) parameter....(enhance the structure if needed)

if you need help, don't hesitate to ask or show where your code fails or doesn't work....
User avatar
GeoTrail
Addict
Addict
Posts: 2794
Joined: Fri Feb 13, 2004 12:45 am
Location: Bergen, Norway
Contact:

Post by GeoTrail »

I think this is great. Easy to use and low on memory consumption. Should be added into the default PB gadget directory in my opinion ;)
I Stepped On A Cornflake!!! Now I'm A Cereal Killer!
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

:D thanks a lot.... yes.. that would be a good thing.. but I guess Fred would not add this because it's written in BASIC :shock: (just kidding :wink:)
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

Code update:
  • - Improved aspects of circle drawing to make it more accurate
    - made 3 changes to ProgressCircle structure:
    ---- Added Size variable to specify size of circle during init, same as it was before last update. Size should always be even.
    ---- Changed radius variable to a float, the user should not set this variable now but use Size instead.
    ---- Added style variable to specify if circle is continous (=1) or segmented.
    - continous circles are implemented! :D set style=1. This enables more segments to fit on a circle of a given size, i.e. for Size 50 you can have 46 segments on a continous circle instead of 41 with a segmented circle. Continuous circles accomplish the coloring of their segments by varying the SegmentColor by small amounts (-1,+0,+1).
Other things I am looking to implement are an additional border on inside and outside for possible 3D look. To be able to specify a color scheme for a circle so that it will use a specific color for a given segment or an interpolated color. A more elaborate demo showing a greater range of features.

Code: Select all

;- ProgressCircle
;  2008 by walker
; modifications by ebs
; modifications by Demivec
; modifications by walker
;--------------------------------------------
;-free for everyone and any purposes
;-enhancements made by others
;-must be published and posted in the same PB-Forum ;-)
;-cross plattform ability must be achieved
;--------------------------------------------
EnableExplicit

; [Demivec] added Size parameter for init instead of radius, changed radius to a float,
;           added style parameter to select a continous or segmented circle (1=continuos)
;Allows more than one progressCircle to be displayed. 
Structure ProgressCircle 
  ;for init: need to set Size,segmentCount,segmentColor,backgroundColor,gx,gy,gadgetNum,imageNum
  ;  all values used for init, excluding segmentColor are considered constants after init is performed
  ;between updates: can change segmentColor,textcolor, either fontID or newFont/fontNum

  
  Size.l            ;size of circle (even #'s only);used during init ;12 is minimum;  maximum... your screen ;-)
  radius.f          ;radius of circle ;(1/2 Size) used internally and not to be changed by the user
  segmentCount.l    ;#of segments in circle ;any value allowed, working values are usually less than Size (i.e. segments<Size)
                    ;for max segments at various sizes these work (seg,Size) (8,12) (12,16) (16,20) (32,30) (1 to 44,50) (100,120) 
  segmentColor.l    ;color of circle ,during init the entire circle is drawn in this color
  backgroundcolor.l ;background color of circle
  textcolor.l       ;color of text in circle
  
  gx.l              ;coordinates to create ImageGadget at (used only during init)
  gy.l  
  
  ;If any of the next two values are set to #PB_any, an init call will set them with their new value,
  ;if they are not equal to #PB_Any their value will be used for the desired item.
  gadgetNum.l       ;if =#PB_Any during init, will be equal to gadget# on exit
  imageNum.l        ;if =#PB_Any during init, will be equal to image# on exit
  
  ;A user should either set fontID to a pre-initialized fontID or they should set newFont to the name of font.
  ;If newFont is used it will have the side-effect of freeing the previous fontID, so for a given
  ;ProgressCircle only one method should be used during it's existance.
  fontID.l          ;font handle for displayed text, if not zero it is treated as valid (or pre-initialized)
  newFont.s         ;name of font to use load during next update, fontID will equal the font handle and this
                    ;will equal "" after LoadFont is attempted
  fontNum.l         ;if =#PB_Any during init, will be equal to font# on exit or zero, this value can be read
                    ;to determine if a new font was succesfully loaded (it will be non-zero if successful)
  
  style.l           ;indicates if using a continuous (=1) or a segmented circle (<>1)
EndStructure


; [Demivec] utilized a structure to hold ProgressCircle data, unified seperate procedures and created
;  a command format to specify actions (rather OOP like), allowed font to be specified and changed,
;  made it possible to use either #pb_any or custom values for gadget#,image#,fontID.

Procedure ProgressCircle(*pCircle.ProgressCircle, cmd.l, state.l = 1, text.s = "")
  Protected x.l,y.l,x2.l,y2.l,m.l,TW2.l,TH2.l,rangeStart.l ;added x2,y2 to help with continuous circle
  Protected angle_rad.f ; [Demivec] changed to a float
  
  ;Call procedure ProgressCircle with the forms below to accomplish each given task:
  ;  ProgressCircle(@Data.ProgressCircle,10)                Setup Data of ProgressCircle with default values
  ;  ProgressCircle(@Data.ProgressCircle,0)                 Init ProgressCircle
  ;  ProgressCircle(@Data.ProgressCircle,1,state# [,Text])  Draw a specific state# [, display Text]
  ;  ProgressCircle(@Data.ProgressCircle,1,-state# [,Text]) Draw a range of states from 1 to +state# [, display Text]
  ;  ProgressCircle(@Data.ProgressCircle,-1)                Free circle elements (image,font,gadget), convenient to
  ;                                                         use if all three items were created by ProgressCircle. 
  Select cmd ;commands in order of likely use
    ;the command #'s are more or less arbitrary, may be better to replace with constants in future
    ;of the form #PC_SetDefaults_cmd or maybe these forms #PCCmd_Init,#PCCmd_Update,#PCCmd_FreeElements
    Case 1 ;{draw state#
      
      ;check if current font should be replaced with a new font
      If *pCircle\newFont <> ""
        If *pCircle\fontNum <> #PB_Any
          FreeFont(*pCircle\fontNum) ;free font if a previous font was loaded
        EndIf
        *pCircle\fontNum = LoadFont(*pCircle\fontNum,*pCircle\newFont,*pCircle\radius / 3 - 1,#PB_Font_Bold)
        *pCircle\newFont = "" ;reset so repeated attempts will not be made
        If IsFont(*pCircle\fontNum) ;if successfully loaded set the fontID
          *pCircle\fontID = FontID(*pCircle\fontNum)
        EndIf 
      EndIf
      CallDebugger
      StartDrawing(ImageOutput(*pCircle\imageNum))   
      ;handle text first
      If text <> "" 
        If *pCircle\fontID <> 0  ;check if fontID is initialized
          DrawingFont(*pCircle\fontID) ;use only successfully loaded font
          TH2 = TextHeight(text) / 2
          TW2 = TextWidth(text) / 2   
          ; [Demivec] adjusted circle function to use new parameter
          Circle(*pCircle\radius + 0.5,*pCircle\radius + 0.5,*pCircle\Size / 3,*pCircle\backgroundcolor);clear text area
          DrawText(*pCircle\radius - TW2,*pCircle\radius - TH2,text,*pCircle\textcolor,*pCircle\backgroundcolor)
        EndIf
      EndIf   
      
      ;check if setting a range or only a single state 
      ; a range will be set (of from 1 to +state) if state is negative,
      ; only the single state will be set if state>0
      If state<0
        rangeStart = 1
        state * -1
      Else
        rangeStart = state
      EndIf
      CallDebugger
      ;first segment is at top of circle
      state - 1
      For m= rangeStart - 1 To state ; To clear the circle (set color to initial color and text to 1 space, and state=-segments)  [Demivec] changed explanation
        angle_rad = #PI * ((2 * m + 1) / *pCircle\segmentCount - 1/2) 
        ; calculate point on radius
        x = *pCircle\radius + (*pCircle\radius - 1) * Cos(angle_rad) ; [Demivec] adjusted calculation, finds a point at a set distance from largest radius
        y = *pCircle\radius + (*pCircle\radius - 1) * Sin(angle_rad)           
        
        ;fill segment with segmentColor, if non-continuos, or small variations of segmentColro if a continuous circle
        If *pCircle\style = 1 
          If m = (*pCircle\segmentCount - 1) 
            FillArea(x,y,-1,*pCircle\segmentColor + 1 - 2 *(*pCircle\segmentCount & 1)) ;handle color choice for last segment
          Else
            FillArea(x,y,-1,*pCircle\segmentColor + (m & 1)) ;handle color choice for all but last segment
          EndIf 
        Else
          FillArea(x,y,*pCircle\backgroundcolor,*pCircle\segmentColor) ;handle non-continuous circle segment
        EndIf 
      Next
      StopDrawing()
      
      SetGadgetState(*pCircle\gadgetNum,ImageID(*pCircle\imageNum)) ;}
    Case 0  ;{initialize progress circle image and gadget
      ;  
      If *pCircle\imageNum = #PB_Any 
        *pCircle\imageNum = CreateImage(#PB_Any,*pCircle\Size,*pCircle\Size) ;assign a generated image#
      Else
        ;use image# supplied (frees previous image if necessary)
        CreateImage(*pCircle\imageNum,*pCircle\Size,*pCircle\Size)
      EndIf 
      
      *pCircle\radius = (*pCircle\Size - 1) / 2 ; [Demivec] adjusted calculation based on circle function side-effects
      StartDrawing(ImageOutput(*pCircle\imageNum))
      Box(0,0,*pCircle\Size,*pCircle\Size,*pCircle\backgroundcolor)
      ; [Demivec] adjusted circle function to use new parameter
      Circle(*pCircle\radius + 0.5,*pCircle\radius + 0.5,*pCircle\radius + 0.5,*pCircle\segmentColor+(*pCircle\style & 1) * 2)
      Circle(*pCircle\radius + 0.5,*pCircle\radius + 0.5,*pCircle\Size / 3,*pCircle\backgroundcolor) 
      LineXY(*pCircle\radius,*pCircle\radius,*pCircle\radius,0,*pCircle\segmentColor)      
      
      If *pCircle\style = 1 ;continuous
         ;draw alternating segments using small variations in color
        For m = 1 To *pCircle\segmentCount - 1
          angle_rad = #PI * ((2 * m) / *pCircle\segmentCount - 1/2) 
          x = *pCircle\radius * (1 + Cos(angle_rad))
          y = *pCircle\radius * (1 + Sin(angle_rad))
          LineXY(*pCircle\radius,*pCircle\radius,x,y,*pCircle\segmentColor + (m & 1))
          angle_rad = #PI * ((2 * m - 1) / *pCircle\segmentCount - 1/2) 
          ; calculate point on radius
          x2 = *pCircle\radius + (*pCircle\radius - 1) * Cos(angle_rad) 
          y2 = *pCircle\radius + (*pCircle\radius - 1) * Sin(angle_rad)           
          FillArea(x2,y2,-1,*pCircle\segmentColor + ((m + 1) & 1))
        Next
        If (*pCircle\segmentCount & 1) = 1 ;for odd segmentCount last segment gets a unique color
          LineXY(*pCircle\radius,*pCircle\radius,x,y,*pCircle\segmentColor - (*pCircle\segmentCount  & 1))
        EndIf
        angle_rad = #PI * ((2 * (*pCircle\segmentCount) - 1) / *pCircle\segmentCount - 1/2) 
        x = *pCircle\radius + (*pCircle\radius - 1) * Cos(angle_rad) 
        y = *pCircle\radius + (*pCircle\radius - 1) * Sin(angle_rad)           
        FillArea(x,y,-1,*pCircle\segmentColor + 1 - 2 *(*pCircle\segmentCount & 1))
      Else  ;non-continuous
        ;draw radial lines
        For m = 1 To *pCircle\segmentCount - 1
          angle_rad = #PI * ((2 * m) / *pCircle\segmentCount - 1/2) ;
          x = *pCircle\radius * (1 + Cos(angle_rad))
          y = *pCircle\radius * (1 + Sin(angle_rad))
          LineXY(*pCircle\radius,*pCircle\radius,x,y,*pCircle\backgroundcolor)
        Next
      EndIf 
      
      StopDrawing()
        
      If *pCircle\gadgetNum = #PB_Any 
        *pCircle\gadgetNum = ImageGadget(#PB_Any,x,y,*pCircle\Size,*pCircle\Size,ImageID(*pCircle\imageNum))
      Else
        ImageGadget(*pCircle\gadgetNum,*pCircle\gx,*pCircle\gy,*pCircle\Size,*pCircle\Size,ImageID(*pCircle\imageNum))
      EndIf ;}
    Case -1 ;{free progress circle elements (image, font, gadget)
      If IsGadget(*pCircle\gadgetNum)
        FreeGadget(*pCircle\gadgetNum)
        *pCircle\gadgetNum = 0
      EndIf
      If IsFont(*pCircle\fontNum)
        FreeFont(*pCircle\fontNum)
        *pCircle\fontNum = 0
      EndIf
      If IsImage(*pCircle\imageNum)
        FreeImage(*pCircle\imageNum)
        *pCircle\imageNum = 0
      EndIf ;}
    Case 10 ;{setup default progress circle data, suitable for an init cmd
      ;gx,gy,textColor values are left unchanged
      *pCircle\Size = 50 ;
      *pCircle\segmentCount = 25
      *pCircle\segmentColor = $AAB68F 
      *pCircle\backgroundcolor = $D2D8C3
      *pCircle\gadgetNum = #PB_Any
      *pCircle\imageNum = #PB_Any
      *pCircle\fontNum = #PB_Any
      *pCircle\fontID = 0 
      
      CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      *pCircle\newFont = "Arial"
      CompilerEndIf
      
      CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
      *pCircle\newFont = "Arial"
      CompilerEndIf
        
      CompilerIf #PB_Compiler_OS = #PB_OS_Linux
      *pCircle\newFont = "FreeSans" ;this is only a guess, suggestions welcome
      CompilerEndIf ;}
       
  EndSelect
  
  While WindowEvent():Wend; update the gadget
  ProcedureReturn 1

EndProcedure

;---- DEMO -----<
Define.l r,g,b,count,m
Define.l event

OpenWindow(0,0,0,310,310,"ProgressCircle DEMO",#PB_Window_SystemMenu | #PB_Window_SizeGadget)
CreateGadgetList(WindowID(0))

Define.ProgressCircle circle_1,circle_2

;setup progressCircle data
ProgressCircle(@circle_1,10) ;set ProgressCircle defaults
circle_1\gadgetNum = 0 ;#PB_Any
circle_1\gx = 70
circle_1\gy = 70
circle_1\style = 1

ProgressCircle(@circle_1,0) ;initialize progressCircle

r = 10
g = 150
b = 10
count = 0
Repeat
  circle_1\segmentColor = RGB(r, g, b)
  For m = 1 To circle_1\segmentCount
    circle_1\textcolor = RGB(236, 200 - (m * 5), 19)
    ProgressCircle(@circle_1,1,m,StrF(m / circle_1\segmentCount * 100,0) + "%") ;update ProgressCircle
    Delay(100)
    event = WindowEvent()
    While event ;improved event catcher for demo
      If event = #PB_Event_CloseWindow
        Break 2
      EndIf
      event=WindowEvent()
    Wend
  Next
  count+1
  r+20
  g+20
  b+10
  If r >240 Or g>240 Or  b>240

    r=Random(255)
    g=Random(255)
    b=Random(255)
  EndIf
Until event =#PB_Event_CloseWindow Or count=3
circle_1\segmentColor = $AAB68F
ProgressCircle(@circle_1,1,-circle_1\segmentCount) ; clear the ProgressCircle....
Delay(300)

;ProgressCircle(@circle_1,-1) ;free Progress Circle elements, image,gadget,font [Demivec]
End
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

it's looking very promising :cool: .. but ... isn't working... (continous circle) I'll get only a green caret... (i'll have a closer look)
The "standard" circle has an issue with the first and last segment... they have no border between them... If I change the line 168 to

Code: Select all

For m = 0 To *pCircle\segmentCount 
it's working again (the segmented circle)... but with a double line on the top (needs some investigation too)

A question: why did you change the var-type to float? (#pi is stored as double... this will cause some inaccuracy besides the "normal" inaccuracy of floats/doubles... is there a particular reason?
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

walker wrote:it's looking very promising :cool: .. but ... isn't working... (continous circle) I'll get only a green caret... (i'll have a closer look)
The "standard" circle has an issue with the first and last segment... they have no border between them... If I change the line 168 to

Code: Select all

For m = 0 To *pCircle\segmentCount 
it's working again (the segmented circle)... but with a double line on the top (needs some investigation too)

A question: why did you change the var-type to float? (#pi is stored as double... this will cause some inaccuracy besides the "normal" inaccuracy of floats/doubles... is there a particular reason?
Thanks for correcting that, I had made attempts at sharing more code between the 2 types of circles but didn't code the coloring of the line properly for the segmented circle. It can be corrected by making the change you specified and you should also move Line#144 (the LineXY() function) down between lines #147 and #148. The code from #144 to #149 should then read:

Code: Select all

      Circle(*pCircle\radius + 0.5,*pCircle\radius + 0.5,*pCircle\Size / 3,*pCircle\backgroundcolor)
      
      If *pCircle\style = 1 ;continuous
        ;draw alternating segments using small variations in color
        LineXY(*pCircle\radius,*pCircle\radius,*pCircle\radius,0,*pCircle\segmentColor)
        For m = 1 To *pCircle\segmentCount - 1
          angle_rad = #PI * ((2 * m) / *pCircle\segmentCount - 1/2)
The short answer regarding the float is it works :wink:.

The long winded answer is that it was determined that the results of the Sin() function was giving results that weren't desired. For instance Sin(#PI) didn't equal Sin(0). This was due to #PI being rounded. I did extensive testing to determine ways of producing identical results from the Sin() function using those values. They crop up when the segmentCount is evenly divisible by 4. The values should come out equal so that the segments on opposite sides of the circle are on the same horizontal line.

This was accomplished by using a float instead of a double. There is no descernible loss in precision. The size of circles are such that they would be much larger than your screen before you would notice a difference. To demonstrate this examine this code:

Code: Select all

Define x.l,x2.l,y.l,y2.l,angle_rad.f,angle_rad2.d,segmentCount.l,radius.f,m.l,Size.l

Size = 1280
segmentCount = 1000
radius = (Size + 1 ) / 2

For m=0 To segmentCount-1
  ;using a float
  angle_rad = #PI * ((2 * m) / segmentCount - 1/2)
  x = radius * (1 + Cos(angle_rad))
  y = radius * (1 + Sin(angle_rad))
  ;using a double
  angle_rad2 = #PI * ((2 * m) / segmentCount - 1/2) 
  x2 = radius * (1 + Cos(angle_rad2))
  y2 = radius * (1 + Sin(angle_rad2))
  
  ;check for differences
  If (x <> x2) Or (y <> y2)
    Debug "m= " + Str(m) + ", (" + Str(x) + "," + Str(y) + ") <> (" + Str(x2) + "," + Str(y2) + ")"
  EndIf 
Next
This calculates the segment points on a circle of size 1280 with 1000 segments. This is in the upper maximums of screen resolution. This shows whenever there would be a difference in coordinates calculated. It shows only 1 difference for the circle in question and only 1 coordinate differs by only 1. I have done more extensive tests, when there are differences they are only 1 coordiant that differs by 1.

For a given circle with a segmentCount as large as the size of the circle it can have as many as 9 such differences. But there are no circles smaller than size 365 that have more than 4 such differences. I think they are neglible.

All other inaccuracies in the rendering of the circles is due to the manner that the functions are translated into API functions. For instance, in PureBasic we are specifying a circle with center at (radius,radius) with radius = radius (naturally). For the Windows OS this is translated into a box from (0,0) to (2*radius,2*radius) that contains a circle touching the borders of the box. This makes the radius of the circle that is drawn have a smaller radius than the one specified (by 0.5).

This is evident if you consider that the width of the box containing a circle of radius X would be an odd number, always. In our case it's always even. This is also what makes the Size limited to being an even number. If it was odd the circle drawn would be either too large for the box or too small (but still an even size). These limitations could not be resolved without drawing the circle entirely through code, without using the PB function. I thought that wasn't the route to take.
walker
Enthusiast
Enthusiast
Posts: 634
Joined: Wed May 05, 2004 4:04 pm
Location: Germany

Post by walker »

:shock: I like the short answer :lol:

You're right... I guess we can live with that sort of limitations...
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Post by superadnim »

Sorry I never meant to use GIF but a series of frames, perhaps in PNG with alpha channel - that's how I did it at least and it works just fine, even Nero does it (but let's face it, they bloated their software long time ago; not necessarily with this though).

I also use vector graphics for my source data, out of convenience. There are certain technologies that Microsoft offers you to handle the skinning / theming of applications in a nice way for both programmers and artists.

:lol: should I bash the keyboard and give up?
:?
User avatar
Demivec
Addict
Addict
Posts: 4270
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Post by Demivec »

superadnim wrote:Sorry I never meant to use GIF but a series of frames, perhaps in PNG with alpha channel - that's how I did it at least and it works just fine, even Nero does it (but let's face it, they bloated their software long time ago; not necessarily with this though).

I also use vector graphics for my source data, out of convenience. There are certain technologies that Microsoft offers you to handle the skinning / theming of applications in a nice way for both programmers and artists.
@superadnim: would you give an example? Or more specifically would you show a simple set of images possibly with skinning / theming and display them repetively in a imageGadget or something? I would find it useful to know how the process could be carried out. A brief example would go a long way. The idea would be useful to see..
Post Reply