Restores "real popups" in display popup

These were removed year ago because the placement is imprecise (macos, gtk do not allow odd pixel placement when UHD scaling, so it was a pixel off) and because of Wayland concerns. But the usefulness outweights these glitches...
This commit is contained in:
mirek-fidler 2025-10-14 09:00:11 +02:00 committed by GitHub
parent 3a6e3ca22a
commit 7edd654237
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 335 additions and 256 deletions

View file

@ -239,13 +239,7 @@ public:
}
#endif
};
/*
template <class T, int N = 1>
class LinkOwner : public Link<T, N> {
public:
~LinkOwner() { Link<T, N>::DeleteList(); }
};
*/
template <class T, class K = String>
class LRUCache {
public:

View file

@ -1,192 +1,263 @@
#include "CtrlLib.h"
namespace Upp {
void DisplayPopup::Pop::PopCtrl::Paint(Draw& w)
{
Rect r = GetSize();
w.DrawRect(r, SColorPaper);
if(!p) return;
if(p->display) {
p->display->PaintBackground(w, r, p->value, p->ink, p->paper, p->style);
r.left += p->margin;
if(p->usedisplaystdsize)
r.top += (r.Height() - p->display->GetStdSize(p->value).cy) / 2;
p->display->Paint(w, r, p->value, p->ink, p->paper, p->style);
}
}
Vector<DisplayPopup::Pop *>& DisplayPopup::Pop::all()
{
static Vector<DisplayPopup::Pop *> all;
return all;
}
DisplayPopup::Pop::Pop()
{
display = NULL;
paper = ink = Null;
style = 0;
item = Null;
margin = 0;
all().Add(this);
view.IgnoreMouse();
view.SetFrame(BlackFrame());
frame.IgnoreMouse();
frame.SetFrame(BlackFrame());
view.p = this;
frame.p = this;
}
DisplayPopup::Pop::~Pop()
{
view.p = nullptr;
frame.p = nullptr;
int q = FindIndex(all(), this);
if(q >= 0)
all().Remove(q);
}
Rect DisplayPopup::Check(Ctrl *ctrl, const Rect& item, const Value& value, const Display *display, int margin)
{
if(display && ctrl && !ctrl->IsDragAndDropTarget() && !(GetMouseLeft() || GetMouseRight() || GetMouseMiddle())) {
Ctrl *top = ctrl->GetTopCtrl();
if(top && top->HasFocusDeep()) {
Size sz = display->GetStdSize(value);
if(sz.cx + 2 * margin > item.GetWidth() || sz.cy > item.GetHeight()) {
Rect vw = ctrl->GetScreenView();
Rect r = (item + vw.TopLeft()) & vw;
if(r.Contains(GetMousePos()))
return r;
}
}
}
return Null;
}
void DisplayPopup::Pop::Sync()
{
if(!IsMainThread()) {
Ptr<Pop> p;
PostCallback([=] { if(p) p->Sync(); });
return;
}
Rect r = Check(ctrl, item, value, display, margin);
if(IsNull(r))
WhenClose();
else {
Ctrl *top = ctrl->GetTopCtrl();
Size sz = display->GetStdSize(value);
Rect wa = top->GetScreenRect();
r.right = min(wa.right, r.left + sz.cx + 2 * margin);
r.bottom = max(r.bottom, r.top + sz.cy);
r.Inflate(1, 1);
view.SetRect(r - top->GetScreenView().TopLeft());
frame.SetFrameRect(r - wa.TopLeft());
if(!frame.GetParent())
*top << view << frame;
}
}
DisplayPopup::DisplayPopup()
{
ONCELOCK {
Ctrl::InstallStateHook(StateHook);
Ctrl::InstallMouseHook(MouseHook);
}
}
void DisplayPopup::SyncAll()
{
for(DisplayPopup::Pop *p : Pop::all())
if(p->ctrl && p->ctrl->IsOpen())
p->Sync();
}
bool DisplayPopup::StateHook(Ctrl *, int reason)
{
if(reason == Ctrl::FOCUS)
SyncAll();
return false;
}
bool DisplayPopup::MouseHook(Ctrl *, bool, int, Point, int, dword)
{
SyncAll();
return false;
}
void DisplayPopup::Pop::Set(Ctrl *_ctrl, const Rect& _item,
const Value& _value, const Display *_display,
Color _ink, Color _paper, dword _style, int _margin)
{
if(!GUI_ToolTips())
return;
if(item != _item || ctrl != _ctrl || value != _value || display != _display || ink != _ink ||
paper != _paper || style != _style) {
item = _item;
ctrl = _ctrl;
value = _value;
display = _display;
ink = _ink;
paper = _paper;
style = _style;
margin = _margin;
if(ctrl) {
String h = ctrl->GetTip();
view.Tip(h);
frame.Tip(h);
}
Sync();
view.Refresh();
frame.Refresh();
}
}
void DisplayPopup::Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display, Color ink, Color paper, dword style, int margin)
{
if(IsNull(Check(ctrl, item, v, display, margin)))
return; // precheck to avoid creating / deleting popup too often, avoid flooding timer with PostCallback
if(!popup) {
popup.Create();
popup->usedisplaystdsize = usedisplaystdsize;
Ptr<DisplayPopup> pt = this;
popup->WhenClose << [=] { PostCallback([=] { if(pt) pt->popup.Clear(); }); };
}
popup->Set(ctrl, item, v, display, ink, paper, style, margin);
}
void DisplayPopup::Cancel()
{
if(popup) {
popup->display = nullptr;
popup->Sync();
}
}
bool DisplayPopup::IsOpen()
{
return popup && popup->view.GetParent();
}
bool DisplayPopup::HasMouse()
{ // TODO: remove
return popup && (popup->view.HasMouse() || popup->frame.HasMouse());
}
void DisplayPopup::UseDisplayStdSize()
{
usedisplaystdsize = true;
if(popup)
popup->usedisplaystdsize = true;
}
DisplayPopup::~DisplayPopup()
{
if(popup)
popup.Clear();
}
}
#include "CtrlLib.h"
namespace Upp {
Point DisplayPopup::PopUp::Op(Point p)
{
return p + GetScreenView().TopLeft() - ctrl->GetScreenView().TopLeft();
}
void DisplayPopup::PopUp::LeftDown(Point p, dword flags)
{
if(ctrl) ctrl->LeftDown(Op(p), flags);
}
void DisplayPopup::PopUp::LeftDrag(Point p, dword flags)
{
if(ctrl) ctrl->LeftDrag(Op(p), flags);
}
void DisplayPopup::PopUp::LeftDouble(Point p, dword flags)
{
if(ctrl) ctrl->LeftDouble(Op(p), flags);
}
void DisplayPopup::PopUp::RightDown(Point p, dword flags)
{
if(ctrl) ctrl->RightDown(Op(p), flags);
}
void DisplayPopup::PopUp::LeftUp(Point p, dword flags)
{
if(ctrl) ctrl->LeftUp(Op(p), flags);
}
void DisplayPopup::PopUp::MouseWheel(Point p, int zdelta, dword flags)
{
if(ctrl) ctrl->MouseWheel(Op(p), zdelta, flags);
}
void DisplayPopup::PopUp::MouseLeave()
{
Cancel();
}
void DisplayPopup::PopUp::MouseMove(Point p, dword flags)
{
p += GetScreenView().TopLeft();
if(!slim.Contains(p))
MouseLeave();
}
void DisplayPopup::PopUp::Paint(Draw& w)
{
Rect r = GetSize();
w.DrawRect(r, SColorPaper);
if(display) {
display->PaintBackground(w, r, value, ink, paper, style);
r.left += margin;
if(usedisplaystdsize)
r.top += (r.Height() - display->GetStdSize(value).cy) / 2;
display->Paint(w, r, value, ink, paper, style);
}
}
Vector<DisplayPopup::PopUp *>& DisplayPopup::PopUp::all()
{
static Vector<DisplayPopup::PopUp *> all;
return all;
}
DisplayPopup::PopUp::PopUp()
{
SetFrame(BlackFrame());
display = NULL;
paper = ink = Null;
style = 0;
item = slim = Null;
margin = 0;
ONCELOCK {
InstallStateHook(StateHook);
InstallMouseHook(MouseHook);
}
all().Add(this);
}
DisplayPopup::PopUp::~PopUp()
{
int q = FindIndex(all(), this);
if(q >= 0)
all().Remove(q);
}
Rect DisplayPopup::Check(Ctrl *ctrl, const Rect& item, const Value& value, const Display *display, int margin)
{
if(display && ctrl && !ctrl->IsDragAndDropTarget() && !(GetMouseLeft() || GetMouseRight() || GetMouseMiddle())) {
Ctrl *top = ctrl->GetTopCtrl();
if(top && top->HasFocusDeep()) {
Size sz = display->GetStdSize(value);
if(sz.cx + 2 * margin > item.GetWidth() || sz.cy > item.GetHeight()) {
Rect vw = ctrl->GetScreenView();
Rect r = (item + vw.TopLeft()) & vw;
if(r.Contains(GetMousePos()))
return r;
}
}
}
return Null;
}
void DisplayPopup::PopUp::Sync()
{
if(!IsMainThread()) {
PostCallback(PTEBACK(Sync));
return;
}
Rect r = Check(ctrl, item, value, display, margin);
if(IsNull(r)) {
DLOG("CLOSE");
DDUMP(r);
DDUMP(ctrl);
DDUMP(item);
DDUMP(value);
DDUMP(display);
DDUMP(margin);
Ctrl *top = ctrl->GetTopCtrl();
DDUMP(top);
if(top)
DDUMP(top->HasFocusDeep());
WhenClose();
}
else {
Ctrl *top = ctrl->GetTopCtrl();
Size sz = display->GetStdSize(value);
Rect wa = top->GetWorkArea();
r.right = min(wa.right, r.left + sz.cx + 2 * margin);
r.bottom = max(r.bottom, r.top + sz.cy);
slim = r;
r.Inflate(1, 1);
SetRect(r);
if(!IsOpen()) {
DLOG("POPUP " << r);
DDUMP(r);
DDUMP(ctrl);
DDUMP(item);
DDUMP(value);
DDUMP(display);
DDUMP(margin);
Ctrl::PopUp(ctrl, true, false, false);
}
}
}
void DisplayPopup::PopUp::SyncAll()
{
int n = 0;
for(DisplayPopup::PopUp *p : all())
if(p->ctrl && p->ctrl->IsOpen()) {
p->Sync();
n++;
}
}
bool DisplayPopup::PopUp::StateHook(Ctrl *, int reason)
{
if(reason == FOCUS)
SyncAll();
return false;
}
bool DisplayPopup::PopUp::MouseHook(Ctrl *, bool, int, Point, int, dword)
{
SyncAll();
return false;
}
void DisplayPopup::PopUp::Cancel()
{
DLOG("CANCEL");
if(GetDragAndDropSource())
return;
display = nullptr;
Sync();
}
bool DisplayPopup::PopUp::IsOpen()
{
return Ctrl::IsOpen();
}
bool DisplayPopup::PopUp::HasMouse()
{
return Ctrl::HasMouse() || ctrl && ctrl->HasMouse();
}
void DisplayPopup::PopUp::Set(Ctrl *_ctrl, const Rect& _item,
const Value& _value, const Display *_display,
Color _ink, Color _paper, dword _style, int _margin)
{
if(!GUI_ToolTips() || GetDragAndDropSource())
return;
if(item != _item || ctrl != _ctrl || value != _value || display != _display || ink != _ink ||
paper != _paper || style != _style) {
item = _item;
ctrl = _ctrl;
value = _value;
display = _display;
ink = _ink;
paper = _paper;
style = _style;
margin = _margin;
if(ctrl)
Tip(ctrl->GetTip());
Sync();
Refresh();
}
}
void DisplayPopup::Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display, Color ink, Color paper, dword style, int margin)
{
if(IsNull(Check(ctrl, item, v, display, margin)))
return; // precheck to avoid creating / deleting popup too often, avoid flooding timer with PostCallback
if(!popup) {
popup.Create();
popup->usedisplaystdsize = usedisplaystdsize;
Ptr<DisplayPopup> pt = this;
popup->WhenClose << [=] { PostCallback([=] { if(pt) pt->popup.Clear(); }); };
}
popup->Set(ctrl, item, v, display, ink, paper, style, margin);
}
void DisplayPopup::Cancel()
{
if(popup)
popup->Cancel();
}
bool DisplayPopup::IsOpen()
{
return popup && popup->IsOpen();
}
bool DisplayPopup::HasMouse()
{
return popup && popup->HasMouse();
}
void DisplayPopup::UseDisplayStdSize()
{
usedisplaystdsize = true;
if(popup)
popup->usedisplaystdsize = true;
}
DisplayPopup::~DisplayPopup()
{
if(popup)
popup->Close();
}
}

