Realbasic: Canvas Tutorial Lesson 7

In this next tutorial we’ll going to add a menu to our elements so the users can really interact with them.

The idea would be that when we click on an element, this element becomes active and gets a menu with a play and stop button. When we click on another element, the menu is removed from the previous element and opened on the new element. When we click on something else than an element, it disappears.

The picture below shows what we want to do:

Let’s get started with the code:

We create a new class ABMenu that will hold our play and stop buttons. We give it a type and a picture.

X as integer
Y as integer
W as integer
H as integer
Image as picture

Sub Constructor(tmpType as integer, tmpImage as picture)
  me.Type = tmpType
  me.Image = tmpImage 
End Sub

Sub Destructor()
  CleanUp
End Sub

Sub CleanUp()
  Image = nil
End Sub

In the ABelement, add a new property HasMenu. This way we can choose if an element has a menu or not. Also add a table that holds our MenuButtons.

Hasmenu as Boolean
MenuButtons(-1) as ABMenu

Change the Init() function to set our HasMenu variable:

Sub Init(tmpID as integer, tmpType as integer, tmpX as integer, tmpY as integer, tmpInnerX as integer, tmpInnerY as integer, tmpImage as picture, tmpMask as picture, tmpGlow as picture, tmpVisible as boolean, tmpHasMenu as boolean )
  me.ID = tmpID
  me.x = tmpX
  me.y = tmpY
  me.Visible = tmpVisible
  me.w = tmpImage.Width
  me.h = tmpImage.Height
  me.Type = tmpType
  me.MyGlow = tmpGlow
  me.MyMask = tmpMask
  me.InnerX = tmpInnerX
  me.innerY = tmpInnerY
  me.HasMenu = tmpHasMenu ' new
  
  Image = NewPicture(w,h, 32)
  image.Graphics.DrawPicture tmpImage,0,0
  
  if MyMask <> nil then
    me.w = MyMask.Width
    me.h = MyMask.Height
  end if
  
  pBuffer = NewPicture(w,h, 32)
  gBuffer = pBuffer.Graphics
  
  DrawMe
End Sub

A new function AddMenuButton() will allow us to add Menu buttons to each element:

Sub AddMenuButton(tmpType as integer, tmpPicture as picture)
  Dim tmpMenu as new ABMenu(tmpType, tmpPicture)
  MenuButtons.Append tmpMenu
End Sub

Now for the changes in the ABCanvas class. We’ll add a new property CurrentMenuElement. This is similar to our DragElement from a previous lesson: it holds a temporary link to the element that currently has the menu.

CurrentMenuElement as ABElement

We make a change to our RefreshElement fuction because we want also to refresh the space that our menu is taking:

Sub RefreshElement(tmpElem as ABElement, Extra as integer) ' new
  Dim iLeft, iTop as Integer
  
  iLeft = tmpElem.x - tmpElem.w \ 2 - Extra ' changed
  iTop = tmpElem.y - tmpElem.h \ 2 - Extra ' changed
  
  ' redraw only me and all other ABElements we are covering
  DrawMe iLeft,iTop,tmpElem.w + Extra * 2,tmpElem.h + Extra * 2 ' changed
End Sub

Two new functions will handle a click on a menu button and if we hover over a menu button. We go through our current menu buttons and see if the mouse is over one of them. In that case we can start or stop playing the CD, DVD or comic. The code to play things is not included here as it would take us to far from our Canvas tutorials. A google search or the RealStudio forum can help you with that.

' to handle a click on one of our menu buttons
Function HandleMenuClick(X as integer, Y as integer) As boolean
  Dim a as integer
  dim tmpMenu as ABMenu
  if CurrentMenuElement <> nil then
    for a = 0 to Ubound(CurrentMenuElement.MenuButtons)
      tmpMenu = CurrentMenuElement.MenuButtons(a)
      if X >= tmpMenu.X and X <= tmpMenu.X + tmpMenu.W and Y >= tmpMenu.Y and Y <= tmpMenu.Y + tmpMenu.H then
        ' a button was clicked, handle what needs to happen (like start or stop playing)
        MsgBox "You pressed button " + Str(tmpMenu.Type) + " on element type " + str(CurrentMenuElement.Type) + " with ID " + str(CurrentMenuElement.ID)
        Return true
      end if
    next
  end if
  Return false
End Function

