Mercurial > foo_out_sdl
diff foosdk/sdk/libPPUI/PaintUtils.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/libPPUI/PaintUtils.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,523 @@ +#include "stdafx.h" +#include <vsstyle.h> + +#include "PaintUtils.h" +#include "gdiplus_helpers.h" + +#include "GDIUtils.h" +#include "win32_op.h" +#include "wtl-pp.h" + +namespace PaintUtils { + static t_uint16 extractChannel16(t_uint32 p_color,int p_which) throw() { + return (t_uint16)( ((p_color >> (p_which * 8)) & 0xFF) << 8 ); + } + + static t_uint8 extractbyte(t_uint32 p_val,t_size p_which) throw() { + return (t_uint8) ( (p_val >> (p_which * 8)) & 0xFF ); + } + + t_uint32 BlendColorEx(t_uint32 p_color1, t_uint32 p_color2, double mix) throw() { + PFC_ASSERT(mix >= 0 && mix <= 1); + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk); + int val = val1 + pfc::rint32((val2 - val1) * mix); + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + t_uint32 BlendColor(t_uint32 p_color1, t_uint32 p_color2, int p_percentage) throw() { + PFC_ASSERT(p_percentage <= 100); + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + int val1 = extractbyte(p_color1,walk), val2 = extractbyte(p_color2,walk); + int val = val1 + MulDiv(val2 - val1,p_percentage,100); + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + t_uint32 DriftColor(t_uint32 p_color,unsigned p_delta,bool p_direction) throw() { + t_uint32 ret = 0; + for(t_size walk = 0; walk < 3; ++walk) { + unsigned val = extractbyte(p_color,walk); + if (p_direction) val = 0xFF - val; + if (val < p_delta) val = p_delta; + val += p_delta; + if (val > 0xFF) val = 0xFF; + if (p_direction) val = 0xFF - val; + ret |= (t_uint32)val << (walk * 8); + } + return ret; + } + + void FillVertexColor(TRIVERTEX & p_vertex,t_uint32 p_color,t_uint16 p_alpha) throw() { + p_vertex.Red = extractChannel16(p_color,0); + p_vertex.Green = extractChannel16(p_color,1); + p_vertex.Blue = extractChannel16(p_color,2); + p_vertex.Alpha = p_alpha; + } + + void FillRectSimple(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color) throw() { + p_dc.FillSolidRect(p_rect, p_color); + } + + void GradientFillRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_color1, t_uint32 p_color2, bool p_horizontal) throw() { + TRIVERTEX verticies[2]; + GRADIENT_RECT element = {0,1}; + FillVertexColor(verticies[0],p_color1); + FillVertexColor(verticies[1],p_color2); + verticies[0].x = p_rect.left; verticies[0].y = p_rect.top; + verticies[1].x = p_rect.right; verticies[1].y = p_rect.bottom; + p_dc.GradientFill(verticies,tabsize(verticies),&element,1,p_horizontal ? GRADIENT_FILL_RECT_H : GRADIENT_FILL_RECT_V); + } + + void GradientSplitRect(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_bkColor, t_uint32 p_gradientColor,int p_splitPercent) throw() { + const long split = p_rect.top + MulDiv(p_rect.Height(),p_splitPercent,100); + CRect rcTemp; + rcTemp = p_rect; + rcTemp.bottom = split; + GradientFillRect(p_dc,rcTemp,p_bkColor,p_gradientColor,false); + rcTemp = p_rect; + rcTemp.top = split; + GradientFillRect(p_dc,rcTemp,p_gradientColor,p_bkColor,false); + } + + void GradientBar(CDCHandle p_dc,const CRect & p_rect,t_uint32 p_exterior, t_uint32 p_interior, int p_percentage) throw() { + const int gradientPix = MulDiv(p_rect.Height(),p_percentage,100); + CRect rcTemp; + + rcTemp = p_rect; + rcTemp.bottom = rcTemp.top + gradientPix; + GradientFillRect(p_dc,rcTemp,p_exterior,p_interior,false); + + rcTemp = p_rect; + rcTemp.top += gradientPix; rcTemp.bottom -= gradientPix; + FillRectSimple(p_dc,rcTemp,p_interior); + + rcTemp = p_rect; + rcTemp.top = rcTemp.bottom - gradientPix; + GradientFillRect(p_dc,rcTemp,p_interior,p_exterior,false); + } + + void RenderItemBackground(CDCHandle p_dc,const CRect & p_itemRect,t_size p_item,t_uint32 p_color) throw() { + const DWORD bkColor_base = p_color; + const DWORD bkColor = DriftColor(bkColor_base,3, (p_item&1) != 0); + + //GradientSplitRect(p_dc,p_itemRect,bkColor,BlendColor(bkColor,textColor,7),80); + GradientBar(p_dc,p_itemRect,bkColor_base,bkColor,10); + } + + double Luminance(t_uint32 color) throw() { + double r = extractbyte(color,0), g = extractbyte(color,1), b = extractbyte(color,2); + return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0; + //return (r * 0.3 + g * 0.59 + b * 0.11) / 255.0; + } + t_uint32 DetermineTextColor(t_uint32 bk) throw() { + double l = Luminance(bk); + if ( l > 0.6 ) { + return 0; // black + } else { + return 0xFFFFFF; // white + } + } + + void AddRectToRgn(HRGN p_rgn,CRect const & p_rect) throw() { + CRgn temp; + WIN32_OP_D( temp.CreateRectRgnIndirect(p_rect) != NULL ); + CRgnHandle(p_rgn).CombineRgn(temp,RGN_OR); + } + + void FocusRect2(CDCHandle dc, CRect const & rect, COLORREF bkColor) throw() { + COLORREF txColor = DetermineTextColor( bkColor ); + COLORREF useColor = BlendColor(bkColor, txColor, 50); + CDCBrush brush(dc, useColor); + WIN32_OP_D( dc.FrameRect(rect,brush) ); + } + void FocusRect(CDCHandle dc, CRect const & rect) throw() { + CDCBrush brush(dc, 0x7F7F7F); + WIN32_OP_D( dc.FrameRect(rect,brush) ); + //dc.DrawFocusRect(rect); + } + + namespace TrackBar { + void DrawThumb(HTHEME theme,HDC dc,int state,const RECT * rcThumb, const RECT * rcUpdate) { + if (theme == NULL) { + RECT blah = *rcThumb; + int flags = DFCS_BUTTONPUSH; + switch(state) { + case TUS_NORMAL: + break; + case TUS_DISABLED: + flags |= DFCS_INACTIVE; + break; + case TUS_PRESSED: + flags |= DFCS_PUSHED; + break; + } + DrawFrameControl(dc,&blah,DFC_BUTTON,flags); + } else { + DrawThemeBackground(theme,dc,TKP_THUMB,state,rcThumb,rcUpdate); + } + } + void DrawTrack(HTHEME theme,HDC dc,const RECT * rcTrack, const RECT * rcUpdate) { + if (theme == NULL) { + RECT blah = *rcTrack; + DrawFrameControl(dc,&blah,DFC_BUTTON,DFCS_BUTTONPUSH|DFCS_PUSHED); + } else { + DrawThemeBackground(theme,dc,TKP_TRACK,TKS_NORMAL,rcTrack,rcUpdate); + } + } + void DrawTrack2(HDC p_dc, const CRect& rcTrack, const CRect& rcUpdate, COLORREF clrHighlight, COLORREF clrShadow) { + CRect rc(*rcTrack); +#if 1 + CDCHandle dc(p_dc); + SelectObjectScope scope(dc, GetStockObject(DC_PEN)); + dc.SetDCPenColor(clrHighlight); + dc.MoveTo(rc.left, rc.bottom); + dc.LineTo(rc.right, rc.bottom); + dc.LineTo(rc.right, rc.top); + dc.SetDCPenColor(clrShadow); + dc.LineTo(rc.left, rc.top); + dc.LineTo(rc.left, rc.bottom); +#else + try { + Gdiplus::Point points[] = { Gdiplus::Point(rc.left, rc.bottom), Gdiplus::Point(rc.right, rc.bottom), Gdiplus::Point(rc.right, rc.top), Gdiplus::Point(rc.left, rc.top)}; + GdiplusErrorHandler eh; + Gdiplus::Graphics graphics(p_dc); + eh << graphics.GetLastStatus(); + Gdiplus::Color c; + c.SetFromCOLORREF(clrHighlight); + Gdiplus::Pen penHL(c); + c.SetFromCOLORREF(clrShadow); + Gdiplus::Pen penSH(c); + eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + eh << graphics.DrawLine(&penHL, points[0], points[1]); + eh << graphics.DrawLine(&penHL, points[1], points[2]); + eh << graphics.DrawLine(&penSH, points[2], points[3]); + eh << graphics.DrawLine(&penSH, points[3], points[0]); + } catch (std::exception const& e) { + (void)e; + PFC_ASSERT(!"???"); + // console::print(e.what()); + } +#endif + } + void DrawTrackVolume2(HDC p_dc, const CRect& rcTrack, const CRect& rcUpdate, COLORREF clrHighlight, COLORREF clrShadow) { + CRect rc(rcTrack); + + try { + Gdiplus::Point points[] = { Gdiplus::Point(rc.left, rc.bottom), Gdiplus::Point(rc.right, rc.bottom), Gdiplus::Point(rc.right, rc.top) }; + GdiplusErrorHandler eh; + Gdiplus::Graphics graphics(p_dc); + eh << graphics.GetLastStatus(); + Gdiplus::Color c; + c.SetFromCOLORREF(clrHighlight); + Gdiplus::Pen penHL(c); + c.SetFromCOLORREF(clrShadow); + Gdiplus::Pen penSH(c); + eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + //graphics.DrawPolygon(&pen,points,tabsize(points)); + eh << graphics.DrawLine(&penSH, points[0], points[0] + Gdiplus::Point(0, -1)); + eh << graphics.DrawLine(&penHL, points[0], points[1]); + eh << graphics.DrawLine(&penHL, points[1], points[2]); + eh << graphics.DrawLine(&penSH, points[2], points[0] + Gdiplus::Point(0, -1)); + } catch (std::exception const& e) { + (void)e; + PFC_ASSERT(!"???"); + // console::print(e.what()); + } + } + void DrawTrackVolume(HTHEME theme,HDC p_dc,const CRect & rcTrack, const CRect & rcUpdate) { + (void)theme; // disregarded + DrawTrackVolume2(p_dc, rcTrack, rcUpdate, GetSysColor(COLOR_BTNHIGHLIGHT), GetSysColor(COLOR_BTNSHADOW)); + } + } + + void DrawSmoothedLine(HDC dc, CPoint pt1, CPoint pt2, COLORREF col, double width) { + try { + Gdiplus::Point points[] = { Gdiplus::Point(pt1.x,pt1.y), Gdiplus::Point(pt2.x,pt2.y) }; + GdiplusErrorHandler eh; + Gdiplus::Graphics graphics(dc); + eh << graphics.GetLastStatus(); + Gdiplus::Color c; + c.SetFromCOLORREF( col ); + Gdiplus::Pen pen(c, (Gdiplus::REAL)( width )); + eh << graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality); + //graphics.DrawPolygon(&pen,points,tabsize(points)); + eh << graphics.DrawLine(&pen, points[0], points[1]); + } catch(std::exception const & e) { + (void) e; + PFC_ASSERT(!"???"); + // console::print(e.what()); + } + } + + + + static int get_text_width(HDC dc,const TCHAR * src,int len) { + if (len<=0) return 0; + else { + SIZE goatse; + GetTextExtentPoint32(dc,src,len,&goatse); + return goatse.cx; + } + } + + static t_uint32 TextOutColors_TranslateColor(const t_uint32 colors[3], int offset) { + const double v = (double)offset / 3.0; + if (v <= -1) return colors[0]; + else if (v < 0) return BlendColorEx(colors[0], colors[1], v + 1); + else if (v == 0) return colors[1]; + else if (v < 1) return BlendColorEx(colors[1], colors[2], v); + else return colors[2]; + } + + void TextOutColors_StripCodesAppend(pfc::string_formatter & out, const char * in) { + t_size done = 0, walk = 0; + for(;;) { + if (in[walk] == 0) { + if (walk > done) out.add_string_nc(in + done, walk - done); + return; + } + if ((unsigned)in[walk] < 32) { + if (walk > done) {out.add_string_nc(in + done, walk - done);} + done = walk + 1; + } + ++walk; + } + } + void TextOutColors_StripCodes(pfc::string_formatter & out, const char * in) { + out.reset(); TextOutColors_StripCodesAppend(out, in); + } + + static bool IsControlChar(TCHAR c) { + return (unsigned)c < 32; + } + static int MatchTruncat(HDC dc, int & pixels, const TCHAR * text, int textLen) { + int min = 0, max = textLen; + int minWidth = 0; + while(min + 1 < max) { + const int probe = (min + max) / 2; + CSize size; + WIN32_OP( GetTextExtentPoint32(dc, text, probe, &size) ); + if (size.cx <= pixels) {min = probe; minWidth = size.cx;} + else max = probe; + } + pixels = minWidth; + return min; + } + static int TruncatHeadroom(HDC dc) { + CSize size; + WIN32_OP( GetTextExtentPoint32(dc, _T("\x2026"), 1, &size) ); + return size.cx; + } + static void ExtTextOut_Truncat(HDC dc, int x, int y, CRect const & clip, const TCHAR * text, int textLen) { + int width = pfc::max_t<int>(0, clip.right - x - TruncatHeadroom(dc)); + int truncat = MatchTruncat(dc, width, text, textLen); + WIN32_OP( ExtTextOut(dc, x, y, ETO_CLIPPED, &clip, text, truncat, NULL) ); + WIN32_OP( ExtTextOut(dc, x + width, y, ETO_CLIPPED, &clip, _T("\x2026"), 1, NULL) ); + + + } + bool TextContainsCodes(const TCHAR * src) { + for(;;) { + if (*src == 0) return false; + if ((unsigned)*src < 32) return true; + ++src; + } + } + void TextOutColorsEx(HDC dc,const TCHAR * src,const CRect & target,DWORD flags,const t_uint32 colors[3]) { + if (!TextContainsCodes(src)) { + SetTextColorScope cs(dc, colors[1]); + CRect rc(target); + CDCHandle(dc).DrawText(src,(int)_tcslen(src),rc,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | flags); + } else { + const CSize textSize = PaintUtils::TextOutColors_CalcSize(dc, src); + CPoint origin = target.TopLeft(); + origin.y = (target.top + target.bottom - textSize.cy) / 2; + switch(flags & (DT_LEFT | DT_RIGHT | DT_CENTER)) { + case DT_LEFT: + break; + case DT_RIGHT: + if (textSize.cx < target.Width()) origin.x = target.right - textSize.cx; + break; + case DT_CENTER: + if (textSize.cx < target.Width()) origin.x = (target.right + target.left - textSize.cx) / 2; + break; + } + TextOutColors(dc, src, (int)_tcslen(src), origin, target, colors); + } + } + void TextOutColors(HDC dc,const TCHAR * src,int len,CPoint offset,const CRect & clip,const t_uint32 colors[3], int tabWidthTotal, int tabWidthDiv) { + SetTextAlign(dc,TA_LEFT); + SetBkMode(dc,TRANSPARENT); + + + int walk = 0; + int position = offset.x; + int colorOffset = 0; + int tabs = 0; + int positionTabDelta = 0; + + for(;;) { + int base = walk; + while(walk < len && !IsControlChar(src[walk])) ++walk; + if (walk>base) { + SetTextColor(dc,TextOutColors_TranslateColor(colors, colorOffset)); + int width = get_text_width(dc,src+base,walk-base); + if (position + width > clip.right) { + ExtTextOut_Truncat(dc, position, offset.y, clip, src + base, walk - base); + return; + } + WIN32_OP( ExtTextOut(dc,position,offset.y,ETO_CLIPPED,&clip,src+base,walk-base,0) ); + position += width; + } + if (walk>=len) break; + + while(walk < len && IsControlChar(src[walk])) { + if (src[walk] == TextOutColors_Dim) --colorOffset; + else if (src[walk] == TextOutColors_Highlight) ++colorOffset; + else if (src[walk] == '\t') { + int newDelta = MulDiv(++tabs, tabWidthTotal, tabWidthDiv); + position += newDelta - positionTabDelta; + positionTabDelta = newDelta; + } + walk++; + } + } + } + + CSize TextOutColors_CalcSize(HDC dc, const TCHAR * src) { + CSize acc(0,0); + for(int walk = 0;;) { + const int done = walk; + while(!IsControlChar(src[walk])) ++walk; + if (walk > done) { + CSize temp; + WIN32_OP( GetTextExtentPoint32(dc,src + done, walk - done, &temp) ); + acc.cx += temp.cx; pfc::max_acc(acc.cy, temp.cy); + } + if (src[walk] == 0) return acc; + while(src[walk] != 0 && IsControlChar(src[walk])) ++walk; + } + } + t_uint32 TextOutColors_CalcWidth(HDC dc, const TCHAR * src) { + t_uint32 acc = 0; + for(int walk = 0;;) { + const int done = walk; + while(!IsControlChar(src[walk])) ++walk; + acc += get_text_width(dc, src + done, walk - done); + if (src[walk] == 0) return acc; + while(src[walk] != 0 && IsControlChar(src[walk])) ++walk; + } + } + + pfc::string TextOutColors_ImportScript(pfc::string script) { + pfc::string_formatter temp; TextOutColors_ImportScript(temp, script.ptr()); return temp.get_ptr(); + } + void TextOutColors_ImportScript(pfc::string_base & out, const char * in) { + out.reset(); + for(;;) { + t_size delta; t_uint32 c; + delta = pfc::utf8_decode_char(in, c); + if (delta == 0) break; + switch(c) { + case '>': + c = PaintUtils::TextOutColors_Highlight; + break; + case '<': + c = PaintUtils::TextOutColors_Dim; + break; + } + out.add_char(c); + in += delta; + } + } + t_uint32 DrawText_TranslateHeaderAlignment(t_uint32 val) { + switch(val & HDF_JUSTIFYMASK) { + case HDF_LEFT: + default: + return DT_LEFT; + case HDF_RIGHT: + return DT_RIGHT; + case HDF_CENTER: + return DT_CENTER; + } + } + + void RenderButton(HWND wnd_, HDC dc_, CRect rcUpdate, bool bPressed) { + CDCHandle dc(dc_); CWindow wnd(wnd_); + CTheme theme; theme.OpenThemeData(wnd, L"BUTTON"); + + RelayEraseBkgnd(wnd, wnd.GetParent(), dc); + + const int part = BP_PUSHBUTTON; + + enum { + stNormal = PBS_NORMAL, + stHot = PBS_HOT, + stDisabled = PBS_DISABLED, + stPressed = PBS_PRESSED, + }; + + int state = 0; + if (!wnd.IsWindowEnabled()) state = stDisabled; + else if (bPressed) state = stPressed; + else state = stNormal; + + CRect rcClient; WIN32_OP_D( wnd.GetClientRect(rcClient) ); + + if (theme != NULL && IsThemePartDefined(theme, part, 0)) { + DrawThemeBackground(theme, dc, part, state, rcClient, &rcUpdate); + } else { + int stateEx = DFCS_BUTTONPUSH; + switch(state) { + case stPressed: stateEx |= DFCS_PUSHED; break; + case stDisabled: stateEx |= DFCS_INACTIVE; break; + } + DrawFrameControl(dc, rcClient, DFC_BUTTON, stateEx); + } + } + + + void PaintSeparatorControl(HWND wnd_) { + CWindow wnd(wnd_); + CPaintDC dc(wnd); + TCHAR buffer[512] = {}; + wnd.GetWindowText(buffer, _countof(buffer)); + const int txLen = (int) pfc::strlen_max_t(buffer, _countof(buffer)); + CRect contentRect; + WIN32_OP_D(wnd.GetClientRect(contentRect)); + + dc.SetTextColor(GetSysColor(COLOR_WINDOWTEXT)); + dc.SetBkMode(TRANSPARENT); + + { + CBrushHandle brush = (HBRUSH)wnd.GetParent().SendMessage(WM_CTLCOLORSTATIC, (WPARAM)(HDC)dc, (LPARAM)wnd.m_hWnd); + if (brush != NULL) dc.FillRect(contentRect, brush); + } + SelectObjectScope scopeFont(dc, wnd.GetFont()); + + if (txLen > 0) { + CRect rcText(contentRect); + if (!wnd.IsWindowEnabled()) { + dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT)); + } + WIN32_OP_D(dc.DrawText(buffer, txLen, rcText, DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT) > 0); + // WIN32_OP_D( dc.GrayString(NULL, NULL, (LPARAM) buffer, txLen, rcText.left, rcText.top, rcText.Width(), rcText.Height() ) ); + } + + SIZE txSize, probeSize; + const TCHAR probe[] = _T("#"); + if (dc.GetTextExtent(buffer, txLen, &txSize) && dc.GetTextExtent(probe, _countof(probe), &probeSize)) { + int spacing = txSize.cx > 0 ? (probeSize.cx / 4) : 0; + if (txSize.cx + spacing < contentRect.Width()) { + const CPoint center = contentRect.CenterPoint(); + CRect rcEdge(contentRect.left + txSize.cx + spacing, center.y, contentRect.right, contentRect.bottom); + WIN32_OP_D(dc.DrawEdge(rcEdge, EDGE_ETCHED, BF_TOP)); + } + } + } +} +