View file

@ -1,55 +1,67 @@
class DisplayPopup : public Pte<DisplayPopup> {
private:
struct Pop : Pte<Pop> {
struct PopCtrl : public Ctrl {
virtual void Paint(Draw& w);
struct Pop *p;
};
Ptr<Ctrl> ctrl;
Rect item;
Value value;
Color paper, ink;
dword style;
const Display *display;
int margin;
bool usedisplaystdsize = false;
PopCtrl view;
PopCtrl frame;
Callback WhenClose;
void Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display,
Color ink, Color paper, dword style, int margin = 0);
void Sync();
static Vector<Pop *>& all();
Pop();
~Pop();
};
One<Pop> popup;
bool usedisplaystdsize = false;
static bool StateHook(Ctrl *, int reason);
static bool MouseHook(Ctrl *, bool, int, Point, int, dword);
static void SyncAll();
static Rect Check(Ctrl *ctrl, const Rect& item, const Value& value, const Display *display, int margin);
typedef DisplayPopup CLASSNAME;
public:
void Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display,
Color ink, Color paper, dword style, int margin = 0);
void Cancel();
bool IsOpen();
bool HasMouse();
void UseDisplayStdSize();
DisplayPopup();
~DisplayPopup();
};
class DisplayPopup : public Pte<DisplayPopup> {
private:
struct PopUp : public Ctrl {
virtual void Paint(Draw& w);
virtual void LeftDown(Point p, dword);
virtual void LeftDrag(Point p, dword);
virtual void LeftDouble(Point p, dword);
virtual void RightDown(Point p, dword);
virtual void LeftUp(Point p, dword);
virtual void MouseWheel(Point p, int zdelta, dword keyflags);
virtual void MouseLeave();
virtual void MouseMove(Point p, dword);
Ptr<Ctrl> ctrl;
Rect item;
Rect slim;
Value value;
Color paper, ink;
dword style;
const Display *display;
int margin;
bool usedisplaystdsize = false;
Point Op(Point p);
void Sync();
static Vector<DisplayPopup::PopUp *>& all();
static bool StateHook(Ctrl *, int reason);
static bool MouseHook(Ctrl *, bool, int, Point, int, dword);
static void SyncAll();
typedef DisplayPopup::PopUp CLASSNAME;
Callback WhenClose;
void Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display,
Color ink, Color paper, dword style, int margin = 0);
void Cancel();
bool IsOpen();
bool HasMouse();
PopUp();
~PopUp();
};
One<PopUp> popup;
bool usedisplaystdsize = false;
static Vector<DisplayPopup *>& all();
static bool StateHook(Ctrl *, int reason);
static bool MouseHook(Ctrl *, bool, int, Point, int, dword);
static void SyncAll();
static Rect Check(Ctrl *ctrl, const Rect& item, const Value& value, const Display *display, int margin);
typedef DisplayPopup CLASSNAME;
public:
void Set(Ctrl *ctrl, const Rect& item, const Value& v, const Display *display,
Color ink, Color paper, dword style, int margin = 0);
void Cancel();
bool IsOpen();
bool HasMouse();
void UseDisplayStdSize();
~DisplayPopup();
};

View file

@ -7,10 +7,12 @@ GUI_APP_MAIN
ArrayCtrl list;
list.AddColumn("Test");
list.Add("Simple");
list.Add("Long " + String('X', 200));
for(int i = 0; i < 10; i++)
list.Add("Long " + String('X', i * 100));
DropList dl;
dl.Add("Simple");
dl.Add("Long " + String('X', 200));
for(int i = 0; i < 10; i++)
dl.Add("Long " + String('X', i * 100));
TopWindow win;
win.Add(dl.TopPosZ(0).LeftPosZ(0, 100));
win.Add(list.VSizePosZ(Zx(20), 0).LeftPosZ(0, 100));