Scrollgroup and UserArea drawing artifacts

On 24/04/2018 at 12:45, xxxxxxxx wrote:

User Information:
Cinema 4D Version:   R19 
Platform:   Windows  ;   
Language(s) :     C++  ;

---------
I am using a GeDialog containing some sort of "log" area to visualize some processing.
This uses a GeUserArea inside of a scrollgroup.
The idea is that the log window automatically scrolls to show the current line being processed, while still allowing the user to scroll back through the results to investigate the rest of the data when processing is completed.

Unfortunately, I cannot seem to find a way to scroll without messing up the drawing of the userarea. Some artifacts happen as a result of the scrolling, resulting in the bottom "smearing" out, occasionally.

Here is a simplified example containing only the necessary things to reproduce the issue.
(It's a full plugin implementation which can be put in a single cpp file, I usually work with multiple header files and cpp files per object, but it seemed easier to provide the code as a single "block")

The plugin consists of a CommandData that shows a dialog. Pressing the "Process" button will fill the "log" window with some dummy data to visualize the scrolling issue.

What am I doing wrong ?
Thanks in advance.

  
// ========================  
// Testing  
// Dialog with scrollgroup and userarea  
// ========================  
  
#include "c4d.h"  
#include "lib_clipmap.h"  
  
// Dummy IDs - for demonstration purposes only  
#define MYCOMMAND_PLUGIN_ID    1999999  
  
enum {  
  IDC_PROCESS = 5000,  
  IDC_SCROLLGROUPV,  
  IDC_USERAREA  
};  
  
#define kSpacing 2  
  
// ====================================  
// UserArea  
// ====================================  
  
struct mydata {  
  String    text;  
  Int32    current;  
  Int32    total;  
  
  mydata(const String& s, const Int32& t) :  
      text(s), current(0), total(t)  
  {}  
};  
  
class MyUserArea : public GeUserArea  
{  
  INSTANCEOF(MyUserArea, GeUserArea)  
  
public:  
  MyUserArea();  
  virtual ~MyUserArea();  
  
  virtual Bool Init(void);  
  virtual Bool GetMinSize(Int32& w, Int32& h);  
  //virtual void Sized(Int32 w, Int32 h);  
  virtual Bool InitValues(void);  
  virtual void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg);  
  
  void AddData(const mydata& newdata);  
  void UpdateCurrent(const Int32& i, const Int32& newcurrent);  
  
private:  
  AutoAlloc<GeClipMap>        mClipmap;  
public:  
  Int32                        mTextHeight;  
  maxon::BaseArray<mydata>    mData;  
};  
  
MyUserArea::MyUserArea(void) : GeUserArea(), mTextHeight(0) {}  
MyUserArea::~MyUserArea(void) {}  
  
Bool MyUserArea::Init(void)  
{  
  // calculate the font's textheight  
  mClipmap->Init(100, 100, 32);  
  mClipmap->BeginDraw();  
  mClipmap->SetFont(nullptr); // use default font  
  mTextHeight = mClipmap->GetTextHeight();  
  mClipmap->EndDraw();  
  return TRUE;  
}  
  
Bool MyUserArea::GetMinSize(Int32& w, Int32& h)  
{  
  w = 100;  
  h = (mTextHeight + kSpacing) * Max(1, (Int32)mData.GetCount());  
  
  return TRUE;  
}  
  
Bool MyUserArea::InitValues(void)  
{  
  return TRUE;  
}  
  
