Page 3 of 3

Re: Flat button with border when hovered ?

Posted: Mon Apr 15, 2013 8:46 pm
by Blue
Bisonte wrote:a litte improvement ...
that "marks" the last leftclicked gadget.
[...]
Nice touch, Bisonte.

Also, think of another improvement you should apply to procedure FlatBorderButtonEvents()
You should replace the Default case by this :
Case #PB_EventType_MouseLeave, #PB_EventType_LeftButtonUp

and, in the first Case , you ought to get rid of event #PB_EventType_MouseMove altogether.

That way, you wouldn't keep redrawing an already correctly drawn button every time the mouse moves. :wink:

Re: Flat button with border when hovered ?

Posted: Tue Apr 16, 2013 8:53 pm
by Blue
A complete colored buttons working example

Custom colored buttons implemented in a simple and effective manner
based on Bisonte's most excellent algorithm and button structure

*** Free to use and modify as you please ***

2 files :
(1) customButtons.pbi :
the include file packaging the button implementation

Code: Select all


;  »»» -------------------------------------------------------------------------------------------
;- »»» colored buttons include file «««
;- »»» Blue - April 2013
;  »»»
;  »»» original idea by charvista
;  »»» genial implementation (algorithm and structure) by Bisonte
;  »»» colored button implementation by Blue
;  »»»
;  »»» http://purebasic.fr/english/viewtopic.php?f=13&t=53765&start=0
;  »»»
;  »»» -------------------------------------------------------------------------------------------

EnableExplicit
;- button constants
;< 3 possible states for buttons
  #buttonNormal  = 33                     ; any value will do, but NEVER ZERO
  #buttonPressed = #buttonNormal + 1
  #buttonHovered = #buttonNormal + 2
;>