' to change our mouse cursor if we are hovering above a button
Function HandleMenuMove(X as integer, Y as integer) As boolean
  Dim a as integer
  dim tmpMenu as ABMenu
  if CurrentMenuElement <> nil then
    for a = 0 to Ubound(CurrentMenuElement.MenuButtons)
      tmpMenu = CurrentMenuElement.MenuButtons(a)
      if X >= tmpMenu.X and X <= tmpMenu.X + tmpMenu.W and Y >= tmpMenu.Y and Y <= tmpMenu.Y + tmpMenu.H then
        Return true
      end if
    next
  end if
  Return false
End Function

Now for the main event: the function to show our menu around our active element. Once we’ve clicked a Element, the menu should be drawn around it. We’ll start with an orange frame around the Element and then we’ll draw our menu buttons. As all our elements have their own buttons, we iterate over the MenuButtons() table and draw them from right to left. We do this because we do not know how many buttons will be drawn and we want them right aligned.

Only when the menu is completely drawn we’ll push the changed part of the buffer to the canvas.

Sub ShowMenu(tmpElem as ABelement)
  Dim iLeft, iTop as Integer
  Dim a as integer
  Dim posX as integer
  
  ' get the real left and right from our center x and y
  iLeft = tmpElem.x - tmpElem.w \ 2
  iTop = tmpElem.y - tmpElem.h \ 2
  
  ' draw our menu
  gBuffer.ForeColor = &cFF5E0F
  gBuffer.PenWidth = 3
  gBuffer.PenHeight = 3
  ' we do minus 5 because our shadow = 5
  gBuffer.DrawRect iLeft - 2, iTop - 23, tmpElem.w + 3 - 5, tmpElem.h + 24 - 5
  
  gBuffer.PenWidth = 1
  gBuffer.PenHeight = 1
  gBuffer.FillRect iLeft - 2, iTop - 23, tmpElem.w + 3 - 5, 24
  
  ' and our buttons
  PosX = iLeft + tmpElem.w - 23
  for a = 0 to Ubound(tmpElem.MenuButtons) 
    gBuffer.DrawPicture tmpElem.MenuButtons(a).Image, PosX, iTop - 21
    tmpElem.MenuButtons(a).X = posX
    tmpElem.MenuButtons(a).Y = iTop - 21
    tmpElem.MenuButtons(a).W = tmpElem.MenuButtons(a).Image.width
    tmpElem.MenuButtons(a).H = tmpElem.MenuButtons(a).Image.height
    posX = posX - 20
  next
  
  ' and draw the result to the canvas
  self.Graphics.DrawPicture pBuffer, iLeft - 3, iTop - 24, tmpElem.w + 6, tmpElem.h + 27, iLeft - 3, iTop - 24, tmpElem.w + 6, tmpElem.h + 27
End Sub

Let’s also write a function to hide our menu:

Sub HideMenu()
  if CurrentMenuElement <> nil then
    ' let's redraw
    ' we add some extra pixels around the object when we redraw because we want the menu removed
    RefreshElement CurrentMenuElement, 25
    
    ' and we reset our CurrentMenuElement
    CurrentMenuElement = nil
  end if
End Sub

Almost there! All we now have to do is handle the mouse down and mouse up event so that they can show or hide the menu, and if a menu button is clicked handle the button action.

First our MouseDown(). A very import part is that if we click on our canvas, we want to check if we are clicking a menu button. This has to be done first and before we’ll handle any other actions. If a menu button is handled, we can exit the function with return false to stop any other mouse events. If we click on the desktop, the existing menu is hidden.

Function MouseDown(X As Integer, Y As Integer) As Boolean
  ' the first thing we do is checking if we clicked on a menu
  if HandleMenuClick(X,Y) then
    ' it is handled
    Return false
  end if
  Dim tmpElem as ABElement
  tmpElem = ElementHit(X,Y)
  if tmpElem  nil then
    ' does someone have a menu that is now visible?
    HideMenu ' new
    
    if IsContextualClick then
      ' right mouse button
      
      ' bring the found ABelement to the front
      BringToFront tmpElem
      
      ' group them together
      GroupMyType tmpElem.type, X, Y, 500
      
      return false
    else
      ' left mouse button
      
      ' remember our current position
      mLastX = X
      mLastY = Y
      
      ' bring the found ABelement to the front
      BringToFront tmpElem
      
      ' refresh the element so it is redrawn
      RefreshElement tmpElem, 0
      
      ' remember this ABElement so we can drag it around
      DragElem = tmpElem
      Return true
    end if
  else
    ' does someone else have a menu that is now visible?
    HideMenu ' new
  end if
  
  ' continue with the default MouseDown event
  return MouseDown(X,Y) 
End Function

