[Gambas-user] Scrollarea Graphics

Tobias Boege taboege at ...626...
Mon Nov 17 18:05:44 CET 2014


On Mon, 17 Nov 2014, Sean Sayandeep Khan wrote:
> Hello, I have the folloing setup (See attached image, 
> UI1.png). The Canvas is a scrollarea. I want to add text 
> commands, such as "ADDTILE 10,10 40,60 hello" in the 
> textbox called Command. I expect as output (on clicking 
> the button "exec") a tile of width 40 and height 60 to 
> appear with top left being 10, 10 in the canvas. Here is 
> my code :
> 
> --8<-- Snip --8<--
>
> Public Sub _new()
> 
> func = "_new"
> Canvas.ResizeContents(400, 400)
> Canvas.Refresh()
> End
> 
> --8<-- Snip --8<--
> 
> Public Sub Canvas_Draw()
> 
>    Dim a As Variant[]
>    a = Canvas.Children
>    Object.Call(Me, func)
>    
>    Canvas.Refresh()
> End
> 
> 

Did you notice that your program has a very high input latency? Above is the
reason why: _new() calls Canvas.Refresh() which triggers Canvas_Draw()
(during the next event loop?) which again calls _new(), and so on.

On a second glance, there is yet another loop: Canvas.Refresh() from inside
Canvas_Draw() triggers a (useless) new Canvas_Draw() event immediately after
this one finished.

So these two loops (only one after you executed the first command) spin in
the background drawing the same thing every free moment of your process'
life.

Note that the Draw event for ScrollAreas is triggered once when the program
starts, so Canvas.Refresh() in _new() is superfluous. The proper code would
be:

  Public Sub _new()
    Canvas.ResizeContents(400, 400)
  End

  Public Sub Canvas_Draw()
    If Not func Then Return
    Object.Call(Me, func)
  End

Also note that (at least in more recent Gambas versions) Canvas.Children is
a virtual object so assigning it to a Variant[] variable a will produce an
error.

> 
> You see, if I drop the canvas.refresh() in subroutine 
> canvas_draw() , then only the first tille would be drawn, 
> and all subsequent ADDTILE .... calls will be ignored, 
> i.e. no new tile is drawn.
> If I keep the refresh() then only the last tile is drawn, 
> and the previous tiles are erased.
> 
> However, I want to keep on adding tiles, and keep the 
> previously added tiles.
> 

OK, I guess you don't understand the Draw event concept.

The Draw event of the Canvas object is raised whenever the interpreter deems
it necessary that the contents of Canvas be redrawn. Before each such redraw,
the Canvas is entirely cleared. You can force a Draw event by calling
Canvas.Refresh().

This explains both problems you stated above: if you drop Canvas.Refresh()
in Canvas_Draw(), then there are no new Draw events. How could the Canvas,
or the interpreter for that matter, know that someone entered a new command
and that this command has anything to do with drawing?

Solution: call Canvas.Refresh() when you received the command and want it to
be drawn, like in:

  Public Sub execute()
    Dim commands As String[]

    ' Your stuff as it was...
    Canvas.Refresh()
  End

This gets us immediately to the second problem you mentioned above. You only
see the outcome of your last command because the ScrollArea contents are
fleeting in the sense that whenever a Draw event is raised (like when you
force one with Canvas.Refresh() above), the entire ScrollArea is cleared for
you to redraw the image from scratch.

This behaviour is sometimes useful and always more efficient, memory-wise,
because we need to store only what is visible in the ScrollArea at a time,
not the whole (potentially arbitrarily large) virtual content through which
you can scroll.

Your application, however, needs a more persistent storage for your drawings,
right? I've heard artists used to paint "pictures". Maybe we can also try
that? :-) The basic idea is that your command interpreter does not paint
onto the (fleeting) Canvas directly but on an Image object. There, you can
draw sequentially, on top of what you drew before. Then you call Refresh()
and in Canvas_Draw() you simply put that Image onto the Canvas.

> Also, how do I remove a particular tile added, say as the 
> second one, in the sequence, of say, five tiles, and 
> retain the other four?
> 

What sequence do you mean? :-) To talk about a sequence of commands, you
need to save a sequence of commands in your program, let's say in an array.
You can then loop through that array of commands to create a painting.

To remove some command from the sequence, remove the corresponding element
of the array and recreate the painting from the remaining elements.

If that's too inefficient for your needs (I'd guess the border is around a
few thousand commands here?), you can still paint over that tile with the
background colour. In any case, you need to somehow save the specification
of your tiles if you want to be able to remove them.

I will attach you a patch for your project which implements all the things
I discussed here, so you can play around with it. If your Gambas is recent
enough (I guess it is 3.5.4?), you can apply this patch via the IDE, menu
Project -> Patch -> Apply. [ And sorry, I couldn't help but tidy your code
up a bit on the way -- using my personal definition of "tidying up". ]

There is a demo button, labelled "TEST" for a quick tour of what the code
can do now.

Regards,
Tobi

-- 
"There's an old saying: Don't change anything... ever!" -- Mr. Monk
-------------- next part --------------
A non-text attachment was scrubbed...
Name: archive-0.0.1~Pahu-0.0.1.patch
Type: text/x-diff
Size: 3894 bytes
Desc: not available
URL: <http://lists.gambas-basic.org/pipermail/user/attachments/20141117/fe8eb005/attachment.patch>


More information about the User mailing list