Prototype ColoredButton_Prototype(pointer, btnState = #buttonNormal)
;
Structure coloredButton
;<
  gadget.i
  Draw.coloredButton_Prototype    ; this is the magic : a callback function
  Text.s
  textColor.i
  hoverColor.i
  borderColor.i
  backColor.i
  font.i
  Extras.i
;>
EndStructure
;
;- hard-coded colours
#pressedColor = #Yellow    ; modify for cross-platform conformity
#inactiveColor = $DADADA

;-
Procedure ColoredButtonDraw(*p.coloredButton, btnState = #buttonNormal)
  Protected x, y
  Protected dX, dY    ; displacement of text when button pressed

  #r = 2               ; curvature of rounded corners

  If *p
    If IsGadget(*p\Gadget)
      If StartDrawing(CanvasOutput(*p\Gadget))
        DrawingMode(#PB_2DDrawing_Default)

        Select btnState
          Case #buttonNormal
            RoundBox(0,0,OutputWidth(),OutputHeight(),#r,#r,#inactiveColor)
            RoundBox(1,1,OutputWidth()-2,OutputHeight()-2,#r,#r,*p\backColor)
          Case #buttonHovered
            RoundBox(0,0,OutputWidth(),OutputHeight(),#r,#r,*p\borderColor)  
            RoundBox(1,1,OutputWidth()-3,OutputHeight()-4,#r,#r,*p\hoverColor)            
          Case #buttonPressed
            RoundBox(0,0,OutputWidth(),OutputHeight(),#r,#r,*p\borderColor)  
            RoundBox(2,2,OutputWidth()-3,OutputHeight()-3,#r,#r,#pressedColor)
            dX = 1    ; text : move right 1 pixel
            dY = 2    ; move down 2 pixels
        EndSelect

        If IsFont(*p\Font)
          DrawingFont(FontID(*p\Font))
        EndIf
        x = (OutputWidth()/2)  - (TextWidth(*p\Text)/2)  + dX
        y = (OutputHeight()/2) - (TextHeight(*p\Text)/2) + dY
        DrawingMode(#PB_2DDrawing_Transparent)
        DrawText(x,y,*p\Text,*p\TextColor)
        StopDrawing()
      EndIf
    EndIf
  EndIf
EndProcedure
;

Procedure ColoredButtonGadget(button, x, y, w, h, Text.s,
                               textColor   = 0,
                               backColor   = -1,
                               hoverColor  = -1, 
                               borderColor = -1, 
                               font        = 0 )

  Protected *p.coloredButton = AllocateMemory(SizeOf(coloredButton))
  If *p = 0
    ProcedureReturn 0
  EndIf

  InitializeStructure(*p, coloredButton)
  
  Protected ID = CanvasGadget(button, x, y, w, h)
  If button = #PB_Any : button = ID : EndIf
  
  SetGadgetData(button, *p)
  
  If borderColor = -1
     borderColor = textColor
  EndIf

  If backColor = -1
     backColor = $EAEAEA  ; default background color
  EndIf

  *p\Gadget      = button
  *p\Draw        = @coloredButtonDraw()   ; the callBack function
  *p\Text        = Text
  *p\textColor   = textColor
  *p\hoverColor  = hoverColor
  *p\borderColor = borderColor
  *p\backColor   = backColor
  *p\Font        = Font
  
  *p\Draw(*p, #buttonNormal)        ; bouton dans son apparence de départ

  ProcedureReturn ID
  
EndProcedure
;
Procedure ColoredButtonEvent(button, evType)

  If 0 = IsGadget(button) : ProcedureReturn 0 : endif  ; nothing to do if this is not a gadget

  Static pressed      ; mouse button state : MUST be remembered between events

  Protected *p.coloredButton = GetGadgetData(button)
  If 0 = *p  : ProcedureReturn 0 : endif  ; nothing to do if button definition is missing

  Protected mx, my    ; mouse coordinates

  Select evType

          Case #PB_EventType_LeftButtonUp
            *p\Draw(*p, #buttonHovered)

            pressed = 0       ;- ...... mouse button released

            ; verify whether released WITHIN button area
            mx = GetGadgetAttribute(button, #PB_Canvas_MouseX)
            my = GetGadgetAttribute(button, #PB_Canvas_MouseY)
            If mx >= 0 And my >= 0
              If mx < GadgetWidth(button) And my < GadgetHeight(button) 
                ProcedureReturn #True   ; yes, released WITHIN button area
              EndIf
            EndIf

          Case #PB_EventType_MouseMove        ;- ...... mouse movement
            If pressed        ; true when mouse button is down
            ; verify whether mouse has wandered outside button area 
              mx = GetGadgetAttribute(button, #PB_Canvas_MouseX)
              my = GetGadgetAttribute(button, #PB_Canvas_MouseY)
              If mx < 0 Or my < 0 Or mx >= GadgetWidth(button) Or my >= GadgetHeight(button) 
                If pressed = #buttonPressed
                  pressed = #buttonNormal               ; which is certainly less than #buttonNormal
                 Debug  Str(button) +" moved, drawing " + Str(pressed)
                  *p\Draw(*p, #buttonNormal)
                EndIf  
              Else  ; mouse button still down, but mouse has returned to button area 
                If pressed = #buttonNormal
                  pressed = #buttonPressed
                  Debug  Str(button) +" moved, drawing " + Str(pressed)
                  *p\Draw(*p, #buttonPressed)
                EndIf
              EndIf
            EndIf

          Case #PB_EventType_MouseEnter        ;- ...... hovering event
            Debug  Str(button) +" MouseEnter"
            *p\Draw(*p, #buttonHovered)

          Case #PB_EventType_MouseLeave
            Debug  Str(button) +" MouseLeave"
            *p\Draw(*p, #buttonNormal)

          Case #PB_EventType_LeftButtonDown        ;- ...... mouse clicked
            Debug  Str(button) +" Left Down"
            *p\Draw(*p, #buttonPressed)
            pressed = #buttonPressed      ; must remember that the mouse button is down

  EndSelect            
 
  ProcedureReturn 0  ; mouse button released outside button area, or a don't-care situation

EndProcedure
(2) the working example making use of the above include file.

Code: Select all


;- »»» Custom Colored Buttons Example file ----------------------------------------------------------
;- »»» Blue - April 2013
;  »»»
;  »»» original idea by charvista
;  »»» genial implementation (algorithm and structure) by Bisonte
;  »»» colored button implementation by Blue
;  »»»
;  »»» http://purebasic.fr/english/viewtopic.php?f=13&t=53765&start=0
;  »»»
;  »»» -------------------------------------------------------------------------------------------

EnableExplicit
;
IncludeFile "customButtons.pbi"

Enumeration
;-. gadgets 
  #cmdOK
  #cmdCANCEL

  #infos
  #infos2
  #infos3a
  #infos3b

  #button1
  #button2
  #button3
  #button4
  #button5
  #button6
  #button7
  #button8
  #button9
;>
EndEnumeration

Define Font = LoadFont(#PB_Any, "Segoe UI", 9)

Define winW = 300
Define winH = 250 
OpenWindow(0,200,100,winW,winH,"Colored buttons")

AddKeyboardShortcut(0,#PB_Shortcut_Escape, #cmdCANCEL)

;-. création des boutons
#dh = 3
Define gW, gH, gX, gY, dh
gH = 25
gW = 80

gX = 20
gY = 25
  coloredButtonGadget(#button1, gX, gY, gW, gH, "Printer", 120, $F0F0F0, $FFE0C2)
gY + gH + #dh
  coloredButtonGadget(#button2, gX, gY, gW, gH, "Computer", $E7691D, $F0F0F0, $FFE0C2)
gY + gH + #dh
  coloredButtonGadget(#button3, gX, gY, gW, gH, "Mouse", $1E1EE6, $F0F0F0, $FFE0C2, -1, Font)
gY + gH + #dh
  coloredButtonGadget(#button4, gX, gY, gW, gH, "Keyboard", $38CC42, $F0F0F0, $FFE0C2, $009933, Font)
gY + gH + #dh
  coloredButtonGadget(#button5, gX, gY, gW, gH, "Hard Disk") ;, 0, $F0F0F0, $FFE0C2, $FF9933, Font)

gY + gH + #dh
  coloredButtonGadget(#cmdOK, gX,gY,gW,gH,"OK", $E7691D, -1, -1,-1,font)
  GadgetToolTip(#cmdOK,  
      "If you click but release the mouse outside the button, nothing will happen.")

; information box
TextGadget(#infos, gX + gW + 10, GadgetY(#button3) + gH/2, 150, gH,
           "Select a button",#PB_Text_Center)

TextGadget(#infos2, gX + gW + 10, GadgetY(#button1), 150, 2*gH,
    "As with regular buttons, releasing the mouse outside a button voids any action.", 
     #PB_Text_Center)
    SetGadgetColor(#infos2, #PB_Gadget_FrontColor, #Red)

TextGadget(#infos3a, gX + gW + 2, GadgetY(#cmdOK) + 4, 32,gH,
           "<<<",#PB_Text_Center)

TextGadget(#infos3b, gX + gW + GadgetWidth(#infos3a)+2, GadgetY(#cmdOK), 150, gH+ 8,
           "Clicking here will display a message box." )

gX = winW - gW - 10 
gY = winH - gH - 10
  ButtonGadget(#cmdCANCEL, gX,gY,gW,gH,"Annuler", #PB_Button_Default)

gX - gW - 10
  ButtonGadget(#button6, gX,gY,gW,gH,"* Rien *")

;>

Define gadget, event, evType

Repeat
;-. boucle Windows
  event = WaitWindowEvent()
  
  Select event

    Case #PB_Event_Gadget

      gadget = EventGadget()      
      evType = EventType()
      
      Select gadget
       Case #cmdCANCEL : Break
       
       Case #cmdOK :
          If coloredButtonEvent(gadget, evType)
            SetGadgetText(#infos, "Button OK clicked")
            MessageRequester("Information", "You clicked the OK button.")
;            MsgBox("You clicked the OK button.")
          EndIf

       Case #button1 To  #button5 : 
        If coloredButtonEvent(gadget, evType)
          Select gadget
            Case #button1
              SetGadgetText(#infos, "Printer selected")
            Case #button2
              SetGadgetText(#infos, "Computer selected")
            Case #button3
              SetGadgetText(#infos, "Mouse selected")
            Case #button4
              SetGadgetText(#infos, "Keyboard selected")
            Case #button5
              SetGadgetText(#infos, "Hard Disk selected")
          EndSelect
        EndIf
      EndSelect

    Case #PB_Event_Menu : Break
    Case #PB_Event_CloseWindow : Break
  EndSelect
;>
ForEver

End

Enjoy...

Re: Flat button with border when hovered ?

Posted: Mon Jun 09, 2014 10:16 pm
by staringfrog
Thank you for this considerably useful topic and solutions.

However, I wonder if this piece above,

Code: Select all

InitializeStructure(*p, coloredButton)
is memory-leak safe, as ClearStructure isn't seemed to be used anywhere within the code? Is it redundant here (as we draw just a few buttons that operate throughout the program run) or just missed for brevity?

Re: Flat button with border when hovered ?

Posted: Tue Jun 10, 2014 6:02 pm
by Danilo
If you want to free the gadget and resources at runtime:

Code: Select all

Procedure ColoredButtonFree(button)
    Protected *p.coloredButton
    If IsGadget(button)
        *p = GetGadgetData(button)
        If *p
            ClearStructure(*p,coloredButton)
            FreeMemory(*p)
        EndIf
        FreeGadget(button)
    EndIf
EndProcedure
If you don't call it, everything gets released at program end. The memory leak would be at runtime,
if you create many ColoredButtonGadget() dynamically and release them by FreeGadget().
In this case the gadget data would remain in memory until program end.

Re: Flat button with border when hovered ?

Posted: Wed Jun 11, 2014 9:18 am
by staringfrog
Thank you for your snippet and explanation, it's all clear now.

Re: Flat button with border when hovered ?

Posted: Fri Jun 13, 2014 2:38 am
by Blue
@staringfrog : Thanks for raising this point. The issue never occurred to me, but now you got me thinking; i can imagine that creating many custom structures could become a problem in a large enough program. That seems to be the very definition of memory leak.

@danilo : Many thanks for your code snippet. Another lesson learned.
But i don't understand at which point in the program the structure becomes irrelevant, so that it's no longer required and may be safely freed;
• once the button has been drawn ???
• or only once the button becomes invisible, such as when its parent dialog box (for instance) is erased ?
When we create/draw regular PB gadgets, we never bother to destroy them, do we ? We leave it to the application to deal with this task when it quits. Couldn't we simply and safely handle these custom structures in a similar way ? Sorry for the somewhat confused questions, but i am indeed a bit confused here.

Re: Flat button with border when hovered ?

Posted: Fri Jun 13, 2014 6:52 am
by Bisonte
Blue wrote: But i don't understand at which point in the program the structure becomes irrelevant, so that it's no longer required and may be safely freed;
Not the structure is irrelevant, it's the allocated memory.
If you free the gadget with FreeGadget() the memory is still there (allocated), till the program ends...
So if you want to free your gadget at runtime, so be sure, that you free the memory with it. (Only this memory, that allocated by yourself)

Re: Flat button with border when hovered ?

Posted: Fri Jun 13, 2014 7:00 am
by said
If those questions are addressed to Danilo, he is definitely more capable to answer than I (he can always rectify) hope this helps
Blue wrote: But i don't understand at which point in the program the structure becomes irrelevant, so that it's no longer required and may be safely freed;
• once the button has been drawn ???
Definitely not here ! You still need the structure to redraw your buttons
• or only once the button becomes invisible, such as when its parent dialog box (for instance) is erased ?
Yes this is the right place :)
When we create/draw regular PB gadgets, we never bother to destroy them, do we ? We leave it to the application to deal with this task when it quits. Couldn't we simply and safely handle these custom structures in a similar way ? Sorry for the somewhat confused questions, but i am indeed a bit confused here.
[/quote] Because when a windows is closes (destroyed) PB takes care of freeing all contained gadgets, the same is done when a parent gadget is destroyed (a container/scrollarea/panel)

Re: Flat button with border when hovered ?

Posted: Fri Jun 13, 2014 2:29 pm
by Blue
@Bisonte, @Said :

Now i really get it. I'm sure (i think...). :?

Ahhh... such a good feeling when the fog finally lifts, thanks to guys like you. :D