neo-nie / pynsource

Automatically exported from code.google.com/p/pynsource
0 stars 0 forks source link

Screen redraw inaccurate when window is scrolled #5

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
What steps will reproduce the problem?
1. bring up a layout
2. scroll window 
3. mousewheel to change the zoom/scale.

What is the expected output? What do you see instead?

Expect perfect redraw/refresh of the screen.

Instead partially drawn and clipped shapes are drawn.

Original issue reported on code.google.com by abu...@gmail.com on 15 Jul 2012 at 12:46

GoogleCodeExporter commented 8 years ago
Another repo case:

What steps will reproduce the problem?
1. bring up a layout
2. scroll window 
3. press L (the menu command for doing a layout)

What is the expected output? What do you see instead?

Expect perfect redraw/refresh of the screen during animated layout.

Instead partially drawn and clipped shapes are drawn during the layout.

Its only when layout is completely finished, does the rendering correct itself. 
   <---- CLUE?

Original comment by abu...@gmail.com on 15 Jul 2012 at 12:52

GoogleCodeExporter commented 8 years ago
Another repo case:

Scroll the workspace and move a shape so that it overlaps. The resulting move 
and draw will not be properly drawn. Select refresh from the menu and the 
situation corrects itself.

Original comment by abu...@gmail.com on 15 Jul 2012 at 12:53

GoogleCodeExporter commented 8 years ago
THIS MAY OR MAY NOT BE RELEVANT:

Dunn in http://osdir.com/ml/wxpython-users/2010-06/msg00587.html says 

First, a Refresh() by default will erase the background before sending the 
paint event (although setting the BG style or catching the erase event would 
have taken care of that.) The second and probably most visible problem in this 
case is that in your on_motion handler you are not offsetting the ClientDC by 
the scroll offsets, just the position in the buffer that you are drawing the 
line segment at. So when the buffer is flushed out to the client DC it is drawn 
at the physical (0,0), not the virtual (0,0). In other words, the flicker you 
are seeing is coming from drawing the buffer at the wrong position after every 
mouse drag event, and then it immediately being drawn again at the right 
position in the on_paint triggered by the Refresh().

You should be able to fix this by calling PrepareDC on the client DC before 
using it, like this: 

cdc = wx.CLientDC(self)
self.PrepareDC(cdc)
dc = wx.BufferedDC(cdc, self.buffer)

However since you are doing a Refresh or RefreshRect? anyway, there is no need 
to use a client DC here at all, just let the flushing of the buffer to the 
screen be done in on_paint instead: 

dc = wx.BufferedDC(None, self.buffer)

Original comment by abu...@gmail.com on 15 Jul 2012 at 12:55

GoogleCodeExporter commented 8 years ago
Perhaps the error is where I try to be smarter and only do a prepare dc etc. 
style refresh - this needs to more correctly take into account the scrollbars?

Original comment by abu...@gmail.com on 15 Jul 2012 at 12:55

GoogleCodeExporter commented 8 years ago
Latest thoughts: Perhaps do a full on redraw of everything WHEN I detect the 
window is scrolled.  This would be a sort of a workaround, though it would slow 
things down, as a full on redraw is, and looks, slower.

Original comment by abu...@gmail.com on 15 Jul 2012 at 12:56

GoogleCodeExporter commented 8 years ago
Reproduced problem with attached simplified example.  Scroll the window and 
repeatedly tap 'm' to move the nodes.  Note the bad screen redraw. Doesn't 
happen if window is non scrolled.

Original comment by abu...@gmail.com on 24 Jul 2012 at 8:38

Attachments:

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r443.

Initial simplified repro examples.  ogl_scroll1.py demos the problem nicely.

Original comment by abu...@gmail.com on 24 Jul 2012 at 8:49

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r444.

The solution seems to be to use canvas.Refresh instead of the diagram clear + 
redraw combo.  The problem with
  self.GetDiagram().Clear(dc)
  self.GetDiagram().Redraw(dc)
