Search code examples
erlangwxwidgets

animation with wxWidgets in erlang in linux and window environment


I made a small program using wxWidget in erlang, and succeed to have it working fine under windows XP. This program animates a bitmap based on internal events and refresh it using a time base (no user event). My problem is that the code does not works properly under Linux Ubuntu (11.04).

the bitmap panel is initialized as follow:

fill_window(W,H,Frame,Z) ->
    MainSz = wxBoxSizer:new(?wxVERTICAL),
    Board = wxPanel:new(Frame),
    wxWindow:setSizer(Board,MainSz),

    PanSz = wxStaticBoxSizer:new(?wxVERTICAL, Board,[{label, "Le Monde"}]),
    KeySz = wxStaticBoxSizer:new(?wxVERTICAL, Board,[{label, "Controle"}]),
    KeyGrSz = wxGridSizer:new(2,3,2,2),

    Panel = wxPanel:new(Board, [{size,{W,H}}]),  

    wxSizer:addSpacer(MainSz,2),
    wxSizer:add(PanSz, Panel, [{flag, ?wxALL bor ?wxEXPAND}]), 
    wxSizer:add(MainSz, PanSz, [{proportion, 0}, {border, 4}, {flag, ?wxALL bor ?wxEXPAND}]), 
    wxSizer:addSpacer(MainSz,3),
    wxPanel:connect(Panel, paint, [callback]),
    wxPanel:connect(Panel, left_up, []),
    wxPanel:connect(Panel, right_down, []),
    wxPanel:connect(Panel, middle_down, []),

    wxSizer:addSpacer(MainSz,3),
    wxSizer:add(KeySz, KeyGrSz, [{flag, ?wxALL bor ?wxEXPAND}]), 
    [wxSizer:add(KeyGrSz, wxButton:new(Board,Id,[{label,Txt},{size,{80,20}}]), [{flag, ?wxALL}]) || {Id,Txt} <- ?BLIST],
    wxSizer:add(MainSz, KeySz, [{proportion, 0}, {border, 4}, {flag, ?wxALL}]), 
    wxSizer:addSpacer(MainSz,2),
    wxWindow:connect(Board, command_button_clicked),
    wxSizer:layout(MainSz),

    ClientDC = wxClientDC:new(Panel),
    Bitmap = wxBitmap:new(W,H),
    PM = wxPen:new(color(dead), [{width, 1}]),
    PV = wxPen:new(color(live), [{width, 1}]),
    BG  = wxBrush:new(color(background)),
    BM  = wxBrush:new(color(dead)),
    BV  = wxBrush:new(color(live)),

%% initialisation de l'image de départ
    MemoryDC = wxMemoryDC:new(Bitmap),
    wxDC:setBackground(MemoryDC,BG),
    wxDC:setBrush(MemoryDC,BG),
    PenTemp = wxPen:new(color(background), [{width, 1}]),
    wxDC:setPen(MemoryDC,PenTemp),
    wxDC:drawRectangle(MemoryDC, {0,0}, {W,H}),
    wxPen:destroy(PenTemp),
    wxDC:setPen(MemoryDC,PM),
    wxDC:setBrush(MemoryDC,BM),
    [cell(MemoryDC, {X,Y},Z) || X <- lists:seq(0,W-1), Y <- lists:seq(0,H-1)],
    redraw(ClientDC,Bitmap,W,H),

    wxWindow:refresh(Panel),    
    wxWindow:show(Frame),
    {Frame, Panel, Bitmap, ClientDC, PV, PM, BV, BM}.

color(live) -> {255,255,255};
color(dead) -> {80,80,80};
color(background) -> {0,0,40}.

redraw(DC, Bitmap, W, H) ->
    MemoryDC = wxMemoryDC:new(Bitmap),
    wxMemoryDC:destroy(MemoryDC).

The process receives some messages from other processes telling it to draw a cell. The window is suppose to automatically refresh the screen or store the change in a list depending on the value of an internal state (refreshtoggle). it receives also some "refresh" messages telling it to execute the pending list of image modification and refresh the screen. I have done this using a synchronous event which update a list of pending modification and execute it when it is require by calling a refresh function:

handle_call(refresh, _From, #state{cellbuf=Cb,clientDC=ClientDC, bitmap=Bitmap, w=W, h=H,zoom=Z, penlive=PV, pendead=PM, brushlive=BV, brushdead=BM,panel=P} = State) ->
    do_refresh(Cb,ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P),
    {reply, ok, State#state{cellbuf=[]}};
handle_call({setcell,Etat,Cell}, _From, #state{cellbuf=Cb,clientDC=ClientDC, bitmap=Bitmap, w=W, h=H,zoom=Z, penlive=PV, pendead=PM, brushlive=BV, brushdead=BM,panel=P,refreshtoggle=true} = State) ->
    do_refresh([{Etat,Cell}|Cb],ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P),
    {reply, ok, State#state{cellbuf=[]}};
handle_call({setcell,Etat,Cell}, _From, #state{cellbuf=Cb} = State) ->
    {reply, ok, State#state{cellbuf=[{Etat,Cell}|Cb]}};
handle_call(What, _From, State) ->
    {stop, {call, What}, State}.

do_refresh(Cb,ClientDC,Bitmap,W,H,Z,PV,PM,BV,BM,P) ->
    wx:batch(fun ()  ->
        Cb1 = lists:reverse(Cb),
        MemoryDC = wxMemoryDC:new(Bitmap),
        [cell(MemoryDC,PV,BV,PM,BM,State,Cell,Z) || {State,Cell} <- Cb1],
        wxDC:blit(ClientDC, {0,0},{W,H},MemoryDC, {0,0}),
        wxMemoryDC:destroy(MemoryDC)
    end).

In windows, this does not refresh the screen, I had to add a synchronous paint event to get the screen updated:

handle_sync_event(#wx{event = #wxPaint{}}, _wxObj, #state{panel=Panel, bitmap=Bitmap, w=W, h=H}) ->
    DC = wxPaintDC:new(Panel),  
    wxPaintDC:destroy(DC),
    ok.

This function does nothing but create a PaintDC from the Panel and destroy it immediately. It looks a bit magic but it is what I have understood from the wxWidget documentation (The user window can be hidden or moved but never resized, so I have nothing to repaint really)

As I said this works very fine on windows XP, the animation is smooth and I have no flickering, but with linux, the screen is never updated. I can see that the bitmap is updated, because if I pause the animation, reduce the window and reopen it, the new image is there.

What is the right method to achieve this kind of animation in a way that avoids flickering and works on different platform?

[edit 1]

here is an extract of the PaintDC documentation, it is said that even if I don't use it I must build and destroy a PaintDC during the onPaint event. I need this event because the user may hide, minimize or move the window (i understood that these event will create an onPaint event, I will check this tonight).

Detailed Description

A wxPaintDC must be constructed if an application wishes to paint on the client area of a window from within an EVT_PAINT() event handler.

This should normally be constructed as a temporary stack object; don't store a wxPaintDC object. If you have an EVT_PAINT() handler, you must create a wxPaintDC object within it even if you don't actually use it.

Using wxPaintDC within your EVT_PAINT() handler is important because it automatically sets the clipping area to the damaged area of the window. Attempts to draw outside this area do not appear.

To draw on a window from outside your EVT_PAINT() handler, construct a wxClientDC object.

To draw on the whole window including decorations, construct a wxWindowDC object (Windows only).

A wxPaintDC object is initialized to use the same font and colours as the window it is associated with.

Nevertheless, I will try to re-insert a refresh() in my code, If i remember correctly I removed it because it was useless.


Solution

  • I don't understand the Erlang code well, but you definitely don't refresh the screen by creating and destroying a wxPaintDC. Use Refresh() to do it instead.