void MyUserArea::DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)  
{  
  if (!mClipmap)  
      return;  
  
  OffScreenOn();  
  SetClippingRegion(x1, y1, x2, y2);  
  
  Int32 w = x2 - x1;  
  Int32 h = y2 - y1;  
  
  mClipmap->Init(w, h, 32);  
  mClipmap->BeginDraw();  
  
  // draw the background  
  Vector bkcolor = GetViewColor(VIEWCOLOR_C4DBACKGROUND);  
  bkcolor *= 255; // vector color is defined as values 0..1, we need 0..255 in GeClipMap  
  mClipmap->SetColor(SAFEINT32(bkcolor.x), SAFEINT32(bkcolor.y), SAFEINT32(bkcolor.z), 255);  
  mClipmap->FillRect(x1, y1, x2, y2);  
  
  mClipmap->SetFont(nullptr); // use default font  
  
  Int32 barlength = SAFEINT32(w * 0.5) - (2 * kSpacing);  
  Int32 posx1 = kSpacing;  
  Int32 posx2 = kSpacing + SAFEINT32(w * 0.5);  
  Int32 posy = kSpacing;  
  
  Vector textColor(255);  
  Vector backColor(0);  
  Vector color(0, 255, 0);  
  
  for (Int32 i = 0; i < (Int32)mData.GetCount(); ++i)  
  {  
      mClipmap->SetColor(SAFEINT32(textColor.x), SAFEINT32(textColor.y), SAFEINT32(textColor.z), 255);  
      mClipmap->TextAt(posx1, posy, mData _.text);  
  
      mClipmap->SetColor(SAFEINT32(backColor.x), SAFEINT32(backColor.y), SAFEINT32(backColor.z), 255);  
      mClipmap->FillRect(posx2 + 1, posy + 1, posx2 + barlength, posy + mTextHeight - 1);  
  
      mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255);  
      Int32 value = (mData _.total != 0) ? barlength * mData _.current / mData _.total : 0;  
      mClipmap->FillRect(posx2 + 1, posy + 1, posx2 + value, posy + mTextHeight - 1);  
  
      posy += mTextHeight + kSpacing;  
  }  
  
  mClipmap->EndDraw();  
  DrawBitmap(mClipmap->GetBitmap(), 0, y1, w, h, 0, y1, w, h, BMP_ALLOWALPHA);  
}  
  
void MyUserArea::AddData(const mydata& newdata)  
{  
  mData.Append(newdata);  
  LayoutChanged();  
}  
  
void MyUserArea::UpdateCurrent(const Int32& i, const Int32& newcurrent)  
{  
  if ((i < 0) || (i >= mData.GetCount()))  
      return;  
  mData _.current = newcurrent;  
  Redraw();  
}  
  
// ====================================  
// Dialog  
// ====================================  
  
class MyDialog : public GeDialog  
{  
  INSTANCEOF(MyDialog, GeDialog)  
  
public:  
  MyDialog(void);  
  virtual ~MyDialog(void);  
  
  virtual Bool CreateLayout(void);  
  virtual Bool InitValues(void);  
  virtual Bool Command(Int32 id, const BaseContainer& msg);  
  
  MyUserArea    mUserArea;  
};  
  
MyDialog::MyDialog(void) : GeDialog() {}  
MyDialog::~MyDialog(void) {}  
  
Bool MyDialog::CreateLayout(void)  
{  
  Bool res = GeDialog::CreateLayout();  
  
  SetTitle("Convert PolyGnome libraries");  
  
  GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, String(), 0);  
  {  
      GroupBegin(0, BFH_SCALEFIT, 2, 0, String(), 0);  
      {  
          AddStaticText(0, BFH_LEFT, 100, 0, String("Some gadgets"), 0);  
          AddStaticText(0, BFH_LEFT | BFH_SCALEFIT, 100, 0, String("... Dummy ..."), 0);  
      }  
      GroupEnd();  
  
      GroupBegin(0, BFH_SCALEFIT, 2, 0, String(), 0);  
      {  
          AddStaticText(0, BFH_LEFT, 100, 0, String("Other gadgets"), 0);  
          AddStaticText(0, BFH_LEFT | BFH_SCALEFIT, 100, 0, String("... More dummy ..."), 0);  
      }  
      GroupEnd();  
  
      // status area displaying the list of old-format labraries  
      // and progress of conversion (per library)  
      ScrollGroupBegin(IDC_SCROLLGROUPV, BFH_SCALEFIT | BFV_SCALEFIT, SCROLLGROUP_VERT | SCROLLGROUP_NOBLIT, 100, 10);  
      C4DGadget* ua = AddUserArea(IDC_USERAREA, BFH_SCALEFIT | BFV_SCALEFIT, 100, 10);  
      if (ua)  
          AttachUserArea(mUserArea, ua);  
      GroupEnd();  
  
      AddButton(IDC_PROCESS, BFH_CENTER, 200, 0, "Process");  
  }  
  GroupEnd();  
  
  return res;  
}  
  
Bool MyDialog::InitValues(void)  
{  
  return TRUE;  
}  
  