is that the clear() only clears the physical visible area and the scrolled off 
area still has content, so that when you scroll to it you get old rubbish 
there. e.g. if the shape that was moved was in the scrolled off area, you will 
see double - the old position and the new.

Original comment by abu...@gmail.com on 25 Jul 2012 at 8:18

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r445.

Research demos related to draw, refresh and scrolling. Note that 
ogl_redraw_f_logic.py has USEFUL comments as to various drawing techniques and 
their strengths and weaknesses.

Original comment by abu...@gmail.com on 25 Jul 2012 at 8:20

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r447.

Ogl official demo from the big fat demo app as standalone app - with my draw 
code injected in a modular way.  Hit 'm' to move a shape.  Hit 'r' to refresh.  
Hit '1-7' to change redraw/refresh technique.

Original comment by abu...@gmail.com on 25 Jul 2012 at 8:33

GoogleCodeExporter commented 8 years ago
NOTE: shape.move (which does a shape.draw) and diagram.redraw (which just loops 
and calls shape.draw on everything) is all about drawing to the screen, and 
works with a scrolled canvas.  

The problem is one of clearing.  The diagram.Clear() only handles the physical 
area.  shape.Erase() only handles the shape area.

However frame.Refresh and canvas.Refresh seem to clear everything including the 
offscreen scrolled area, and repaints everything cleanly. Yey. But a bit 
flickery and possibly there are smarter ways to do this?

Original comment by abu...@gmail.com on 25 Jul 2012 at 8:36

GoogleCodeExporter commented 8 years ago
Need to work out the scrollbars and canvas sizes etc. next.  See diagram of the 
difference between size and virtualsize.  

Some possibly helpful notes being gathered from googling in 
http://pynsource.googlecode.com/svn/trunk/Research/ogl%20tests%2001/ogl%20notes.
txt

Original comment by abu...@gmail.com on 25 Jul 2012 at 8:40

Attachments:

GoogleCodeExporter commented 8 years ago
This person seems to have had the exact same problem as me in 2008.  His 
question wasn't answered. 
http://wxpython-users.1045709.n5.nabble.com/dc-Clear-is-this-a-bug-or-am-I-doing
-something-wrong-td2368313.html

Original comment by abu...@gmail.com on 25 Jul 2012 at 11:40

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r448.

Created standalone example for the mailing list.
https://groups.google.com/forum/?hl=en&fromgroups#!searchin/wxpython-users/dc.Cl
ear():$20is$20this$20a$20bug$20or$20am$20I$20doing$20something$20wrong?/wxpython
-users/X840K1bA5R4/kf29SKR5X40J

Original comment by abu...@gmail.com on 25 Jul 2012 at 1:11

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r449.

The current redraw breakthrough of

  shape.Move(dc, x, y)  # handles shape.MoveLinks(dc) internally too
  canvas.Refresh()     # or canvas.frame.Refresh()

leaves me with an OUTSTANDING QUESTION: Why isn't an eventual shape.draw() or 
diagram.redraw() needed
anymore. DOES canvas.Refresh() SOMEHOW TRIGGER AN ACTUAL DRAW?

Original comment by abu...@gmail.com on 25 Jul 2012 at 2:01

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r450.

Re Refresh().  Tip: a Refresh() by default will erase the background before 
sending the paint event.

Also re wx.SafeYield() or self.Update() - You need to be yielding or updating 
on a regular basis, so that when your OS/window manager sends repaint messages 
to your app, it can handle them.  
http://stackoverflow.com/questions/10825128/wxpython-how-to-force-ui-refresh
Without this call the nodes don't paint during a "L" layout (edges do!?)

Original comment by abu...@gmail.com on 25 Jul 2012 at 2:13

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r451.

Various experiments confirming that .Refresh() triggers a proper clear over the 
virtualsize shapecanvas and then calls paint which then most likely calls Draw 
on everything.

