ultimatepp/uppsrc/CtrlCore/CtrlDraw.cpp
2024-10-22 10:18:47 +02:00

709 lines
17 KiB
C++

#include "CtrlCore.h"
namespace Upp {
#define LLOG(x) // DLOG(x)
#define LTIMING(x) // DTIMING(x)
bool Ctrl::globalbackpaint;
bool Ctrl::globalbackbuffer;
bool Ctrl::was_fullrefresh;
static void sCheckGuiLock()
{
ASSERT_(ThreadHasGuiLock(), "Using GUI in non-main thread without GuiLock");
}
void Ctrl::RefreshFrame(const Rect& r) {
LTIMING("RefreshFrame");
sCheckGuiLock();
GuiLock __; // Beware: Even if we have ThreadHasGuiLock ASSERT, we still can be the main thread!
DLOG("RefreshFrame " << Name() << ' ' << r << " version 3");
if(!IsOpen() || !IsVisible() || r.IsEmpty()) return;
DLOG("RefreshRect A");
if(GuiPlatformRefreshFrameSpecial(r))
return;
DLOG("RefreshRect B");
if(!top && !IsDHCtrl()) {
Ctrl *parent = GetParent();
DDUMP(GetRect());
if(InFrame())
parent->RefreshFrame(r + GetRect().TopLeft());
else
parent->Refresh(r + GetRect().TopLeft());
}
else {
DLOG("WndInvalidateRect: " << r << ' ' << Name());
LTIMING("RefreshFrame InvalidateRect");
WndInvalidateRect(r);
}
}
void Ctrl::Refresh0(const Rect& area) {
RefreshFrame((area + GetView().TopLeft()) & GetView().Inflated(OverPaint()));
}
void Ctrl::Refresh(const Rect& area) {
sCheckGuiLock();
GuiLock __; // Beware: Even if we have ThreadHasGuiLock ASSERT, we still can be the main thread!
DLOG("Refresh " << Name() << ' ' << area << " full:" << fullrefresh);
if(fullrefresh || !IsVisible() || !IsOpen()) return;
Refresh0(area);
}
void Ctrl::Refresh() {
sCheckGuiLock();
GuiLock __; // Beware: Even if we have ThreadHasGuiLock ASSERT, we still can be the main thread!
DLOG("Refresh " << Name() << " full:" << fullrefresh);
if(fullrefresh || !IsVisible() || !IsOpen()) return;
Rect r = Rect(GetSize()).Inflated(OverPaint());
if(r.IsEmpty())
return;
if(!GuiPlatformSetFullRefreshSpecial())
was_fullrefresh = fullrefresh = true; // Needs to be set ahead because of possible MT ICall that can cause repaint during Refresh0
Refresh0(r);
}
void Ctrl::Refresh(int x, int y, int cx, int cy) {
Refresh(RectC(x, y, cx, cy));
}
void Ctrl::RefreshFrame(int x, int y, int cx, int cy) {
RefreshFrame(RectC(x, y, cx, cy));
}
void Ctrl::RefreshFrame() {
LLOG("RefreshFrame " << Name());
RefreshFrame(Rect(GetRect().Size()).Inflated(overpaint));
}
void Ctrl::ScrollRefresh(const Rect& r, int dx, int dy)
{
sCheckGuiLock();
GuiLock __; // Beware: Even if we have ThreadHasGuiLock ASSERT, we still can be the main thread!
LLOG("ScrollRefresh " << r << " " << dx << " " << dy);
if(!IsOpen() || !IsVisible() || r.IsEmpty()) return;
int tdx = tabs(dx), tdy = tabs(dy);
if(dx) WndInvalidateRect(RectC(dx >= 0 ? r.left : r.right - tdx, r.top - tdy, tdx, r.Height()));
if(dy) WndInvalidateRect(RectC(r.left - tdx, dy >= 0 ? r.top : r.bottom - tdy, r.Width(), tdy));
}
bool Ctrl::AddScroll(const Rect& sr, int dx, int dy)
{
GuiLock __;
Top *top = GetTop();
if(!top)
return true;
for(int i = 0; i < top->scroll.GetCount(); i++) {
Scroll& sc = top->scroll[i];
if(sc.rect == sr && sgn(dx) == sgn(sc.dx) && sgn(dy) == sgn(sc.dy)) {
sc.dx += dx;
sc.dy += dy;
ScrollRefresh(sc.rect, sc.dx, sc.dy);
return false;
}
if(sc.rect.Intersects(sr)) {
sc.rect |= sr;
sc.dx = sc.dy = 0;
WndInvalidateRect(sc.rect);
return true;
}
}
Scroll& sc = top->scroll.Add();
sc.rect = sr;
sc.dx = dx;
sc.dy = dy;
ScrollRefresh(sc.rect, sc.dx, sc.dy);
return false;
}
Rect Ctrl::GetClippedView()
{
GuiLock __;
Rect sv = GetScreenView();
Rect view = sv;
Ctrl *q = GetParent();
Ctrl *w = this;
while(q) {
view &= w->InFrame() ? q->GetScreenRect() : q->GetScreenView();
w = q;
q = q->GetParent();
}
return view - GetScreenRect().TopLeft();
}
void Ctrl::ScrollCtrl(Top *top, Ctrl *q, const Rect& r, Rect cr, int dx, int dy)
{
if(top && r.Intersects(cr)) { // Uno: Contains -> Intersetcs
Rect to = cr;
GetTopRect(to, false);
if(r.Intersects(cr.Offseted(-dx, -dy))) { // Uno's suggestion 06/11/26 Contains -> Intersetcs
Rect from = cr.Offseted(-dx, -dy);
GetTopRect(from, false);
MoveCtrl *m = FindMoveCtrlPtr(top->move, q);
if(m && m->from == from && m->to == to) {
LLOG("ScrollView Matched " << from << " -> " << to);
m->ctrl = NULL;
return;
}
}
if(r.Intersects(cr.Offseted(dx, dy))) { // Uno's suggestion 06/11/26 Contains -> Intersetcs
Rect from = to;
to = cr.Offseted(dx, dy);
GetTopRect(to, false);
MoveCtrl& m = top->scroll_move.Add(q);
m.from = from;
m.to = to;
m.ctrl = q;
LLOG("ScrollView Add " << UPP::Name(q) << from << " -> " << to);
return;
}
cr &= r;
if(!cr.IsEmpty()) {
Refresh(cr);
Refresh(cr + Point(dx, dy));
}
}
}
void Ctrl::ScrollView(const Rect& _r, int dx, int dy)
{
GuiLock __;
LLOG("ScrollView " << _r << " " << dx << " " << dy);
#ifdef GUIPLATFORM_NOSCROLL
LLOG("NOSCROLL");
Refresh(_r);
#else
if(IsFullRefresh() || !IsVisible())
return;
if(IsDHCtrl()) {
Refresh(_r);
return;
}
Size vsz = GetSize();
dx = sgn(dx) * min(abs(dx), vsz.cx);
dy = sgn(dy) * min(abs(dy), vsz.cy);
Rect r = _r & vsz;
LLOG("ScrollView2 " << r << " " << dx << " " << dy);
Ctrl *w;
for(w = this; w->GetParent(); w = w->GetParent())
if(w->InFrame()) {
Refresh();
return;
}
if(!w || !w->top) return;
Rect view = InFrame() ? GetView() : GetClippedView();
Rect sr = (r + view.TopLeft()) & view;
sr += GetScreenRect().TopLeft() - w->GetScreenRect().TopLeft();
if(w->AddScroll(sr, dx, dy))
Refresh();
else {
LTIMING("ScrollCtrls1");
Top *top = GetTopCtrl()->GetTop();
for(Ctrl *q = GetFirstChild(); q; q = q->GetNext())
if(q->InView())
ScrollCtrl(top, q, r, q->GetRect(), dx, dy);
if(GetParent())
for(Ctrl *q = GetParent()->GetFirstChild(); q; q = q->GetNext())
if(q->InView() && q != this)
ScrollCtrl(top, q, r, q->GetScreenRect() - GetScreenView().TopLeft(), dx, dy);
}
#endif
}
void Ctrl::ScrollView(int x, int y, int cx, int cy, int dx, int dy) {
ScrollView(RectC(x, y, cx, cy), dx, dy);
}
void Ctrl::ScrollView(int dx, int dy) {
ScrollView(Rect(GetSize()), dx, dy);
}
void Ctrl::SyncScroll()
{
GuiLock __;
Top *top = GetTop();
if(!top)
return;
Vector<Scroll> scroll = pick(top->scroll);
top->scroll.Clear();
if(IsFullRefresh())
return;
for(int i = 0; i < scroll.GetCount(); i++) {
Scroll& sc = scroll[i];
if(abs(sc.dx) > 3 * sc.rect.Width() / 4 || abs(sc.dy) > 3 * sc.rect.Height() / 4) {
LLOG("Sync scroll Invalidate rect" << sc.rect);
WndInvalidateRect(sc.rect);
}
else
if(sc.dx || sc.dy) {
LLOG("WndScrollView " << sc.rect);
WndScrollView(sc.rect, sc.dx, sc.dy);
}
}
}
Rect Ctrl::GetOpaqueRect() const
{
return IsTransparent() ? Rect(0, 0, 0, 0) : GetSize();
}
Rect Ctrl::GetVoidRect() const
{
return Rect(0, 0, 0, 0);
}
#ifdef _DEBUG
struct sDrawLevelCheck {
Draw& w;
int lvl;
const Ctrl *q;
void Check() {
ASSERT_(lvl == w.GetCloffLevel(), "Draw::Begin/End mismatch for " + UPP::Name(q));
}
sDrawLevelCheck(Draw& w, const Ctrl *q) : w(w), lvl(w.GetCloffLevel()), q(q) {}
// NOTE: Checking level in destructor is a bad idea because of exceptions
};
#define LEVELCHECK(w, q) sDrawLevelCheck _x_(w, q)
#define DOLEVELCHECK _x_.Check();
#else
#define LEVELCHECK(w, q)
#define DOLEVELCHECK
#endif
void Ctrl::CtrlPaint(SystemDraw& w, const Rect& clip) {
GuiLock __;
LEVELCHECK(w, this);
LTIMING("CtrlPaint");
LLOG("=== CtrlPaint " << UPP::Name(this) << ", clip: " << clip << ", rect: " << GetRect() << ", view: " << GetView());
Rect rect = GetRect().GetSize();
Rect orect = rect.Inflated(overpaint);
if(!IsShown() || orect.IsEmpty() || clip.IsEmpty() || !clip.Intersects(orect))
return;
Rect view = rect;
int n = GetFrameCount();
for(int i = 0; i < n; i++) {
LEVELCHECK(w, NULL);
Frame& f = GetFrame0(i);
f.frame->FramePaint(w, view);
view = f.GetView();
}
Rect oview = view.Inflated(overpaint);
bool hasviewctrls = false;
bool viewexcluded = false;
bool hiddenbychild = false;
for(Ctrl& q : *this)
if(q.IsShown()) {
if(q.GetRect().Contains(orect) && !q.IsTransparent())
hiddenbychild = true;
if(q.InFrame()) {
if(!viewexcluded && IsTransparent() && q.GetRect().Intersects(view)) {
w.Begin();
w.ExcludeClip(view);
viewexcluded = true;
}
LEVELCHECK(w, &q);
Point off = q.GetRect().TopLeft();
w.Offset(off);
q.CtrlPaint(w, clip - off);
w.End();
}
else
hasviewctrls = true;
}
if(viewexcluded)
w.End();
DOLEVELCHECK;
if(!hiddenbychild && !oview.IsEmpty() && oview.Intersects(clip) && w.IsPainting(oview)) {
LEVELCHECK(w, this);
if(overpaint) {
w.Clip(oview);
w.Offset(view.left, view.top);
Paint(w);
PaintCaret(w);
w.End();
w.End();
}
else {
w.Clipoff(view);
Paint(w);
PaintCaret(w);
w.End();
}
}
if(hasviewctrls && !view.IsEmpty()) {
Rect cl = clip & view;
w.Clip(cl);
for(Ctrl& q : *this)
if(q.IsShown() && q.InView()) {
LEVELCHECK(w, &q);
Rect qr = q.GetRect();
Point off = qr.TopLeft() + view.TopLeft();
Rect ocl = cl - off;
if(ocl.Intersects(Rect(qr.GetSize()).Inflated(overpaint))) {
w.Offset(off);
q.CtrlPaint(w, ocl);
w.End();
}
}
w.End();
}
DOLEVELCHECK;
}
int sShowRepaint;
void Ctrl::ShowRepaint(int q)
{
sShowRepaint = q;
}
void ShowRepaintRect(SystemDraw& w, const Rect& r, Color c)
{
if(sShowRepaint) {
w.DrawRect(r, c);
SystemDraw::Flush();
Sleep(sShowRepaint);
}
}
bool Ctrl::PaintOpaqueAreas(SystemDraw& w, const Rect& r, const Rect& clip, bool nochild)
{
GuiLock __;
LTIMING("PaintOpaqueAreas");
if(!IsShown() || r.IsEmpty() || !r.Intersects(clip) || !w.IsPainting(r))
return true;
Point off = r.TopLeft();
Point viewpos = off + GetView().TopLeft();
if(backpaint == EXCLUDEPAINT)
return w.ExcludeClip(r);
Rect cview = clip & (GetView() + off);
for(Ctrl& q : *this)
if(!q.PaintOpaqueAreas(w, q.GetRect() + (q.InView() ? viewpos : off),
q.InView() ? cview : clip))
return false;
if(nochild && (GetLastChild() || GetNext()))
return true;
Rect opaque = (GetOpaqueRect() + viewpos) & clip;
if(opaque.IsEmpty())
return true;
#ifdef SYSTEMDRAW
if(backpaint == FULLBACKPAINT && !dynamic_cast<BackDraw *>(&w))
#else
if(backpaint == FULLBACKPAINT && !w.IsBack())
#endif
{
ShowRepaintRect(w, opaque, LtRed());
BackDraw bw;
bw.Create(w, opaque.GetSize());
bw.Offset(viewpos - opaque.TopLeft());
bw.SetPaintingDraw(w, opaque.TopLeft());
{
LEVELCHECK(bw, this);
Paint(bw);
PaintCaret(bw);
DOLEVELCHECK;
}
bw.Put(w, opaque.TopLeft());
}
else {
w.Clip(opaque);
ShowRepaintRect(w, opaque, Green());
w.Offset(viewpos);
{
LEVELCHECK(w, this);
Paint(w);
PaintCaret(w);
DOLEVELCHECK;
}
w.End();
w.End();
}
LLOG("Exclude " << opaque);
return w.ExcludeClip(opaque);
}
inline int Area(const Rect& r)
{
return r.GetHeight() * r.GetWidth();
}
void CombineArea(Vector<Rect>& area, const Rect& r)
{
LTIMING("CombineArea");
if(r.IsEmpty()) return;
int ra = Area(r);
for(int i = 0; i < area.GetCount(); i++) {
Rect ur = r | area[i];
int a = Area(ur);
if(a < 2 * (ra + Area(area[i])) || a < 16000) {
area[i] = ur;
return;
}
}
area.Add(r);
}
void Ctrl::GatherTransparentAreas(Vector<Rect>& area, SystemDraw& w, Rect r, const Rect& clip)
{
GuiLock __;
LTIMING("GatherTransparentAreas");
Point off = r.TopLeft();
Point viewpos = off + GetView().TopLeft();
r.Inflate(overpaint);
Rect notr = GetVoidRect();
if(notr.IsEmpty())
notr = GetOpaqueRect();
notr += viewpos;
if(!IsShown() || r.IsEmpty() || !clip.Intersects(r) || !w.IsPainting(r))
return;
if(notr.IsEmpty())
CombineArea(area, r & clip);
else {
if(notr != r) {
CombineArea(area, clip & Rect(r.left, r.top, notr.left, r.bottom));
CombineArea(area, clip & Rect(notr.right, r.top, r.right, r.bottom));
CombineArea(area, clip & Rect(notr.left, r.top, notr.right, notr.top));
CombineArea(area, clip & Rect(notr.left, notr.bottom, notr.right, r.bottom));
}
for(Ctrl& q : *this) {
Point qoff = q.InView() ? viewpos : off;
Rect qr = q.GetRect() + qoff;
if(clip.Intersects(qr))
q.GatherTransparentAreas(area, w, qr, clip);
}
}
}
void Ctrl::ExcludeDHCtrls(SystemDraw& w, const Rect& r, const Rect& clip)
{
GuiLock __;
LTIMING("PaintOpaqueAreas");
if(!IsShown() || r.IsEmpty() || !r.Intersects(clip) || !w.IsPainting(r))
return;
Point off = r.TopLeft();
Point viewpos = off + GetView().TopLeft();
if(dynamic_cast<DHCtrl *>(this)) {
w.ExcludeClip(r);
return;
}
Rect cview = clip & (GetView() + off);
for(Ctrl& q : *this)
q.ExcludeDHCtrls(w, q.GetRect() + (q.InView() ? viewpos : off),
q.InView() ? cview : clip);
}
void Ctrl::UpdateArea0(SystemDraw& draw, const Rect& clip, int backpaint)
{
GuiLock __;
LTIMING("UpdateArea");
LLOG("========== UPDATE AREA " << UPP::Name(this) << ", clip: " << clip << " ==========");
ExcludeDHCtrls(draw, GetRect().GetSize(), clip);
if(globalbackbuffer) {
CtrlPaint(draw, clip);
LLOG("========== END (TARGET IS BACKBUFFER)");
return;
}
if(backpaint == FULLBACKPAINT || globalbackpaint) {
ShowRepaintRect(draw, clip, LtRed());
BackDraw bw;
bw.Create(draw, clip.GetSize());
bw.Offset(-clip.TopLeft());
bw.SetPaintingDraw(draw, clip.TopLeft());
CtrlPaint(bw, clip);
bw.Put(draw, clip.TopLeft());
LLOG("========== END (FULLBACKPAINT)");
return;
}
/* if(backpaint == TRANSPARENTBACKPAINT) {
LLOG("TransparentBackpaint");
Vector<Rect> area;
GatherTransparentAreas(area, draw, GetRect().GetSize(), clip);
for(int i = 0; i < area.GetCount(); i++) {
Rect ar = area[i];
LLOG("Painting area: " << ar);
ShowRepaintRect(draw, ar, LtBlue());
BackDraw bw;
bw.Create(draw, ar.GetSize());
bw.Offset(-ar.TopLeft());
bw.SetPaintingDraw(draw, ar.TopLeft());
CtrlPaint(bw, ar);
bw.Put(draw, ar.TopLeft());
if(!draw.ExcludeClip(ar)) {
LLOG("========== END");
return;
}
}
PaintOpaqueAreas(draw, GetRect().GetSize(), clip);
LLOG("========== END");
return;
}*/
CtrlPaint(draw, clip);
LLOG("========== END");
}
void SweepMkImageCache();
void Ctrl::UpdateArea(SystemDraw& draw, const Rect& clip)
{
GuiLock __;
if(IsPanicMode())
return;
RemoveFullRefresh();
UpdateArea0(draw, clip, backpaint);
SweepMkImageCache();
}
void Ctrl::RemoveFullRefresh()
{
GuiLock __;
fullrefresh = false;
for(Ctrl *q = GetFirstChild(); q; q = q->GetNext())
q->RemoveFullRefresh();
}
void Ctrl::FullRefreshCleanup()
{ // remove any potentially stuck fullrefresh
GuiLock __;
if(was_fullrefresh) {
DTIMESTOP("FullRefreshCleanup");
for(Ctrl *q : GetTopCtrls())
q->RemoveFullRefresh();
was_fullrefresh = false;
}
}
Ctrl *Ctrl::GetTopRect(Rect& r, bool inframe, bool clip)
{
GuiLock __;
if(!inframe) {
if(clip)
r &= Rect(GetSize());
r.Offset(GetView().TopLeft());
}
Ctrl *parent = GetParent();
if(parent) {
r.Offset(GetRect().TopLeft());
return parent->GetTopRect(r, InFrame(), clip);
}
return this;
}
void Ctrl::DoSync(Ctrl *q, Rect r, bool inframe)
{
GuiLock __;
ASSERT(q);
LLOG("DoSync " << UPP::Name(q) << " " << r);
Ctrl *top = q->GetTopRect(r, inframe);
if(top && top->IsOpen()) {
top->SyncScroll();
top->WndUpdate(r);
}
}
void Ctrl::Sync()
{
GuiLock __;
LLOG("Sync " << Name());
Ctrl *parent = GetParent();
if(top && IsOpen()) {
LLOG("Sync UpdateWindow " << Name());
SyncScroll();
WndUpdate();
}
else
if(parent)
DoSync(parent, GetRect(), inframe);
SyncCaret();
}
void Ctrl::Sync(const Rect& sr)
{
GuiLock __;
LLOG("Sync " << Name() << " " << sr);
DoSync(this, sr, true);
SyncCaret();
}
void Ctrl::DrawCtrlWithParent(Draw& w, int x, int y)
{
GuiLock __;
Ctrl *parent = GetParent();
if(parent) {
Rect r = GetRect();
Ctrl *top = parent->GetTopRect(r, inframe);
w.Clip(x, y, r.Width(), r.Height());
w.Offset(x - r.left, y - r.top);
SystemDraw *ws = dynamic_cast<SystemDraw *>(&w);
if(ws)
top->UpdateArea(*ws, r);
w.End();
w.End();
}
else
DrawCtrl(w, x, y);
}
void Ctrl::DrawCtrl(Draw& w, int x, int y)
{
GuiLock __;
w.Offset(x, y);
SystemDraw *ws = dynamic_cast<SystemDraw *>(&w);
if(ws)
UpdateArea(*ws, GetRect().GetSize());
// CtrlPaint(w, GetSize()); _DBG_
w.End();
}
void Ctrl::SyncMoves()
{
GuiLock __;
Top *top = GetTop();
if(!top)
return;
for(int i = 0; i < top->move.GetCount(); i++) {
MoveCtrl& m = top->move[i];
if(m.ctrl) {
RefreshFrame(m.from);
RefreshFrame(m.to);
}
}
for(int i = 0; i < top->scroll_move.GetCount(); i++) {
MoveCtrl& s = top->scroll_move[i];
if(s.ctrl) {
RefreshFrame(s.from);
RefreshFrame(s.to);
}
}
top->move.Clear();
top->scroll_move.Clear();
}
void Ctrl::GlobalBackPaint(bool b)
{
GuiLock __;
globalbackpaint = b;
}
void Ctrl::GlobalBackPaintHint()
{
GlobalBackPaint();
}
void Ctrl::GlobalBackBuffer(bool b)
{
GuiLock __;
globalbackbuffer = b;
}
}