Page 1 of 1

Thoughts on the best way to define a 'dynamic text box'

Posted: Sun Nov 20, 2011 8:35 pm
by IdeasVacuum
I have been experimenting to find a good way to present a 'Dynamic Text Box' that the User can freely re-size, edit and export as a 32bit PNG.

A short movie showing an example of one: http://www.professorcad.co.uk/pbforumquestion.html

In the movie, graphical 'handles' are displayed which the User can use to drag the Object (box graphic + multi-line text) to the desired size - that feature is not necessarily required, slider bars would be adequate, but I do need to be able to change the colour of the box and change the text font attributes.

Methods

1) 2D Sprite, using predefined image for the box

Advantages - Draw the text on the sprite, easy to output the finished result.
Disadvantages - Difficult to re-size/fit the text; Difficult to re-size the Object.

2) 2D Sprite, box graphic drawn on-the-fly

Advantages - Draw the text on the sprite; Easy to resize the sprite (replace with a re-draw); Easy to output the finished result.
Disadvantages - Difficult to re-size/fit the text; Potential for screen flicker during re-size.

3) Separate, Overlaid Windows - (Both border-less, top most has transparent background) 1 being the output for 2D drawing, the other, top window consisting of an Editor Gadget.

Advantages - Easy to resize the Object; Easy to handle box and font attribute changes.
Disadvantages - Don't know how best to handle the Z-level of the Windows so that they re-size together with a continuous display; Output the result as a screen region capture - not tested.

So, are there better methods? Would the new canvas gadget be among the contenders?

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Sun Nov 20, 2011 9:48 pm
by infratec
Hi IdeasVacuum,

A fast try:

Code: Select all

#PickUpPixels = 10

#None = $00
#Move = $01
#ResizeLeft = $02
#ResizeRight = $04
#ResizeUp = $08
#ResizeDown = $10

OpenWindow(0, 0, 0, 640, 480, "Test", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)

CanvasXPos = 10
CanvasYPos = 10
CanvasWidth = 200
CanvasHeight = 100
CanvasGadget(0, CanvasXPos, CanvasYPos, CanvasWidth, CanvasHeight)

GadgetMod = #None

Exit =#False
Repeat
  Event = WaitWindowEvent()
  Select Event
    Case #PB_Event_Gadget
      If EventGadget() = 0
        Select EventType()
          Case #PB_EventType_LeftButtonDown
            StartXPos = WindowMouseX(0)
            StartYPos = WindowMouseY(0)
            If GetGadgetAttribute(0, #PB_Canvas_MouseX) > #PickUpPixels And GetGadgetAttribute(0, #PB_Canvas_MouseX) < GadgetWidth(0) - #PickUpPixels And GetGadgetAttribute(0, #PB_Canvas_MouseY) > #PickUpPixels And GetGadgetAttribute(0, #PB_Canvas_MouseY) < GadgetHeight(0) - #PickUpPixels
              GadgetMod | #Move
            Else
              If GetGadgetAttribute(0, #PB_Canvas_MouseX) < #PickUpPixels
                GadgetMod | #ResizeLeft
              ElseIf GetGadgetAttribute(0, #PB_Canvas_MouseX) > GadgetWidth(0) - #PickUpPixels
                GadgetMod | #ResizeRight
              EndIf
              If GetGadgetAttribute(0, #PB_Canvas_MouseY) < #PickUpPixels
                GadgetMod | #ResizeUp
              ElseIf GetGadgetAttribute(0, #PB_Canvas_MouseY) > GadgetHeight(0) - #PickUpPixels
                GadgetMod | #ResizeDown
              EndIf
            EndIf
          Case #PB_EventType_LeftButtonUp
            If GadgetMod <> #None
              CurrentXPos = WindowMouseX(0)
              CurrentYPos = WindowMouseY(0)
              If GadgetMod & #Move
                CanvasXPos = CanvasXPos + (CurrentXPos - StartXPos)
                CanvasYPos = CanvasYPos + (CurrentYPos - StartYPos)
              Else
                If GadgetMod & #ResizeLeft
                  CanvasXPos = CanvasXPos - (StartXPos - CurrentXPos)
                  CanvasWidth = CanvasWidth + (StartXPos - CurrentXPos)
                ElseIf GadgetMod & #ResizeRight
                  CanvasWidth + CurrentXPos - StartXPos
                EndIf
                If GadgetMod & #ResizeUp
                  CanvasYPos = CanvasYPos - (StartYPos - CurrentYPos)
                  CanvasHeight = CanvasHeight + (StartYPos - CurrentYPos)
                ElseIf GadgetMod & #ResizeDown
                  CanvasHeight + CurrentYPos - StartYPos
                EndIf
              EndIf
              CanvasGadget(0, CanvasXPos, CanvasYPos, CanvasWidth, CanvasHeight)
              GadgetMod = #None
            EndIf
        EndSelect
      EndIf
    Case #PB_Event_CloseWindow
      Exit = #True
  EndSelect
Until Exit
Maybe it makes your decision easier. :mrgreen:

Bernd

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Sun Nov 20, 2011 10:22 pm
by kenmo
CanvasGadget was my first instinct too... it's great for drawing interactive objects. I think it's quite flicker-free as well?

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Sun Nov 20, 2011 10:42 pm
by infratec
But I found a problem:

Code: Select all

#PickUpPixels = 10

#None = $00
#Move = $01
#ResizeLeft = $02
#ResizeRight = $04
#ResizeUp = $08
#ResizeDown = $10

Structure GadgetStr
  No.i
  Mod.i
  XPos.i
  YPos.i
  Width.i
  Height.i
EndStructure

NewMap GadgetMap.GadgetStr()


OpenWindow(0, 0, 0, 640, 480, "Test", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)

AddMapElement(GadgetMap(), "0")
GadgetMap()\No = 0
GadgetMap()\XPos = 10
GadgetMap()\YPos = 10
GadgetMap()\Width = 200
GadgetMap()\Height = 100
CanvasGadget(0, GadgetMap()\XPos, GadgetMap()\YPos, GadgetMap()\Width, GadgetMap()\Height, #PB_Canvas_Border)

AddMapElement(GadgetMap(), "1")
GadgetMap()\No = 1
GadgetMap()\XPos = 10
GadgetMap()\YPos = 200
GadgetMap()\Width = 200
GadgetMap()\Height = 100
CanvasGadget(1, GadgetMap()\XPos, GadgetMap()\YPos, GadgetMap()\Width, GadgetMap()\Height, #PB_Canvas_Border)

GadgetMod = #None

Exit =#False
Repeat
  Event = WaitWindowEvent()
  Select Event
    Case #PB_Event_Gadget
      EventGadget = EventGadget()
      If FindMapElement(GadgetMap(), Str(EventGadget))
        Select EventType()
          Case #PB_EventType_LeftButtonDown
            StartXPos = WindowMouseX(0)
            StartYPos = WindowMouseY(0)
            If GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseX) > #PickUpPixels And GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseX) < GadgetWidth(GadgetMap()\No) - #PickUpPixels And GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseY) > #PickUpPixels And GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseY) < GadgetHeight(GadgetMap()\No) - #PickUpPixels
              GadgetMap()\Mod | #Move
            Else
              If GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseX) < #PickUpPixels
                GadgetMap()\Mod | #ResizeLeft
              ElseIf GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseX) > GadgetWidth(GadgetMap()\No) - #PickUpPixels
                GadgetMap()\Mod | #ResizeRight
              EndIf
              If GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseY) < #PickUpPixels
                GadgetMap()\Mod | #ResizeUp
              ElseIf GetGadgetAttribute(GadgetMap()\No, #PB_Canvas_MouseY) > GadgetHeight(GadgetMap()\No) - #PickUpPixels
                GadgetMap()\Mod | #ResizeDown
              EndIf
            EndIf
          Case #PB_EventType_LeftButtonUp 
            If GadgetMap()\Mod <> #None
              CurrentXPos = WindowMouseX(0)
              CurrentYPos = WindowMouseY(0)
              If GadgetMap()\Mod & #Move
                GadgetMap()\XPos = GadgetMap()\XPos + (CurrentXPos - StartXPos)
                GadgetMap()\YPos = GadgetMap()\YPos + (CurrentYPos - StartYPos)
              Else
                If GadgetMap()\Mod & #ResizeLeft
                  GadgetMap()\XPos = GadgetMap()\XPos - (StartXPos - CurrentXPos)
                  GadgetMap()\Width = GadgetMap()\Width + (StartXPos - CurrentXPos)
                ElseIf GadgetMap()\Mod & #ResizeRight
                  GadgetMap()\Width + CurrentXPos - StartXPos
                EndIf
                If GadgetMap()\Mod & #ResizeUp
                  GadgetMap()\YPos = GadgetMap()\YPos - (StartYPos - CurrentYPos)
                  GadgetMap()\Height = GadgetMap()\Height + (StartYPos - CurrentYPos)
                ElseIf GadgetMap()\Mod & #ResizeDown
                  GadgetMap()\Height + CurrentYPos - StartYPos
                EndIf
              EndIf
              CanvasGadget(GadgetMap()\No, GadgetMap()\XPos, GadgetMap()\YPos, GadgetMap()\Width, GadgetMap()\Height, #PB_Canvas_Border)
              GadgetMap()\Mod = #None
            EndIf
        EndSelect
      EndIf
    Case #PB_Event_CloseWindow
      Exit = #True
  EndSelect
Until Exit
If you move them that they are overlapping, you can not select the right Gadget for resize :cry:

Bernd

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Sun Nov 20, 2011 11:57 pm
by IdeasVacuum
Interesting infratec, I didn't think about using the canvas itself as part of the Object. Learnt a lot from your code. Overlapping would never be a problem because there would only ever be one Object at a time. However, I think the canvas can only be rectangular/square, so as-is it will not meet the requirements.

Another possibility would be to have an Editor gadget on top of an image gadget - officially, that is not recommended, but I have 'got away with it' in the past with the image gadget disabled once loaded.

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Mon Nov 21, 2011 1:12 am
by IdeasVacuum
This is (less) rubbish (now), but I think it shows that the Canvas Gadget could be the solution. The code is stripped-out of the PB help example:

Code: Select all

Enumeration
#IMAGE_Content  ; stores the previous CanvasGadget content while the mouse is down
#IMAGE_Color
#IMAGE_LoadSave
#ImageSave
#DynText
#Editor
#Canvas
#Color
#Refresh
#Shadow
#Ctr
#Lft
#Rgt
#Clear
#Load
#Save
#TextEditor
EndEnumeration

Global igCurrentColour.i, igStartX.i, igStartY.i, igEndX.i, igEndY.i
Global igLastStartX.i, igLastStartY.i, igLastEndX.i, igLastEndY.i
Global gsrcDC.l, igDeskX.i, igDeskY.i, igLastDeskX.i, igLastDeskY.i
Global sgImageOutFile.s = ""
Global igObjectExists.i = #False
;Editor Font
Global   sgFontName.s = "Arial"
Global   igFontSize.i = 16
Global igFontColour.i = RGB(0,0,0)
Global  igFontStyle.i = (#PB_Font_Bold | #PB_Font_HighQuality + 2)
Global    igFontID1.i = LoadFont(1, sgFontName, igFontSize, igFontStyle)
Global     igShadow.i = #False
Global       sgText.s = "Mary had a little lamb, it's fleece was white as snow. Everywhere that Mary went, the lamb was sure to go."

UsePNGImageDecoder()
UsePNGImageEncoder()

          ;CreateImage(#IMAGE_Content, 380, 380,32|#PB_Image_Transparent)
          CreateImage(#IMAGE_Content, 380, 380, 24)

          ;Colour Selection Button

          igCurrentColour = RGB(191,223,255)
          CreateImage(#IMAGE_Color, 40, 15, 24)

          If StartDrawing(ImageOutput(#IMAGE_Color))
                  Box(0, 0, 40, 15, igCurrentColour)
                  StopDrawing()
          EndIf

Procedure SetAlignment(iJustify, iX, iY)
;--------------------------------------

  Protected sText.s
            sText = GetGadgetText(#TextEditor)
              FreeGadget(#TextEditor)
            EditorGadget(#TextEditor, igStartX + 25, igStartY + 25, (iX-igStartX) - 30, (iY-igStartY) - 30, iJustify)
   SendMessage_(GadgetID(#TextEditor),#EM_SETTARGETDEVICE,#Null,0) ;WordWrap on
          SetGadgetColor(#TextEditor,#PB_Gadget_BackColor,igCurrentColour)
           SetGadgetFont(#TextEditor,igFontID1)
           SetGadgetText(#TextEditor, sText)

EndProcedure

Procedure.l CaptureRegion(iX.i, iY.i, iW.i, iH.i)
;------------------------------------------------

Shared gsrcDC
   dm.DEVMODE

       gsrcDC = CreateDC_("DISPLAY","","",dm)
      trgDC.l = CreateCompatibleDC_(gsrcDC)
  BMPHandle.l = CreateCompatibleBitmap_(gsrcDC,iW,iH)

  SelectObject_(trgDC,BMPHandle)
        BitBlt_(trgDC,0,0,iW,iH,gsrcDC,iX,iY,#SRCCOPY)
      DeleteDC_(trgDC)
     ReleaseDC_(BMPHandle,srcDC)

  ProcedureReturn BMPHandle

EndProcedure

Procedure ImageSave()
;--------------------

Protected iX.i = (igLastDeskX - 5)
Protected iY.i = (igLastDeskY - 5)
Protected iW.i = (igLastEndX - igLastStartX) + 10
Protected iH.i = (igLastEndY - igLastStartY) + 10

  If(igShadow = #True) : iW + 4 : iH + 4 : EndIf

     ScreenCaptureAddress = CaptureRegion(iX,iY,iW,iH)
  If ScreenCaptureAddress <> 0

           ;CreateImage(#ImageSave,iW,iH,32|#PB_Image_Transparent)
            CreateImage(#ImageSave,iW,iH,24)
           StartDrawing(ImageOutput(#ImageSave))
              DrawImage(ScreenCaptureAddress,0,0)
            StopDrawing()
              SaveImage(#ImageSave,sgImageOutFile,#PB_ImagePlugin_PNG)

              FreeImage(#ImageSave)
              DeleteDC_(gsrcDC)
          DeleteObject_(ScreenCaptureAddress)
  Else
          MessageRequester("Image Save Failed", "Cannot save file: " + sgImageOutFile)
  EndIf

EndProcedure

Procedure DrawObject(x,y)
;------------------------

        Shared igObjectExists, igCurrentColour, igStartX, igStartY, igLastStartX, igLastStartY, igLastEndX, igLastEndY, igDeskX, igDeskY

        If StartDrawing(CanvasOutput(#Canvas))

                   DrawImage(ImageID(#IMAGE_Content), 0, 0)

                   ;Shadow
                    If(igShadow = #True)

                    RoundBox(igStartX + 4, igStartY + 4, (x-igStartX), (y-igStartY), 12, 12, RGB(128,128,128))
                    EndIf

                   ;Outline
                    RoundBox(igStartX, igStartY, x-igStartX, y-igStartY, 14, 14, RGB(0,0,0))

                   ;Fill
                    RoundBox(igStartX + 2, igStartY + 2, (x-igStartX) - 4, (y-igStartY) - 4, 12, 12, igCurrentColour)


              SetGadgetColor(#TextEditor,#PB_Gadget_BackColor,igCurrentColour)
               SetGadgetFont(#TextEditor,igFontID1)
                ResizeGadget(#TextEditor,igStartX + 25, igStartY + 25, (x-igStartX) - 30, (y-igStartY) - 30)
               SetGadgetText(#TextEditor,sgText)

                 DrawingMode(#PB_2DDrawing_Default)
                 StopDrawing()

                 igLastStartX = igStartX
                 igLastStartY = igStartY
                  igLastDeskX = igDeskX
                  igLastDeskY = igDeskY
                   igLastEndX = x
                   igLastEndY = y
               igObjectExists = #True
        EndIf

EndProcedure

Procedure OpenWin()
;------------------

        If OpenWindow(#DynText,0,0,460,400,"Dynamic Caption Box",#PB_Window_Invisible|#PB_Window_SystemMenu|#PB_Window_ScreenCentered)

                       CanvasGadget(#Canvas, 10, 10, 380, 380, #PB_Canvas_ClipMouse)

                  ButtonImageGadget(#Color,  400,  10, 50, 25, ImageID(#IMAGE_Color))
                       ButtonGadget(#Refresh,400,  40, 50, 25, "Refresh")

                       ButtonGadget(#Shadow, 400,  80, 50, 25, "Shadow")
                       ButtonGadget(#Ctr,    400, 125, 50, 25, "Ctr Just")
                       ButtonGadget(#Lft,    400, 150, 50, 25, "Lft Just")
                       ButtonGadget(#Rgt,    400, 175, 50, 25, "Rgt Just")

                       ButtonGadget(#Clear,  400, 280, 50, 25, "Clear")
                       ButtonGadget(#Load,   400, 335, 50, 25, "Load")
                       ButtonGadget(#Save,   400, 365, 50, 25, "Save")

                       EditorGadget(#TextEditor,10,10,380,380,#ES_CENTER)

                       SendMessage_(GadgetID(#TextEditor),#EM_SETTARGETDEVICE,#Null,0) ;WordWrap on

                 SetGadgetAttribute(#Canvas,#PB_Canvas_Cursor,#PB_Cursor_Default)
                         HideWindow(#DynText,#False)

        EndIf

EndProcedure

Procedure WaitForUser()
;----------------------
  Shared igObjectExists
  Repeat
       iEvent = WaitWindowEvent()
    If iEvent = #PB_Event_Gadget

                  iEventGadget = EventGadget()
           Select iEventGadget

             Case #Canvas
                     igEndX = GetGadgetAttribute(#Canvas, #PB_Canvas_MouseX)
                     igEndY = GetGadgetAttribute(#Canvas, #PB_Canvas_MouseY)

                     Select EventType()

                               Case #PB_EventType_LeftButtonDown
                                 ;
                                 ; This stores the current content of the CanvasGadget in #IMAGE_Content,
                                 ; so it can be re-drawn while the mouse moves
                                 ;
                                 If StartDrawing(ImageOutput(#IMAGE_Content))
                                          DrawImage(GetGadgetAttribute(#Canvas, #PB_Canvas_Image), 0, 0)
                                        StopDrawing()
                                 EndIf

                                  igDeskX = DesktopMouseX()
                                  igDeskY = DesktopMouseY()
                                 igStartX = igEndX
                                 igStartY = igEndY
                                 DrawObject(igEndX, igEndY)

                               Case #PB_EventType_LeftButtonUp
                                 DrawObject(igEndX, igEndY)

                               Case #PB_EventType_MouseMove
                                 If GetGadgetAttribute(#Canvas, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton
                                   DrawObject(igEndX, igEndY)
                                 EndIf
                     EndSelect

             Case #Color
                     igCurrentColour = ColorRequester(igCurrentColour)
                     If StartDrawing(ImageOutput(#IMAGE_Color))
                                          Box(0, 0, 40, 15, igCurrentColour)
                                  StopDrawing()
                           SetGadgetAttribute(#Color, #PB_Button_Image, ImageID(#IMAGE_Color))
                     EndIf

             Case #Shadow
                     If(igShadow = #False)
                                igShadow = #True
                     Else
                                igShadow = #False
                     EndIf

                     If(igObjectExists = #True)

                              igStartX = igLastStartX
                              igStartY = igLastStartY
                              DrawObject(igLastEndX,igLastEndY)
                     EndIf

             Case #Refresh
                     igStartX = igLastStartX
                     igStartY = igLastStartY
                     DrawObject(igLastEndX,igLastEndY)

             Case #Ctr
                     igStartX = igLastStartX
                     igStartY = igLastStartY
                     SetAlignment(#ES_CENTER, igLastEndX, igLastEndY)

             Case #Lft
                     igStartX = igLastStartX
                     igStartY = igLastStartY
                     SetAlignment(#ES_LEFT, igLastEndX, igLastEndY)

             Case #Rgt
                     igStartX = igLastStartX
                     igStartY = igLastStartY
                     SetAlignment(#ES_RIGHT, igLastEndX, igLastEndY)

             Case #Clear
                     If StartDrawing(CanvasOutput(#Canvas))
                       Box(0, 0, 380, 380, $FFFFFF)
                       StopDrawing()
                     EndIf

                     igObjectExists = #False

             Case #Load
                        File$ = OpenFileRequester("Load Image...", "", "PNG Images|*.png|All Files|*.*", 0)
                     If File$

                              If LoadImage(#IMAGE_LoadSave, File$)

                                    If StartDrawing(CanvasOutput(#Canvas))

                                           Box(0, 0, 380, 380, $FFFFFF)
                                           DrawImage(ImageID(#IMAGE_LoadSave), 0, 0)
                                           StopDrawing()
                                    EndIf

                                    FreeImage(#IMAGE_LoadSave)
                              Else

                                    MessageRequester("Image File", "Cannot load image: " + File$)
                              EndIf
                     EndIf

             Case #Save
                        sgImageOutFile = SaveFileRequester("Save Image", sgImageOutFile, "PNG Images|*.png|All Files|*.*", 0)
                     If sgImageOutFile And (FileSize(sgImageOutFile) = -1 Or MessageRequester("File Exists", "Overwrite this file? " + sgImageOutFile, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes)

                            ImageSave()
                     EndIf

           EndSelect

    EndIf

  Until iEvent = #PB_Event_CloseWindow

EndProcedure

OpenWin()
WaitForUser()
End
Snag with the editor gadget is the scrollbars showing-up. Is there a way to suppress them?

Edit: Slightly refined to be less rubbish :)
Edit2: Further refinement, plus extra issues!
Edit3: Changed to Kiffy's text alignment method.

New question: I'm saving the Object out as a PNG. As it is an irregular shape, the image has to include some of the background. The apps receiving the image need the background to be transparent. I can set 32bits during image create, but since the save is done via screen capture, the image is actually 24bit. Is there a way to identify the (white) background as being the transparency region so that it is saved as such in a PNG? This is something that image viewers such as IrfanView can accomplish with 24bit images (but with User input)

Edit: Answer: Create a 32bit Image for the screen capture. Before saving, set DrawingMode(#PB_2DDrawing_AlphaChannel), then Fill the background with the colour representing transparency: FillArea(0, 0, igBorderColour, igTransparent) [igTransparent = RGBA(0,0,0,0)]

Re: Thoughts on the best way to define a 'dynamic text box'

Posted: Mon Nov 21, 2011 6:51 am
by IdeasVacuum
Oh-oh, when I do a screen capture (BitBlt), the capture is perfect......... but only the graphic is there, the text is missing :shock:

I'm thinking that might be a z-level issue, but how to promote an Editor Gadget to topmost Z?

Edit: Kiffy's text justification enhancement seems to have fixed this problem. Code in 1st post updated again.