Also confirmed that redraw_everything() does nothing. What's really causing 
this to repair any smudgy redraw problems, was that a .Refresh() was indirectly 
generated via the frame resize scrollbar hack.

Original comment by abu...@gmail.com on 26 Jul 2012 at 8:07

GoogleCodeExporter commented 8 years ago
redraw_everything() removed. :-) See commentary in issue 12 r455

Original comment by abu...@gmail.com on 28 Jul 2012 at 5:27

Attachments:

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r456.

stage2 renamed build_view and the needless rendering it does removed, as later 
stateofthenation will do it.  This makes build_view nice and clean - and it 
just does one thing, build a view (shapes etc) from a model.  Used by a file 
open and file import - which makes sense as we are building a new view from 
persisted or newly built model.

Original comment by abu...@gmail.com on 28 Jul 2012 at 6:10

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r457.

Made another demo for wxpython mailing list discussion re functionality of 
canvas.Clear(dc)

Ironically this discussion seems to be leading back to the topic of the 
original poster's subject line "dc.Clear(): is this a bug or am I doing 
something wrong?".  I think there may be a bug in .Clear(dc) not taking into 
account of the scroll, even though canvas.PrepareDC(dc) has been called.

Original comment by abu...@gmail.com on 29 Jul 2012 at 4:27

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r458.

Resurrected old clear/redraw technique in my research code.  Made mailing list 
post:

On Tue, Jul 31, 2012 at 2:40 AM, Robin Dunn <robin@alldunn.com> wrote:   
Ok, I see it too.  It's not quite expected but I'm not sure it's a bug as from 
some perspectives it is probably the correct thing to do... Anyway, a simple 
work around is to simply not call PrepareDC in that case.  Then it will always 
be the physical window area that is cleared.

Thanks - a most fascinating workaround, which opens up all sorts of 
possibilities again.  Now I have a way to Clear the visible physical window 
area regardless of scroll.  This now means that I can rework my old code and it 
now works e.g.

dc = wx.ClientDC(canvas)
canvas.GetDiagram().Clear(dc)  # do not prepare the dc !!

canvas.PrepareDC(dc)
shape.Move(dc, x, y)
canvas.GetDiagram().Redraw(dc)

which is not quite as pithy as the newer .Refresh based solution 

shape.Move(dc, x, y, display=False)
canvas.Refresh()

but nevertheless is now a viable option.

Yeah its curious that you must call PrepareDC before drawing operations in 
order to take into account scrolling offsets, but must not call PrepareDC 
before clearing operations.  It does seem inconsistent.

But now that I think about it, it sort of makes sense.  I mean, to clear the 
current physical screen is a no brainer - its just sitting there in the frame 
all the time - from a hardware point of view. On the other hand to clear just 
the topmost window area that has possibly scrolled out of view - that's the 
thing that requires calculation, and thus which requires a call to PrepareDC - 
and thus you do get the clever ability to only clear that top windowful of area 
and if e.g. there is a slight scroll in place, the Clear will only clear a 
portion of the visible screen.  As to why anybody would ever want to just clear 
the top window-ful of a scrolled canvas - I don't know.  But I think I'm at 
peace with how this all works now. Thanks!

Original comment by abu...@gmail.com on 31 Jul 2012 at 8:46

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r459.

Created a Move2 method to inject into shape.  Refactored the coord_utils.py
stateofthenation() simplified to use Move2() followed by a .Refresh().

Original comment by abu...@gmail.com on 5 Aug 2012 at 12:46

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r460.

stage2 removed.  All stage2() did was overlap removal.  So a new shape canvas 
method remove_overlaps() created which people can call explicitly.  And then a 
subsequent stateofthenation can also be called explicityly.  This simplifies 
everything.

So now we basically have
.remove_overlaps
.stateofthenation
and when doing a CmdLayout or CmdFileImportBase.execute
.layout_and_position_shapes

Original comment by abu...@gmail.com on 5 Aug 2012 at 1:28

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r461.