In our MouseUp() event we’ll show our menu.

Sub MouseUp(X As Integer, Y As Integer)
  if DragElem <> nil then
    ' refresh one last time so we have the very last position
    RefreshElement DragElem,0
    ' set our DragElem to nothing
    DragElem = Nil
  end if
  
  Dim tmpElem as ABElement
  tmpElem = ElementHit(X,Y)
  if tmpElem <> nil then
    if IsContextualClick = false then
      ' left mouse button
      
      ' show our menu, if any
      if tmpElem.HasMenu then
        ShowMenu tmpElem
        ' remember this ABelement having the menu
        CurrentMenuElement = tmpElem
      end if
    end if
  end if
  
  ' continue with the default MouseUp event
  MouseUp X,Y
End Sub

And in the MouseMove() event we’ll handle the mouse cursor if we are hovering above a menu button:

Sub MouseMove(X As Integer, Y As Integer)
  ' first check if we are over a menu
  if HandleMenuMove(X,Y) then
    self.MouseCursor = System.Cursors.FingerPointer
    Return
  end if
  
  ' if we are above one of our Elements, we change the mouse cursor to a little hand
  if ElementHit(X,Y) <> nil then
    self.MouseCursor = System.Cursors.FingerPointer
  else
    self.MouseCursor = System.Cursors.StandardPointer
  end if
  
  ' continue with the default MouseMove event
  MouseMove X,Y
End Sub

Finally we’ll change the LoadType() function in the Main form to add our menu buttons to our elements. The CD and DVD we’ll give a play and stop button, the Comic only a play button. Here is the changed code:

        tmpElem.Init(Counter,tmpType, rnd * (me.Width - tmpPic.width) + tmpPic.Width / 2, rnd * (me.Height - tmpPic.Height) + tmpPic.Height / 2,InnerX,InnerY, tmpPic, tmpPicMask, tmpPicGlow, true, true) ' new we use true to add a menu
        
        ' and add the menu items
        ' load our play button for our menu
        select case tmpType
        case 1 ' a comic has only a play button
          Menuf = GetFolderItem("PlayButton.png")
          tmpElem.AddMenuButton(1, menuf.OpenAsPicture)
        case 2,3 ' a CD and DVD has a play and stop button
          Menuf = GetFolderItem("PlayButton.png")
          tmpElem.AddMenuButton(1, menuf.OpenAsPicture)
          Menuf = GetFolderItem("StopButton.png")
          tmpElem.AddMenuButton(2, menuf.OpenAsPicture)
        end select

Phew, Done! Let’s run our application with the added menu. It should work like this:

As always you can download the source code for this tutorial:
http://www.gorgeousapps.com/Tut7.zip

Until next time!

Click here to Donation if you like my work

Advertisements

About Alwaysbusy

My name is Alain Bailleul and I'm the Senior Software Architect/Engineer at One-Two. I like to experiment with new technologies, Computer Vision and A.I. My projects are programmed in B4X , Xojo, C#, java, HTML, CSS and JavaScript. View all posts by Alwaysbusy

6 responses to “Realbasic: Canvas Tutorial Lesson 7

  • Richard

    Project still gives a NilObjectException @ select case f.name
    with “for a = 1 to all.count”

    Mac OSX Snow Leopard RealStudio 2011.R1.1

  • Alwaysbusy

    Hi Richard,

    Set a stop in the code and run through the loop to see what the value of a is when it happens. If a is all.count, try all.count – 1. I do not have a Mac so I can’t test this.

    Maybe someone with also a Mac and RealStudio 2011+ could help out here?

    Alwaysbusy

  • Richard

    ‘ some Comics
    LoadType 1, GetFolderItem(“”).AbsolutePath + “COMIC\”, 0,0
    ‘ some CDs
    LoadType 2, GetFolderItem(“”).AbsolutePath + “CD\”, 17,4
    ‘ Some DVDs
    LoadType 3, GetFolderItem(“”).AbsolutePath + “DVD\”, 0,2

    To

    LoadType 1, GetFolderItem(“”).Child(“COMIC”).AbsolutePath,0,0
    LoadType 2, GetFolderItem(“”).Child(“CD”).AbsolutePath,2,4
    LoadType 3, GetFolderItem(“”).Child(“DVD”).AbsolutePath,0,2

  • Charles Fasano

    I’d like to implement something similar to this, but I want a border to appear around an Element when the mouse hovers over it instead of clicking on it. The main issue is that depending on the circumstance the border color will be different to indicate different things. The border will be a picture and not a simple RGB color border.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: