How to remove trailing effect when drawing on a panel? Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

I think there is just a "dirty" flag missing.

Code: Select all

    if(!m_WaveformBitmap.IsOk()
       // || m_WaveformBitmap.IsNull()
       || m_WaveformBitmap.GetWidth() != size.x
       || m_WaveformBitmap.GetHeight() != size.y
       || m_dirty == true)                        <============ new
    {
        wxLogDebug("Updating waveform bitmap..");
        /// more code
        UpdateWaveformBitmap();
        m_dirty = false;                  <=============== new
Whenever you select a new file and the waveform data changes (or zoom factor or anything like it), set the "dirty" flag to true and call Refresh() so that the display gets updated.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Sat Jul 31, 2021 3:36 pm I think there is just a "dirty" flag missing.

Code: Select all

    if(!m_WaveformBitmap.IsOk()
       // || m_WaveformBitmap.IsNull()
       || m_WaveformBitmap.GetWidth() != size.x
       || m_WaveformBitmap.GetHeight() != size.y
       || m_dirty == true)                        <============ new
    {
        wxLogDebug("Updating waveform bitmap..");
        /// more code
        UpdateWaveformBitmap();
        m_dirty = false;                  <=============== new
Whenever you select a new file and the waveform data changes (or zoom factor or anything like it), set the "dirty" flag to true and call Refresh() so that the display gets updated.
I think it worked,
Image

I added this where I play the sample, there are 2 ways to play a sample, by clicking a row in wxDVLC if autoplay is on, or by clicking the play button, this is play button handler,

Code: Select all

void MainFrame::OnClickPlay(wxCommandEvent& event)
{
    bStopped = false;

    int selected_row = m_Library->GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selection = m_Library->GetTextValue(selected_row, 1);

    wxString sample_path = GetFilenamePathAndExtension(selection).Path;

    m_MediaCtrl->Load(sample_path);

    PushStatusText(wxString::Format(_("Now playing: %s"), selection), 1);

    bBitmapDirty = true;            // <--------------
    m_TopWaveformPanel->Refresh();        // <------------

    m_MediaCtrl->Play();

    m_Timer->Start(100, wxTIMER_CONTINUOUS);
}
Added the same in the wxDVLC handler.

And here is the OnPaint()

Code: Select all

void MainFrame::OnPaint(wxPaintEvent& event)
{
    wxPaintDC dc(m_TopWaveformPanel);

    const wxSize& size = m_TopWaveformPanel->GetClientSize();

    if(!m_WaveformBitmap.IsOk()
       // || m_WaveformBitmap.IsNull()
       || m_WaveformBitmap.GetWidth() != size.x
       || m_WaveformBitmap.GetHeight() != size.y
       || bBitmapDirty)         // <-----------------
    {
        wxLogDebug("Updating waveform bitmap..");
#if 1
        m_WaveformBitmap = wxBitmap(wxImage(size.x, size.y), 32);
#else
        m_WaveformBitmap.Create(size.x, size.y, 32);
#endif
        UpdateWaveformBitmap();
        bBitmapDirty = false;            // <----------------
    }
    // else
        // wxLogDebug("Cannot update waveform bitmap..");

    dc.DrawBitmap(m_WaveformBitmap, 0, 0, false);
    // m_WaveformBitmap.SaveFile("waveform.png", wxBITMAP_TYPE_PNG);

    RenderPlayhead(dc);
}
It now updates the bitmap, when I click on a different item. It also works with wx3.0.5. I will move to wx3.1.5, the only problem is, no distribution of Linux ships wx3.1.5 in their repositories. So I either have to ship the wx source with my application, as git submodule perhaps.

To set the background color initially, should add the SetBackgroundColour() for the panel, in the CallAfter() perhaps?
Last edited by apoorv569 on Sat Jul 31, 2021 4:27 pm, edited 1 time in total.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

To set the background color initially, should add the SetBackgroundColour() for the panel, in the CallAfter() perhaps?
Shouldn't you see the bitmap filled with white by UpdateWaveformBitmap()?
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Sat Jul 31, 2021 4:27 pm
To set the background color initially, should add the SetBackgroundColour() for the panel, in the CallAfter() perhaps?
Shouldn't you see the bitmap filled with white by UpdateWaveformBitmap()?
Yes, it just looks weird, that the app open with black panel, and when I click on a sample it changes to white, with whatever color the waveform is. Maybe I just clear the bitmap with black color only?
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

apoorv569 wrote: Sat Jul 31, 2021 4:35 pm Yes, it just looks weird, that the app open with black panel, and when I click on a sample it changes to white, with whatever color the waveform is. Maybe I just clear the bitmap with black color only?
I don't quite understand why you don't see the white background right away. As soon as the panel gets a paint event it should create a new bitmap, call UpdateWaveformBitmap(), which then fills the background with white, but doesn't draw any waveform, because there isn't any.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Sat Jul 31, 2021 4:46 pm
apoorv569 wrote: Sat Jul 31, 2021 4:35 pm Yes, it just looks weird, that the app open with black panel, and when I click on a sample it changes to white, with whatever color the waveform is. Maybe I just clear the bitmap with black color only?
I don't quite understand why you don't see the white background right away. As soon as the panel gets a paint event it should create a new bitmap, call UpdateWaveformBitmap(), which then fills the background with white, but doesn't draw any waveform, because there isn't any.
Yea, for now I'll clear with black color only, to keep the look consistent. But thanks a lot @doublemax, you really helped me a lot. I really appreciate it. :D
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

You're welcome. I'm happy that it finally works :)
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Thu Jul 29, 2021 10:41 am
What about the ability to move the drawn line with mouse, to seek through the audio sample? Like how a wxSlider would work.
Possible, but you'll have to code the whole functionality.

- catch wxEVT_MOTION event on the panel
- if the mouse is near the line, change cursor to indicate that the user can grab it
- on mouse-down and if mouse is near the line, capture mouse ( wxWindow::CaptureMouse )
- while mouse is captured, move the position of the line to the position of the mouse pointer in the wxEVT_MOTION handler
- on mouse-up release mouse capture
Sorry to bother again, but about moving the playhead. I made these 3 functions, for motion, mouse down and mouse up.

Code: Select all

    m_TopWaveformPanel->Bind(wxEVT_MOTION, &MainFrame::OnHoverPlayhead, this);
    m_TopWaveformPanel->Bind(wxEVT_LEFT_DOWN, &MainFrame::OnGrabPlayhead, this);
    m_TopWaveformPanel->Bind(wxEVT_LEFT_UP, &MainFrame::OnReleasePlayhead, this);

Code: Select all

void MainFrame::OnHoverPlayhead(wxMouseEvent& event)
{
    int selected_row = m_Library->GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selected = m_Library->GetTextValue(selected_row, 1);
    std::string path = GetFilenamePathAndExtension(selected).Path.ToStdString();

    Tags tags(path);

    int length = tags.GetAudioInfo().length;

    double position = m_MediaCtrl->Tell();

    int panel_width = m_TopWaveformPanel->GetSize().GetWidth();
    double line_pos = panel_width * (position / length);

    wxPoint pos = event.GetPosition();

    if (pos.x >= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5)
    {
        this->SetCursor(wxCursor(wxCURSOR_HAND));
        wxLogDebug("Cursor on playhead..");
    }
    else
    {
        SetCursor(wxCursor(wxCURSOR_ARROW));
    }

    if (wxWindow::HasCapture())
    {
        // Move the playhead to mouse position 
        m_MediaCtrl->Seek(pos.x, wxFromCurrent);
    }

    wxLogDebug("Mouse at: '(%d, %d)'", pos.x, pos.y);
}

void MainFrame::OnGrabPlayhead(wxMouseEvent& event)
{
    int selected_row = m_Library->GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selected = m_Library->GetTextValue(selected_row, 1);
    std::string path = GetFilenamePathAndExtension(selected).Path.ToStdString();

    Tags tags(path);

    int length = tags.GetAudioInfo().length;

    double position = m_MediaCtrl->Tell();

    int panel_width = m_TopWaveformPanel->GetSize().GetWidth();
    double line_pos = panel_width * (position / length);

    wxPoint pos = event.GetPosition();

    if (pos.x >= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5)
    {
        wxWindow::CaptureMouse();
        wxLogDebug("Mouse Captured playhead..");
    }
}

void MainFrame::OnReleasePlayhead(wxMouseEvent& event)
{
    wxWindow::ReleaseMouse();
    wxLogDebug("Mouse released playhead..");
}
When I hover over the arrow on the playhead at the top, it does detect I have mouse over it, but the cursor doesn't change, no matter which cursor I choose.
Image

But when I do click on it, it does capture my mouse, but upon releasing the button, it doesn't release the mouse capture. So I'm stuck with my mouse captured by the application, and I have to manually close the application by processor manager with keyboard.

Also for moving the playhead(line) to mouse position do I have clear the panel, and then draw the line again? But this will also clear the waveform.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

When I hover over the arrow on the playhead at the top, it does detect I have mouse over it, but the cursor doesn't change, no matter which cursor I choose.
Just a guess, try m_TopWaveformPanel->SetCursor(...)
But when I do click on it, it does capture my mouse, but upon releasing the button, it doesn't release the mouse capture.
When you capture the mouse in the mainframe, all mouse events will be redirected there, but your wxEVT_LEFT_UP handler is bound to the m_TopWaveformPanel. It's a general problem that your event handlers for m_TopWaveformPanel are all in the mainframe. This may be the time to derive your own class for m_TopWaveformPanel and move all event handlers there. This will make the code cleaner and more future proof.
Also for moving the playhead(line) to mouse position do I have clear the panel, and then draw the line again? But this will also clear the waveform.
Just call m_TopWaveformPanel->Refresh().
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

When you capture the mouse in the mainframe, all mouse events will be redirected there, but your wxEVT_LEFT_UP handler is bound to the m_TopWaveformPanel. It's a general problem that your event handlers for m_TopWaveformPanel are all in the mainframe. This may be the time to derive your own class for m_TopWaveformPanel and move all event handlers there. This will make the code cleaner and more future proof.
I see.

After spending an hour or something this is what I got,

Code: Select all

WaveformViewer::WaveformViewer(wxWindow* parentFrame, wxWindow* window, bool dirtyBitmap, wxDataViewListCtrl& library, wxMediaCtrl& mediaCtrl,
                               wxTimer& timer, wxInfoBar& infoBar, const std::string& configFilepath, const std::string& databaseFilepath)
    : wxPanel(window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE),
      m_ParentFrame(parentFrame), m_Window(window), bBitmapDirty(dirtyBitmap), m_Library(library), m_MediaCtrl(mediaCtrl), m_Timer(timer),
      m_InfoBar(infoBar), m_ConfigFilepath(configFilepath), m_DatabaseFilepath(databaseFilepath)
{
    Bind(wxEVT_PAINT, &WaveformViewer::OnPaint, this);
    Bind(wxEVT_MOTION, &WaveformViewer::OnHoverPlayhead, this);
    Bind(wxEVT_LEFT_DOWN, &WaveformViewer::OnGrabPlayhead, this);
    Bind(wxEVT_LEFT_UP, &WaveformViewer::OnReleasePlayhead, this);
}
All the functions are still the same as before.

And in the MainFrame class,

Code: Select all

    m_TopWaveformPanel = new WaveformViewer(this, m_TopPanel, IsBitmapDirty(), *m_Library, *m_MediaCtrl, *m_Timer, *m_InfoBar, m_ConfigFilepath, m_DatabaseFilepath);
At first I had the same problem with waveform only showing when frame or splitter is resized, so in WaveformViewer class I made a variable bool& BitmapDirty, and in MainFrame class I made this function,

Code: Select all

inline bool IsBitmapDirty() { return bBitmapDirty; }
which is initialized as false.

It seems to be working, however it looks like it is constantly updating something. App almost feels laggy.
Image
Just a guess, try m_TopWaveformPanel->SetCursor(...)
Cursor seems to change now, so one problem fixed.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

It seems to be working, however it looks like it is constantly updating something. App almost feels laggy.
Check if the dirty flag is set and reset correctly.
At first I had the same problem with waveform only showing when frame or splitter is resized, so in WaveformViewer class I made a variable bool& BitmapDirty, and in MainFrame class I made this function,

Code: Select all

inline bool IsBitmapDirty() { return bBitmapDirty; }
You either worded that badly, or this is the problem. There should be only one bBitmapDirty flag, in the WaveformViewer class. And a method WaveformViewer::SetBitmapDirty() to set it from the outside.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Sat Jul 31, 2021 8:58 pm
It seems to be working, however it looks like it is constantly updating something. App almost feels laggy.
Check if the dirty flag is set and reset correctly.
At first I had the same problem with waveform only showing when frame or splitter is resized, so in WaveformViewer class I made a variable bool& BitmapDirty, and in MainFrame class I made this function,

Code: Select all

inline bool IsBitmapDirty() { return bBitmapDirty; }
You either worded that badly, or this is the problem. There should be only one bBitmapDirty flag, in the WaveformViewer class. And a method WaveformViewer::SetBitmapDirty() to set it from the outside.
Yup, that was the problem, I changed it before I read this post :D
Made these 2 functions,

Code: Select all

    public:
        inline bool IsBitmapDirty() { return bBitmapDirty; }
        inline void SetBitmapDirty(bool dirty) { bBitmapDirty = dirty; }
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

doublemax wrote: Sat Jul 31, 2021 6:44 pm
When I hover over the arrow on the playhead at the top, it does detect I have mouse over it, but the cursor doesn't change, no matter which cursor I choose.
Just a guess, try m_TopWaveformPanel->SetCursor(...)
But when I do click on it, it does capture my mouse, but upon releasing the button, it doesn't release the mouse capture.
When you capture the mouse in the mainframe, all mouse events will be redirected there, but your wxEVT_LEFT_UP handler is bound to the m_TopWaveformPanel. It's a general problem that your event handlers for m_TopWaveformPanel are all in the mainframe. This may be the time to derive your own class for m_TopWaveformPanel and move all event handlers there. This will make the code cleaner and more future proof.
Also for moving the playhead(line) to mouse position do I have clear the panel, and then draw the line again? But this will also clear the waveform.
Just call m_TopWaveformPanel->Refresh().
So the cursor does change, when I hover over the triangle on the position indicator, but I also noticed it thinks the entire top part of the panel where the triangle is, where it needs to change cursor. Though I have a condition, which is not precise, but it should work. Here is the motion event handler,

Code: Select all

void WaveformViewer::OnHoverPlayhead(wxMouseEvent& event)
{
    Database db(m_InfoBar);

    int selected_row = m_Library.GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selected = m_Library.GetTextValue(selected_row, 1);
    std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString());

    Tags tags(path);

    int length = tags.GetAudioInfo().length;

    double position = m_MediaCtrl.Tell();

    int panel_width = this->GetSize().GetWidth();
    double line_pos = panel_width * (position / length);

    wxPoint pos = event.GetPosition();

    if (pos.x >= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5)
    {
        SetCursor(wxCursor(wxCURSOR_HAND));
        wxLogDebug("Cursor on playhead..");
    }

    if (wxWindow::HasCapture())
    {
        wxLogDebug("Has capture");

        if (event.LeftUp())
        {
            wxLogDebug("Left button up");
            m_MediaCtrl.Seek(pos.x, wxFromCurrent);
            m_MediaCtrl.Play();
        }
    }

    wxLogDebug("Mouse at: '(%d, %d)'", pos.x, pos.y);
}
Also it doesn't change cursor, when I click and hold on the triangle, indicating the mouse is clicked, and the indicator is draggable now. Here is left down event handler,

Code: Select all

void WaveformViewer::OnGrabPlayhead(wxMouseEvent& event)
{
    Database db(m_InfoBar);
    int selected_row = m_Library.GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selected = m_Library.GetTextValue(selected_row, 1);
    std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString());

    Tags tags(path);

    int length = tags.GetAudioInfo().length;

    double position = m_MediaCtrl.Tell();

    int panel_width = this->GetSize().GetWidth();
    double line_pos = panel_width * (position / length);

    wxPoint pos = event.GetPosition();

    if (pos.x <= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5)
    {
        wxWindow::CaptureMouse();

        if (!SetCursor(wxCURSOR_CLOSED_HAND))
            wxLogDebug("Cannot set cursor..");

        wxLogDebug("Mouse Captured playhead..");
    }
}
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to remove trailing effect when drawing on a panel?

Post by doublemax »

Code: Select all

if (pos.x <= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5)
In the x-part, the "line_pos - line_pos) cancel out, that can't be right.
In the y-part, i don't understand why you're comparing with line_pos, which seems to be a coordinate in x-direction.

You should double check the whole expression.

Shouldn't this be enough?

Code: Select all

If (abs(pos.x - line_pos) <= 5 && pos.y <= 5)
BTW:

Code: Select all

m_MediaCtrl.Seek(pos.x, wxFromCurrent);
You probably need to convert pos.x into ms here.
Use the source, Luke!
apoorv569
Super wx Problem Solver
Super wx Problem Solver
Posts: 426
Joined: Tue Oct 20, 2020 3:35 pm

Re: How to remove trailing effect when drawing on a panel?

Post by apoorv569 »

Shouldn't this be enough?

Code: Select all

If (abs(pos.x - line_pos) <= 5 && pos.y <= 5)
This seems to work, but sometimes it gets stuck with hand cursor, after I first hover over the triangle, and keeps showing hand cursor after that, when I enter the panel, sometimes its fine.

Also in motion event handler, this is not getting evaluated,

Code: Select all

    if (wxWindow::HasCapture())
    {
        wxLogDebug("Has capture");
        if (event.LeftUp())
        {
            wxLogDebug("Left button up");
            m_MediaCtrl.Seek(line_pos, wxFromCurrent);
            m_MediaCtrl.Play();
        }
    }
It doesn't detect that mouse button has been lifted up. It straight away calls left up handler. Should I move this in left handler instead? Also cursor doesn't change to closed hand, when the left button is down.
BTW:

Code: Select all

m_MediaCtrl.Seek(pos.x, wxFromCurrent);
You probably need to convert pos.x into ms here.
Yes, I noticed that pos.x is only showing up to the panel width, while line_pos is calculated according to the sample length. Maybe I just pass line_pos here?


EDIT: Okay I have some progress,
I changed the left up handler to this,

Code: Select all

void WaveformViewer::OnReleasePlayhead(wxMouseEvent& event)
{
    Database db(m_InfoBar);

    int selected_row = m_Library.GetSelectedRow();

    if (selected_row < 0)
        return;

    wxString selected = m_Library.GetTextValue(selected_row, 1);
    std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString());

    Tags tags(path);

    int length = tags.GetAudioInfo().length;

    double position = m_MediaCtrl.Tell();

    int panel_width = this->GetSize().GetWidth();
    double line_pos = panel_width * (position / length);

    wxPoint pos = event.GetPosition();

    wxWindow::ReleaseMouse();
    SetCursor(wxCURSOR_ARROW);
    wxLogDebug("Mouse released playhead..");

    m_MediaCtrl.Seek(pos.x, wxFromCurrent);
    // m_MediaCtrl.Play();
}
Now upon releasing the left button, the position indicator does seem to move, not precisely where I want, but it does move.
Post Reply