Changing shape canvas size more accurately.
Now getting a bug when zoom scale with scroll wheel to min size and then scale 
up again - this never happened before. Prevent by holding SHIFT down whilst 
scrolling or by doing layout first.
Also need to expand canvas when move shapes out of bounds.
Also need to handle frame resizing better.

Original comment by abu...@gmail.com on 5 Aug 2012 at 2:01

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r462.

Fixed canvas resizing problems by ensuring word size passed in is correct 
during boot up phase.

Delayed creation of umlwin sub objects till frame is up and running and has the 
right size. New method InitSizeAndObjs created which is now called when that is 
all done.

Tip2: No need to call self.resize_virtual_canvas_tofit() every time the frame 
is resized since the  bounds of the shapes area doesn't change when resizing a 
frame, so you would end up calling it with the same values again and again.

Original comment by abu...@gmail.com on 6 Aug 2012 at 1:17

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r463.

Expand canvas when move shapes out of bounds.

Original comment by abu...@gmail.com on 6 Aug 2012 at 3:22

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r464.

Changed scroll to be normal scroll. CTRL scroll is not resize scale.  Scale 
logic now always preserves layout.  

Virtualsize canvas trimming better.  Responds to frame resizes.  Responds to 
node movement out of bounds.

Trick is to make the canvas virtual size == bounds of all the shapes.
You change virtual size by calling SetScrollbars()

SCROLLBAR BEHAVIOUR TIP
As you resize the frame, the canvas virtual size stays the same as what
you set it to using SetScrollbars() - up until the point at which the
scrollbars exhaust themselves and disappear - which means the that you
have finally made frame == virtualsize. After which point virtual size
auto-grows as the frame continues to grow. If you shrink the frame
again, then virtualsize will reduce until it hits the old original value
of virtualsize set by SetScrollbars(). If you continue to reduce the
frame then the scrollbars appear, but the virtualsize remains the same.

ALGORITHM:
IF its a programmatic change of bounds e.g. via stateofthenation:
    set canvas virtualsize to match bounds taking account % leeway when compacting
ELSE is_frame_resize i.e. its a call from resize of frame event:
    if frame > bounds then must be in virtualsize autogrow mode 
                        and we should not attempt to alter virtualsize
    else: set canvas virtualsize to match bounds, not taking into account any % leeway.

Note: repeated calls to frame resize shouldn't be a problem because
canvas virtualsize should == bounds after the first time its done.
Making frame smaller won't affect bounds or canvas virtualsize. Making
frame bigger ditto. Making frame bigger than bounds will result in
nothing happening cos we do nothing in "autogrow mode".

Rule: virtual size must be >= bounds

Rule: virtual size must be no more than n% bigger than the bounds else
virtualsize will be trimmed/compacted down. The purpose of this is to
relax the rules and not have it so strict - after all it doesn't hurt to
have a slightly larger workspace - better than a trim workspace
jumping/flickering around all the time. Plus if you manually drag resize
the frame it will trim perfectly.

Original comment by abu...@gmail.com on 6 Aug 2012 at 11:33

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r465.

Switched to a time based approach to calling 
resize_virtual_canvas_tofit_bounds() from FRAME resize event.  Seems to work 
better - user experience wise.

Original comment by abu...@gmail.com on 6 Aug 2012 at 1:20

GoogleCodeExporter commented 8 years ago
A bit of a post Mortem on all this resizing of virtual canvas to match the 
bounds of all shapes:

You can theoretically resize virtual canvas using setscrollbars() anytime - 
even when in my so called 'auto virtual size mode' - its not going to hurt 
because you are setting it accurately to the size of the shapes bounds.

The problem is how do you avoid calling this expensive (and screen draw flashy) 
setscrollbars() operation unnecessarily?  In particular when it's being called 
repeatedly from the frame resize event. 

I thought that detecting my so called 'auto virtual size mode' would be a time 
NOT to do it.  I think in the end this turned out to be a not so useful 
optimization. Sure I got less calls to setscrollbars() but I didn't get the 
functionality of setscrollbars() at all in that 'mode'.   The 'time' based 
approach on the other hand let one through every 5 seconds whilst resizing.  A 
bit hacky but works and delivers functionality. 