Bool MyDialog::Command(Int32 id, const BaseContainer& msg)  
{  
  switch (id)  
  {  
      case IDC_PROCESS:  
          {  
              mUserArea.mData.Reset();  
              // process some data  
              String txt[] = { "Entry 1", "Entry 2", "Entry 3", "Entry 4", "Entry 5", "Entry 6", "Entry 7" };  
              Int32 count[] = { 10, 15, 10, 7, 5, 8, 6 };  
  
              for (Int32 i = 0; i < 7; ++i)  
              {  
                  mydata data(txt _, count _);  
                  mUserArea.AddData(data);  
  
                  // set the focus on the current data entry  
                  {  
                      Int32 sx1, sx2, sy1, sy2;  
                      GetVisibleArea(IDC_SCROLLGROUPV, &sx1, &sy1, &sx2, &sy2);  
                      Int32 currentY = mUserArea.mTextHeight * i;  
                      if ((currentY < sy1) || (currentY > sy2))  
                      {  
                          sy1 = currentY;  
                          sy2 = sy1 + mUserArea.mTextHeight;  
                          SetVisibleArea(IDC_SCROLLGROUPV, sx1, sy1, sx2, sy2); // -> artifacts  
                      }  
                  }  
                  mUserArea.LayoutChanged();  
  
                  // update the data entry  
                  for (Int32 j = 0; j <= count _; ++j)  
                  {  
                      mUserArea.UpdateCurrent(i, j);  
                      GeSleep(50);  
                  }  
              }  
          }  
          break;  
  }  
  return TRUE;  
}  
  
// ====================================  
// CommandData  
// ====================================  
  
class MyCommand : public CommandData  
{  
  INSTANCEOF(MyCommand, CommandData)  
  
public:  
  MyDialog    mDlg;  
  
public:  
  virtual Bool Execute(BaseDocument* doc);  
  virtual Bool ExecuteSubID(BaseDocument* doc, Int32 subid);  
  virtual Int32 GetState(BaseDocument* doc);  
  //virtual Bool GetSubContainer(BaseDocument* doc, BaseContainer& submenu);  
  virtual Bool RestoreLayout(void* secret);  
  virtual Bool Message(Int32 type, void* data);  
};  
  
Bool MyCommand::Execute(BaseDocument* doc)  
{  
  if (!mDlg.IsOpen())  
      mDlg.Open(DLG_TYPE_ASYNC, MYCOMMAND_PLUGIN_ID, -1, -1, 300, 75, 0);  
  return TRUE;  
}  
  
Bool MyCommand::ExecuteSubID(BaseDocument* doc, Int32 subid)  
{  
  return TRUE;  
}  
  
  
Int32 MyCommand::GetState(BaseDocument* doc)  
{  
  return CMD_ENABLED;  
}  
  
Bool MyCommand::RestoreLayout(void* secret)  
{  
  mDlg.RestoreLayout(MYCOMMAND_PLUGIN_ID, 0, secret);  
  return TRUE;  
}  
  
Bool MyCommand::Message(Int32 type, void* data)  
{  
  return SUPER::Message(type, data);  
}  
  
Bool RegisterMyCommand(void)  
{  
  return RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Testing", 0, AutoBitmap("icon.png"), "Test", NewObjClear(MyCommand));  
}  
  
// ====================================  
// Plugin Main   
// ====================================  
Bool PluginStart(void)  
{  
  RegisterMyCommand();  
  return TRUE;  
}  
void PluginEnd(void)   
{  
}  
Bool PluginMessage(Int32 id, void * data)  
{  
  switch (id) {  
  case C4DPL_INIT_SYS:  
      if (!resource.Init())  
          return FALSE;  
      return TRUE;  
  case C4DMSG_PRIORITY:  
      return TRUE;  
  case C4DPL_BUILDMENU:  
      break;  
  case C4DPL_ENDACTIVITY:  
      return TRUE;  
  }  
  return FALSE;  
}  
  

On 25/04/2018 at 01:34, xxxxxxxx wrote:

Hi C4DS,

The main issue with your code is you actually draw into a bitmap which always equals to the size of the GeUserArea. And you do all your drawing from the top of this bitmap.
Then you scroll this GeUserArea (so you ask, to draw a part of the bitmap which is not drawn)

In order to fix it simply do:
mClipmap->Init(w, y2, 32);

Cheers,
Maxime

On 25/04/2018 at 02:16, xxxxxxxx wrote:

Aaargh !!!
Silly me. Never thought about that.
Thanks for pointing it out Maxime.