Release v0.6.54: adjustable pencil smoothing and unsaved settings state

This commit is contained in:
agrynco committed 2026-11-06 14:46:53 +02:00
1 parent 727dc96908
commit 63b7b879a3
14 files changed
+318 -18

No files matched your search

+76 -4
View File
@@ -178,6 +178,8 @@ struct Capture
Mark textDefaults{};
ScreenshotShape shape=ScreenshotShape::None;
LineStyle lineStyle=LineStyle::Solid;
int smoothing=25;
bool smoothingDrag=false;
LineEnds arrowEnds=LineEnds::End, freehandEnds=LineEnds::None;
COLORREF currentColor=RGB(225,42,50);
COLORREF customColors[16]{};
@@ -630,6 +632,13 @@ bool selectedLine(const Capture &c)
}
void syncLineOptions(HWND w,const Capture &c)
{
bool pencil=c.selected>=0 && c.marks[c.selected].freehand;
int amount=pencil?c.marks[c.selected].smoothing:c.smoothing;
SendDlgItemMessageW(w,41,TBM_SETPOS,TRUE,amount);
auto smoothingText=L"Smoothing: "+std::to_wstring(amount)+L"%";
SetDlgItemTextW(w,42,smoothingText.c_str());
SetDlgItemTextW(w,43,pencil?L"Selected stroke":L"New pencil strokes");
EnableWindow(GetDlgItem(w,44),std::any_of(c.marks.begin(),c.marks.end(),[](const Mark &m){return m.freehand;}));
EnableWindow(GetDlgItem(w,34),c.textTool || (c.selected>=0 && (c.marks[c.selected].isText() || !c.marks[c.selected].label.empty())));
EnableWindow(GetDlgItem(w,35),c.selected>=0 && c.marks[c.selected].isLine());
bool selected=selectedLine(c),enabled=selected || c.arrow || c.freehand;
@@ -689,8 +698,12 @@ void layout(HWND w, Capture &c)
SendDlgItemMessageW(w,28,WM_SETFONT,(WPARAM)c.font,TRUE);
MoveWindow(GetDlgItem(w,28),MulDiv(648,dpi,96),MulDiv(6,dpi,96),
MulDiv(78,dpi,96),MulDiv(32,dpi,96),TRUE);
c.panelHeight=c.unit+MulDiv(40,dpi,96);
for(int id:{30,31,32,33,34,35}) SendDlgItemMessageW(w,id,WM_SETFONT,(WPARAM)c.font,TRUE);
c.panelHeight=c.unit+MulDiv(80,dpi,96);
for(int id:{30,31,32,33,34,35,41,42,43,44}) SendDlgItemMessageW(w,id,WM_SETFONT,(WPARAM)c.font,TRUE);
MoveWindow(GetDlgItem(w,42),MulDiv(12,dpi,96),c.unit+MulDiv(44,dpi,96),MulDiv(128,dpi,96),MulDiv(24,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,41),MulDiv(142,dpi,96),c.unit+MulDiv(40,dpi,96),MulDiv(200,dpi,96),MulDiv(30,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,43),MulDiv(350,dpi,96),c.unit+MulDiv(44,dpi,96),MulDiv(150,dpi,96),MulDiv(24,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,44),MulDiv(516,dpi,96),c.unit+MulDiv(40,dpi,96),MulDiv(210,dpi,96),MulDiv(30,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,32),MulDiv(12,dpi,96),c.unit+MulDiv(6,dpi,96),MulDiv(64,dpi,96),MulDiv(24,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,30),MulDiv(80,dpi,96),c.unit+MulDiv(2,dpi,96),MulDiv(150,dpi,96),MulDiv(180,dpi,96),TRUE);
MoveWindow(GetDlgItem(w,33),MulDiv(248,dpi,96),c.unit+MulDiv(6,dpi,96),MulDiv(40,dpi,96),MulDiv(24,dpi,96),TRUE);
@@ -1072,6 +1085,14 @@ void edit(HWND w, Capture &c)
SetWindowSubclass(labelButton,toolHover,1,0);
fontTip.uId=(UINT_PTR)labelButton; fontTip.lpszText=const_cast<wchar_t *>(L"Arrow label (F2 / double-click); drag label to reposition");
SendMessageW(tips,TTM_ADDTOOLW,0,(LPARAM)&fontTip);
INITCOMMONCONTROLSEX sliders{sizeof(sliders),ICC_BAR_CLASSES}; InitCommonControlsEx(&sliders);
CreateWindowW(TRACKBAR_CLASSW,L"Pencil smoothing",WS_CHILD|WS_VISIBLE|WS_TABSTOP|TBS_NOTICKS,
0,0,0,0,w,(HMENU)41,c.app.instance,nullptr);
SendDlgItemMessageW(w,41,TBM_SETRANGE,TRUE,MAKELPARAM(0,100));
SendDlgItemMessageW(w,41,TBM_SETPAGESIZE,0,10);
for(int id:{42,43}) CreateWindowW(L"STATIC",L"",WS_CHILD|WS_VISIBLE,0,0,0,0,w,(HMENU)(INT_PTR)id,c.app.instance,nullptr);
CreateWindowW(L"BUTTON",L"Apply to all strokes",WS_CHILD|WS_VISIBLE|WS_TABSTOP|BS_PUSHBUTTON,
0,0,0,0,w,(HMENU)44,c.app.instance,nullptr);
c.syncHistory();
auto work = monitor.rcWork;
SetWindowPos(w,HWND_NOTOPMOST,work.left+20,work.top+20,
@@ -1140,6 +1161,21 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
case WM_SETCURSOR:
if ((HWND)wp==w && LOWORD(lp)==HTCLIENT) { captureCursor(w,*c); return TRUE; }
break;
case WM_HSCROLL:
if((HWND)lp==GetDlgItem(w,41) && !c->dragging) {
int amount=int(SendDlgItemMessageW(w,41,TBM_GETPOS,0,0));
bool pencil=c->selected>=0 && c->marks[c->selected].freehand;
if(pencil && c->marks[c->selected].smoothing!=amount) {
if(!c->smoothingDrag) c->remember(c->marks);
setScreenshotSmoothing(c->marks[c->selected],amount);
resolveScreenshotLinks(c->marks);
c->smoothingDrag=LOWORD(wp)==TB_THUMBTRACK;
} else if(!pencil) c->smoothing=amount;
if(LOWORD(wp)!=TB_THUMBTRACK) c->smoothingDrag=false;
syncLineOptions(w,*c); InvalidateRect(w,nullptr,FALSE);
return 0;
}
break;
case WM_COMMAND:
if(LOWORD(wp)==40) {
if(c->inlineText && HIWORD(wp)==EN_CHANGE) { readInlineText(*c); positionInlineText(*c); }
@@ -1147,6 +1183,17 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
return 0;
}
if (c->dragging) return 0;
if(LOWORD(wp)==44) {
int amount=int(SendDlgItemMessageW(w,41,TBM_GETPOS,0,0));
c->smoothing=amount;
if(std::any_of(c->marks.begin(),c->marks.end(),[&](const Mark &m){return m.freehand && m.smoothing!=amount;})) {
c->remember(c->marks);
for(auto &mark:c->marks) if(mark.freehand) setScreenshotSmoothing(mark,amount);
resolveScreenshotLinks(c->marks);
}
c->smoothingDrag=false; syncLineOptions(w,*c); InvalidateRect(w,nullptr,FALSE);
return 0;
}
if(LOWORD(wp)==35) {
finishInlineText(w,*c,true);
if(c->selected>=0 && c->marks[c->selected].isLine()) beginMarkText(w,*c,c->selected);
@@ -1543,6 +1590,7 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
c->remember(c->marks); c->marks.push_back(std::move(c->stroke));
if(c->freehand) {
auto &mark=c->marks.back();
mark.rawPoints=mark.points;
linkScreenshotEndpoint(mark,true,c->start,c->marks,6);
linkScreenshotEndpoint(mark,false,c->end,c->marks,6);
resolveScreenshotLinks(c->marks);
@@ -1553,10 +1601,18 @@ LRESULT CALLBACK captureProc(HWND w, UINT msg, WPARAM wp, LPARAM lp)
ScreenshotShape recognized{};
bool straightLine=false;
auto improved=improveScreenshotStroke(c->marks.back().points,&recognized,&straightLine);
if(recognized==ScreenshotShape::None && !straightLine) {
improved=smoothScreenshotStroke(c->marks.back().rawPoints,c->smoothing);
const auto &raw=c->marks.back().rawPoints;
if(improved.size()==raw.size() && std::equal(improved.begin(),improved.end(),raw.begin(),
[](POINT a,POINT b){return a.x==b.x && a.y==b.y;})) improved.clear();
}
if(!improved.empty()) {
c->remember(c->marks);
auto &mark=c->marks.back();
mark.points=std::move(improved);
if(recognized==ScreenshotShape::None && !straightLine) {
setScreenshotSmoothing(mark,c->smoothing);
} else { mark.points=std::move(improved); mark.rawPoints.clear(); }
mark.from=mark.points.front(); mark.to=mark.points.back();
if(straightLine) { mark.points.clear(); mark.freehand=false; }
if(recognized!=ScreenshotShape::None) {
@@ -1914,7 +1970,14 @@ void resolveScreenshotLinks(std::vector<ScreenshotMark> &marks)
POINT start=from?from->from:m.from, end=to?to->from:m.to;
if(from) m.from=rim(*from,end,true);
if(to) m.to=rim(*to,start,false);
if(m.freehand && !m.points.empty()) { m.points.front()=m.from; m.points.back()=m.to; }
if(m.freehand && !m.points.empty()) {
m.points.front()=m.from; m.points.back()=m.to;
if(!m.rawPoints.empty() && (m.rawPoints.front().x!=m.from.x || m.rawPoints.front().y!=m.from.y ||
m.rawPoints.back().x!=m.to.x || m.rawPoints.back().y!=m.to.y)) {
m.rawPoints.front()=m.from; m.rawPoints.back()=m.to;
setScreenshotSmoothing(m,m.smoothing);
}
}
}
state[index]=2;
};
@@ -2068,10 +2131,15 @@ ScreenshotMark dragScreenshotMark(const ScreenshotMark &original, MarkPart part,
return low<=high?std::clamp(delta,low,high):0L;
};
auto bounds=markBounds(m);
for(auto p:m.rawPoints) {
bounds.left=std::min(bounds.left,p.x); bounds.right=std::max(bounds.right,p.x);
bounds.top=std::min(bounds.top,p.y); bounds.bottom=std::max(bounds.bottom,p.y);
}
LONG dx=shift(position.x-origin.x,bounds.left,bounds.right,width);
LONG dy=shift(position.y-origin.y,bounds.top,bounds.bottom,height);
m.from.x+=dx; m.to.x+=dx; m.from.y+=dy; m.to.y+=dy;
for(auto &point:m.points) { point.x+=dx; point.y+=dy; }
for(auto &point:m.rawPoints) { point.x+=dx; point.y+=dy; }
}
else if(part==MarkPart::Start || part==MarkPart::End)
{
@@ -2096,6 +2164,10 @@ ScreenshotMark dragScreenshotMark(const ScreenshotMark &original, MarkPart part,
}
}
if(m.freehand && !m.points.empty()) (part==MarkPart::Start?m.points.front():m.points.back())=endpoint;
if(m.freehand && !m.rawPoints.empty()) {
(part==MarkPart::Start?m.rawPoints.front():m.rawPoints.back())=endpoint;
setScreenshotSmoothing(m,m.smoothing);
}
if(m.shape!=ScreenshotShape::None && m.rotation!=0) {
auto fixedBefore=rotateShapePoint(original,part==MarkPart::Start?original.to:original.from);
auto fixedAfter=rotateShapePoint(m,part==MarkPart::Start?m.to:m.from);