Note that the 'resize virtual canvas to fit' method either expands the virtual 
size to meet the needs of the bounds of all shapes, or it shrinks the virtual 
size to the bounds.  

If its expanding the virtual size, then once that is done, the virtual size 
should be OK for the foreseeable future, it will either match the bounds or be 
bigger than the bounds, due to a frame resize stretch auto mode thing - thus 
the need to want to expand the virtual size will be quenched.  

If on the other hand its compacting the virtual size then once that is done, we 
are NOT OK for the future because whilst the virtual size may have successfully 
been set, next time we read the value of canvas virtual size we may get the 
temporarily stretched value, due to the frame resize stretch auto mode thing - 
hence the algorithm will not be quenched and another attempt will be made again 
and again to compact - esp. when this is being called from a frame resize 
event. Performance and flashy city ensues. So actually there sort of IS a point 
to not doing any compacting whilst in the funny stretched mode where we can't 
rely on virtual size shrinking when we ask it to shrink - because the frame is 
bigger than the bounds and in a sense the virtual size cannot physically shrink 
- its constrained by the size of the real frame and cannot get smaller when we 
ask it to.

Original comment by abu...@gmail.com on 6 Aug 2012 at 2:33

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r466.

Moved the virtual canvas resizing to a new non time based algorithm.  Basically 
the solution is to remember the overall shapes bounds whenever we set the 
virtual canvas size to = bounds.  That way we only do the painful flashy 
resizing operation when bounds have truly changed.  

This (I think) gets us around the previously mentioned issue that virtualsize 
may be a temporarily stretched value, due to the frame resize stretch auto mode 
thing.

Original comment by abu...@gmail.com on 7 Aug 2012 at 2:39

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r467.

Added some caching to GetBoundsAllShapes() calculation.  Some refactoring.  We 
now use a allshapes_bounds and an allshapes_bounds_dirty flag.

Also set virtualsize using SetScrollBars() call with extra parameters which 
avoid the flashing! :-)

Original comment by abu...@gmail.com on 7 Aug 2012 at 5:37

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r468.

Refactored canvas virtual size resizing logic, got rid of dirty flag and now 
its a parameter to resize_virtual_canvas_tofit_bounds(). Now only have
self.allshapes_bounds_cached
self.allshapes_bounds_last

Nice debugging statements on in this version.

Original comment by abu...@gmail.com on 7 Aug 2012 at 8:33

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r469.

Moved canvas resizing and associated functionality into separate class in 
canvas_resizer.py
Class CanvasResizer

Original comment by abu...@gmail.com on 7 Aug 2012 at 11:54

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r470.

Refactoring and cleanup of canvas_resizer.py
Debugging info still present.

Original comment by abu...@gmail.com on 7 Aug 2012 at 1:04

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r471.

Turned off most debugging in canvas_resizer.py

Still need to figure out why canvas resizing sometimes happens repeatedly 
during a layout. e.g.
Setting virtual size to 970,1022
Setting virtual size to 970,1022

Original comment by abu...@gmail.com on 7 Aug 2012 at 1:14

GoogleCodeExporter commented 8 years ago
This issue was updated by revision r472.

Optimised canvas_resizer.py to reduce number of virtual canvas resizings.

Also note the use of the new whoscalling2() diagnostic which dumps the call 
stack in a nice way.

Original comment by abu...@gmail.com on 8 Aug 2012 at 2:21

GoogleCodeExporter commented 8 years ago
At this point the screen is drawing correctly and also the scrollbars (and 
virtual canvas size) is resizing nicely.  

Any leeway in canvas size is due to the 40% leeway I have in place to prevent 
overly aggressive compacting of the virtual canvas size. A manual resize of the 
frame will always correct this.

Original comment by abu...@gmail.com on 8 Aug 2012 at 2:28