1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Window procedure of the tool settings window
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  *             Copyright 2018 Stanislav Motylkov <x86corez@gmail.com>
7  *             Copyright 2021-2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
8  */
9 
10 /* INCLUDES *********************************************************/
11 
12 #include "precomp.h"
13 
14 #define X_TOOLSETTINGS  0
15 #define Y_TOOLSETTINGS  (CY_TOOLBAR + 3)
16 #define CX_TOOLSETTINGS CX_TOOLBAR
17 #define CY_TOOLSETTINGS 140
18 
19 #define CX_TRANS_ICON 40
20 #define CY_TRANS_ICON 30
21 #define MARGIN1 3
22 #define MARGIN2 2
23 
24 #define MAX_ZOOM_TRACK 6
25 #define MIN_ZOOM_TRACK 0
26 #define DEFAULT_ZOOM_TRACK 3
27 
28 static const BYTE s_AirRadius[4] = { 5, 8, 3, 12 };
29 
30 CToolSettingsWindow toolSettingsWindow;
31 
32 /* FUNCTIONS ********************************************************/
33 
34 BOOL CToolSettingsWindow::DoCreate(HWND hwndParent)
35 {
36     RECT toolSettingsWindowPos =
37     {
38         X_TOOLSETTINGS, Y_TOOLSETTINGS,
39         X_TOOLSETTINGS + CX_TOOLSETTINGS, Y_TOOLSETTINGS + CY_TOOLSETTINGS
40     };
41     return !!Create(toolBoxContainer, toolSettingsWindowPos, NULL, WS_CHILD | WS_VISIBLE);
42 }
43 
44 static INT
45 getSplitRects(RECT *rects, INT cColumns, INT cRows, LPCRECT prc, LPPOINT ppt)
46 {
47     INT cx = prc->right - prc->left, cy = prc->bottom - prc->top;
48     for (INT i = 0, iRow = 0; iRow < cRows; ++iRow)
49     {
50         for (INT iColumn = 0; iColumn < cColumns; ++iColumn)
51         {
52             RECT& rc = rects[i];
53             rc.left = prc->left + (iColumn * cx / cColumns);
54             rc.top = prc->top + (iRow * cy / cRows);
55             rc.right = prc->left + ((iColumn + 1) * cx / cColumns);
56             rc.bottom = prc->top + ((iRow + 1) * cy / cRows);
57             if (ppt && ::PtInRect(&rc, *ppt))
58                 return i;
59             ++i;
60         }
61     }
62     return -1;
63 }
64 
65 static inline INT getTransRects(RECT rects[2], LPCRECT prc, LPPOINT ppt = NULL)
66 {
67     return getSplitRects(rects, 1, 2, prc, ppt);
68 }
69 
70 VOID CToolSettingsWindow::drawTrans(HDC hdc, LPCRECT prc)
71 {
72     RECT rc[2];
73     getTransRects(rc, prc);
74 
75     ::FillRect(hdc, &rc[toolsModel.IsBackgroundTransparent()], (HBRUSH)(COLOR_HIGHLIGHT + 1));
76     ::DrawIconEx(hdc, rc[0].left, rc[0].top, m_hNontranspIcon,
77                  CX_TRANS_ICON, CY_TRANS_ICON, 0, NULL, DI_NORMAL);
78     ::DrawIconEx(hdc, rc[1].left, rc[1].top, m_hTranspIcon,
79                  CX_TRANS_ICON, CY_TRANS_ICON, 0, NULL, DI_NORMAL);
80 }
81 
82 static inline INT getRubberRects(RECT rects[4], LPCRECT prc, LPPOINT ppt = NULL)
83 {
84     return getSplitRects(rects, 1, 4, prc, ppt);
85 }
86 
87 VOID CToolSettingsWindow::drawRubber(HDC hdc, LPCRECT prc)
88 {
89     RECT rects[4], rcRubber;
90     getRubberRects(rects, prc);
91     INT xCenter = (prc->left + prc->right) / 2;
92     for (INT i = 0; i < 4; i++)
93     {
94         INT iColor, radius = i + 2;
95         if (toolsModel.GetRubberRadius() == radius)
96         {
97             ::FillRect(hdc, &rects[i], ::GetSysColorBrush(COLOR_HIGHLIGHT));
98             iColor = COLOR_HIGHLIGHTTEXT;
99         }
100         else
101         {
102             iColor = COLOR_WINDOWTEXT;
103         }
104 
105         INT yCenter = (rects[i].top + rects[i].bottom) / 2;
106         rcRubber.left = xCenter - radius;
107         rcRubber.top = yCenter - radius;
108         rcRubber.right = rcRubber.left + radius * 2;
109         rcRubber.bottom = rcRubber.top + radius * 2;
110         ::FillRect(hdc, &rcRubber, GetSysColorBrush(iColor));
111     }
112 }
113 
114 static inline INT getBrushRects(RECT rects[12], LPCRECT prc, LPPOINT ppt = NULL)
115 {
116     return getSplitRects(rects, 3, 4, prc, ppt);
117 }
118 
119 struct BrushStyleAndWidth
120 {
121     BrushStyle style;
122     INT width;
123 };
124 
125 static const BrushStyleAndWidth c_BrushPresets[] =
126 {
127     { BrushStyleRound,     7 }, { BrushStyleRound,     4 }, { BrushStyleRound,     1 },
128     { BrushStyleSquare,    8 }, { BrushStyleSquare,    5 }, { BrushStyleSquare,    2 },
129     { BrushStyleForeSlash, 8 }, { BrushStyleForeSlash, 5 }, { BrushStyleForeSlash, 2 },
130     { BrushStyleBackSlash, 8 }, { BrushStyleBackSlash, 5 }, { BrushStyleBackSlash, 2 },
131 };
132 
133 VOID CToolSettingsWindow::drawBrush(HDC hdc, LPCRECT prc)
134 {
135     RECT rects[12];
136     getBrushRects(rects, prc);
137 
138     for (INT i = 0; i < 12; i++)
139     {
140         RECT rcItem = rects[i];
141         INT x = (rcItem.left + rcItem.right) / 2, y = (rcItem.top + rcItem.bottom) / 2;
142 
143         INT iColor;
144         const BrushStyleAndWidth& data = c_BrushPresets[i];
145         if (data.width == toolsModel.GetBrushWidth() && data.style == toolsModel.GetBrushStyle())
146         {
147             iColor = COLOR_HIGHLIGHTTEXT;
148             ::FillRect(hdc, &rcItem, (HBRUSH)(COLOR_HIGHLIGHT + 1));
149         }
150         else
151         {
152             iColor = COLOR_WINDOWTEXT;
153         }
154 
155         Brush(hdc, x, y, x, y, ::GetSysColor(iColor), data.style, data.width);
156     }
157 }
158 
159 static inline INT getLineRects(RECT rects[5], LPCRECT prc, LPPOINT ppt = NULL)
160 {
161     return getSplitRects(rects, 1, 5, prc, ppt);
162 }
163 
164 VOID CToolSettingsWindow::drawLine(HDC hdc, LPCRECT prc)
165 {
166     RECT rects[5];
167     getLineRects(rects, prc);
168 
169     for (INT i = 0; i < 5; i++)
170     {
171         INT penWidth = i + 1;
172         CRect rcLine = rects[i];
173         rcLine.InflateRect(-2, 0);
174         rcLine.top = (rcLine.top + rcLine.bottom - penWidth) / 2;
175         rcLine.bottom = rcLine.top + penWidth;
176         if (toolsModel.GetLineWidth() == penWidth)
177         {
178             ::FillRect(hdc, &rects[i], ::GetSysColorBrush(COLOR_HIGHLIGHT));
179             ::FillRect(hdc, &rcLine, ::GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
180         }
181         else
182         {
183             ::FillRect(hdc, &rcLine, ::GetSysColorBrush(COLOR_WINDOWTEXT));
184         }
185     }
186 }
187 
188 static INT getAirBrushRects(RECT rects[4], LPCRECT prc, LPPOINT ppt = NULL)
189 {
190     INT cx = (prc->right - prc->left), cy = (prc->bottom - prc->top);
191 
192     rects[0] = rects[1] = rects[2] = rects[3] = *prc;
193 
194     rects[0].right = rects[1].left = prc->left + cx * 3 / 8;
195     rects[0].bottom = rects[1].bottom = prc->top + cy / 2;
196 
197     rects[2].top = rects[3].top = prc->top + cy / 2;
198     rects[2].right = rects[3].left = prc->left + cx * 2 / 8;
199 
200     if (ppt)
201     {
202         for (INT i = 0; i < 4; ++i)
203         {
204             if (::PtInRect(&rects[i], *ppt))
205                 return i;
206         }
207     }
208     return -1;
209 }
210 
211 VOID CToolSettingsWindow::drawAirBrush(HDC hdc, LPCRECT prc)
212 {
213     RECT rects[4];
214     getAirBrushRects(rects, prc);
215 
216     srand(0);
217     for (size_t i = 0; i < 4; ++i)
218     {
219         RECT& rc = rects[i];
220         INT x = (rc.left + rc.right) / 2;
221         INT y = (rc.top + rc.bottom) / 2;
222         BOOL bHigh = (s_AirRadius[i] == toolsModel.GetAirBrushRadius());
223         if (bHigh)
224         {
225             ::FillRect(hdc, &rc, ::GetSysColorBrush(COLOR_HIGHLIGHT));
226 
227             for (int k = 0; k < 3; ++k)
228                 Airbrush(hdc, x, y, ::GetSysColor(COLOR_HIGHLIGHTTEXT), s_AirRadius[i]);
229         }
230         else
231         {
232             for (int k = 0; k < 3; ++k)
233                 Airbrush(hdc, x, y, ::GetSysColor(COLOR_WINDOWTEXT), s_AirRadius[i]);
234         }
235     }
236 }
237 
238 static inline INT getBoxRects(RECT rects[3], LPCRECT prc, LPPOINT ppt = NULL)
239 {
240     return getSplitRects(rects, 1, 3, prc, ppt);
241 }
242 
243 VOID CToolSettingsWindow::drawBox(HDC hdc, LPCRECT prc)
244 {
245     CRect rects[3];
246     getBoxRects(rects, prc);
247 
248     for (INT iItem = 0; iItem < 3; ++iItem)
249     {
250         CRect& rcItem = rects[iItem];
251 
252         if (toolsModel.GetShapeStyle() == iItem)
253             ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHT));
254 
255         rcItem.InflateRect(-5, -5);
256 
257         if (iItem <= 1)
258         {
259             COLORREF rgbPen;
260             if (toolsModel.GetShapeStyle() == iItem)
261                 rgbPen = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
262             else
263                 rgbPen = ::GetSysColor(COLOR_WINDOWTEXT);
264             HGDIOBJ hOldBrush;
265             if (iItem == 0)
266                 hOldBrush = ::SelectObject(hdc, ::GetStockObject(NULL_BRUSH));
267             else
268                 hOldBrush = ::SelectObject(hdc, ::GetSysColorBrush(COLOR_APPWORKSPACE));
269             HGDIOBJ hOldPen = ::SelectObject(hdc, ::CreatePen(PS_SOLID, 1, rgbPen));
270             ::Rectangle(hdc, rcItem.left, rcItem.top, rcItem.right, rcItem.bottom);
271             ::DeleteObject(::SelectObject(hdc, hOldPen));
272             ::SelectObject(hdc, hOldBrush);
273         }
274         else
275         {
276             if (toolsModel.GetShapeStyle() == iItem)
277                 ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_HIGHLIGHTTEXT));
278             else
279                 ::FillRect(hdc, &rcItem, ::GetSysColorBrush(COLOR_WINDOWTEXT));
280         }
281     }
282 }
283 
284 LRESULT CToolSettingsWindow::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
285 {
286     /* preloading the draw transparent/nontransparent icons for later use */
287     m_hNontranspIcon = (HICON)LoadImageW(g_hinstExe, MAKEINTRESOURCEW(IDI_NONTRANSPARENT),
288                                          IMAGE_ICON, CX_TRANS_ICON, CY_TRANS_ICON, LR_DEFAULTCOLOR);
289     m_hTranspIcon = (HICON)LoadImageW(g_hinstExe, MAKEINTRESOURCEW(IDI_TRANSPARENT),
290                                       IMAGE_ICON, CX_TRANS_ICON, CY_TRANS_ICON, LR_DEFAULTCOLOR);
291 
292     CRect trackbarZoomPos, rect2;
293     calculateTwoBoxes(trackbarZoomPos, rect2);
294     trackbarZoomPos.InflateRect(-1, -1);
295 
296     trackbarZoom.Create(TRACKBAR_CLASS, m_hWnd, trackbarZoomPos, NULL, WS_CHILD | TBS_VERT | TBS_AUTOTICKS);
297     trackbarZoom.SendMessage(TBM_SETRANGE, TRUE, MAKELPARAM(MIN_ZOOM_TRACK, MAX_ZOOM_TRACK));
298     trackbarZoom.SendMessage(TBM_SETPOS, TRUE, DEFAULT_ZOOM_TRACK);
299     return 0;
300 }
301 
302 LRESULT CToolSettingsWindow::OnDestroy(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
303 {
304     ::DestroyIcon(m_hNontranspIcon);
305     ::DestroyIcon(m_hTranspIcon);
306     return 0;
307 }
308 
309 LRESULT CToolSettingsWindow::OnVScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
310 {
311     INT trackPos = MAX_ZOOM_TRACK - (INT)trackbarZoom.SendMessage(TBM_GETPOS, 0, 0);
312     canvasWindow.zoomTo(MIN_ZOOM << trackPos);
313 
314     INT zoomRate = toolsModel.GetZoom();
315 
316     CStringW strZoom;
317     if (zoomRate % 10 == 0)
318         strZoom.Format(L"%d%%", zoomRate / 10);
319     else
320         strZoom.Format(L"%d.%d%%", zoomRate / 10, zoomRate % 10);
321 
322     ::SendMessageW(g_hStatusBar, SB_SETTEXT, 1, (LPARAM)(LPCWSTR)strZoom);
323 
324     OnToolsModelZoomChanged(nMsg, wParam, lParam, bHandled);
325     return 0;
326 }
327 
328 LRESULT CToolSettingsWindow::OnNotify(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
329 {
330     NMHDR *pnmhdr = (NMHDR*)lParam;
331     if (pnmhdr->code == NM_CUSTOMDRAW)
332     {
333         NMCUSTOMDRAW *pCustomDraw = (NMCUSTOMDRAW*)pnmhdr;
334         pCustomDraw->uItemState &= ~CDIS_FOCUS; // Do not draw the focus
335     }
336     return 0;
337 }
338 
339 VOID CToolSettingsWindow::calculateTwoBoxes(CRect& rect1, CRect& rect2)
340 {
341     CRect rcClient;
342     GetClientRect(&rcClient);
343     rcClient.InflateRect(-MARGIN1, -MARGIN1);
344 
345     INT yCenter = (rcClient.top + rcClient.bottom) / 2;
346     rect1.SetRect(rcClient.left, rcClient.top, rcClient.right, yCenter);
347     rect2.SetRect(rcClient.left, yCenter, rcClient.right, rcClient.bottom);
348 
349     rect1.InflateRect(-MARGIN2, -MARGIN2);
350     rect2.InflateRect(-MARGIN2, -MARGIN2);
351 }
352 
353 LRESULT CToolSettingsWindow::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
354 {
355     CRect rect1, rect2;
356     calculateTwoBoxes(rect1, rect2);
357 
358     PAINTSTRUCT ps;
359     HDC hdc = BeginPaint(&ps);
360 
361     if (toolsModel.GetActiveTool() != TOOL_ZOOM)
362         ::DrawEdge(hdc, &rect1, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
363 
364     if (toolsModel.GetActiveTool() >= TOOL_RECT)
365         ::DrawEdge(hdc, &rect2, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
366 
367     rect1.InflateRect(-MARGIN2, -MARGIN2);
368     rect2.InflateRect(-MARGIN2, -MARGIN2);
369     switch (toolsModel.GetActiveTool())
370     {
371         case TOOL_FREESEL:
372         case TOOL_RECTSEL:
373         case TOOL_TEXT:
374             drawTrans(hdc, &rect1);
375             break;
376         case TOOL_RUBBER:
377             drawRubber(hdc, &rect1);
378             break;
379         case TOOL_BRUSH:
380             drawBrush(hdc, &rect1);
381             break;
382         case TOOL_AIRBRUSH:
383             drawAirBrush(hdc, &rect1);
384             break;
385         case TOOL_LINE:
386         case TOOL_BEZIER:
387             drawLine(hdc, &rect1);
388             break;
389         case TOOL_RECT:
390         case TOOL_SHAPE:
391         case TOOL_ELLIPSE:
392         case TOOL_RRECT:
393             drawBox(hdc, &rect1);
394             drawLine(hdc, &rect2);
395             break;
396         case TOOL_FILL:
397         case TOOL_COLOR:
398         case TOOL_ZOOM:
399         case TOOL_PEN:
400             break;
401     }
402     EndPaint(&ps);
403     return 0;
404 }
405 
406 LRESULT CToolSettingsWindow::OnLButtonDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
407 {
408     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
409 
410     CRect rect1, rect2;
411     calculateTwoBoxes(rect1, rect2);
412     RECT rects[12];
413 
414     INT iItem;
415     switch (toolsModel.GetActiveTool())
416     {
417         case TOOL_FREESEL:
418         case TOOL_RECTSEL:
419         case TOOL_TEXT:
420             iItem = getTransRects(rects, &rect1, &pt);
421             if (iItem != -1)
422                 toolsModel.SetBackgroundTransparent(iItem);
423             break;
424         case TOOL_RUBBER:
425             iItem = getRubberRects(rects, &rect1, &pt);
426             if (iItem != -1)
427                 toolsModel.SetRubberRadius(iItem + 2);
428             break;
429         case TOOL_BRUSH:
430             iItem = getBrushRects(rects, &rect1, &pt);
431             if (iItem != -1)
432             {
433                 const BrushStyleAndWidth& data = c_BrushPresets[iItem];
434                 toolsModel.SetBrushStyle(data.style);
435                 toolsModel.SetBrushWidth(data.width);
436             }
437             break;
438         case TOOL_AIRBRUSH:
439             iItem = getAirBrushRects(rects, &rect1, &pt);
440             if (iItem != -1)
441                 toolsModel.SetAirBrushRadius(s_AirRadius[iItem]);
442             break;
443         case TOOL_LINE:
444         case TOOL_BEZIER:
445             iItem = getLineRects(rects, &rect1, &pt);
446             if (iItem != -1)
447                 toolsModel.SetLineWidth(iItem + 1);
448             break;
449         case TOOL_RECT:
450         case TOOL_SHAPE:
451         case TOOL_ELLIPSE:
452         case TOOL_RRECT:
453             iItem = getBoxRects(rects, &rect1, &pt);
454             if (iItem != -1)
455                 toolsModel.SetShapeStyle(iItem);
456 
457             iItem = getLineRects(rects, &rect2, &pt);
458             if (iItem != -1)
459                 toolsModel.SetLineWidth(iItem + 1);
460             break;
461         case TOOL_FILL:
462         case TOOL_COLOR:
463         case TOOL_ZOOM:
464         case TOOL_PEN:
465             break;
466     }
467 
468     ::SetCapture(::GetParent(m_hWnd));
469     return 0;
470 }
471 
472 LRESULT CToolSettingsWindow::OnToolsModelToolChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
473 {
474     Invalidate();
475     trackbarZoom.ShowWindow((wParam == TOOL_ZOOM) ? SW_SHOW : SW_HIDE);
476     return 0;
477 }
478 
479 LRESULT CToolSettingsWindow::OnToolsModelSettingsChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
480 {
481     Invalidate();
482     return 0;
483 }
484 
485 LRESULT CToolSettingsWindow::OnToolsModelZoomChanged(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
486 {
487     int tbPos = MIN_ZOOM_TRACK;
488     int tempZoom = toolsModel.GetZoom();
489 
490     while (tempZoom > MIN_ZOOM)
491     {
492         tbPos++;
493         tempZoom = tempZoom >> 1;
494     }
495 
496     trackbarZoom.SendMessage(TBM_SETPOS, TRUE, MAX_ZOOM_TRACK - tbPos);
497     return 0;
498 }
499