1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.Windows
 10 {
 11     public Color DefaultToolTipTextColor()
 12     {
 13         return Color.Black();
 14     }
 15 
 16     public Color DefaultToolTipFrameColor()
 17     {
 18         return Color.Black();
 19     }
 20 
 21     public string DefaultToolTipFontFamilyName()
 22     {
 23         return "Segoe UI";
 24     }
 25 
 26     public float DefaultToolTipFontSize()
 27     {
 28         return 9.000000f;
 29     }
 30 
 31     public ControlCreateParams& ToolTipControlCreateParams(ControlCreateParams& controlCreateParams)
 32     {
 33         return controlCreateParams.SetWindowClassName("System.Windows.ToolTip").SetWindowStyle(HiddenChildWindowStyle()).
 34             SetWindowClassBackgroundColor(SystemColor.COLOR_WINDOW).SetBackgroundColor(Color.FloralWhite()).SetSize(Size(00));
 35     }
 36 
 37     public class ToolTipCreateParams
 38     {
 39         public ToolTipCreateParams(ControlCreateParams& controlCreateParams_) : 
 40             controlCreateParams(controlCreateParams_)
 41             textColor(DefaultToolTipTextColor())
 42             frameColor(DefaultToolTipFrameColor())
 43             fontFamilyName(DefaultToolTipFontFamilyName())
 44             fontSize(DefaultToolTipFontSize())
 45         {
 46         }
 47         public ToolTipCreateParams& Defaults()
 48         {
 49             return *this;
 50         }
 51         public ToolTipCreateParams& SetTextColor(const Color& textColor_)
 52         {
 53             textColor = textColor_;
 54             return *this;
 55         }
 56         public ToolTipCreateParams& SetFrameColor(const Color& frameColor_)
 57         {
 58             frameColor = frameColor_;
 59             return *this;
 60         }
 61         public ToolTipCreateParams& SetFontFamilyName(const string& fontFamilyName_)
 62         {
 63             fontFamilyName = fontFamilyName_;
 64             return *this;
 65         }
 66         public ToolTipCreateParams& SetFontSize(float fontSize_)
 67         {
 68             fontSize = fontSize_;
 69             return *this;
 70         }
 71         public ControlCreateParams& controlCreateParams;
 72         public Color textColor;
 73         public Color frameColor;
 74         public string fontFamilyName;
 75         public float fontSize;
 76     }
 77 
 78     public class ToolTip : Control
 79     {
 80         private enum Flags : sbyte
 81         {
 82             none = 0changed = 1 << 0
 83         }
 84         public ToolTip(const Color& backgroundColorconst Color& textColorconst Color& frameColorconst string& text_const Point& location_
 85             const Font& font_) : 
 86             base("System.Windows.ToolTip"DefaultWindowClassStyle()HiddenChildWindowStyle()DefaultExtendedWindowStyle()backgroundColor
 87             text_location_Size(00)Dock.noneAnchors.none)framePen(frameColor)font(font_)textBrush(textColor)
 88             format(StringAlignment.nearStringAlignment.near)textHeight(0)
 89         {
 90             SetChanged();
 91         }
 92         public ToolTip(const Color& backgroundColor) : 
 93             this(backgroundColorColor.Black()Color.Black()""Point(00)Font(FontFamily("Segoe UI")9.000000f))
 94         {
 95         }
 96         public ToolTip() : this(Color.FloralWhite())
 97         {
 98         }
 99         public ToolTip(ToolTipCreateParams& createParams) : 
100             base(createParams.controlCreateParams)
101             framePen(createParams.frameColor)
102             font(FontFamily(createParams.fontFamilyName)createParams.fontSize)
103             textBrush(createParams.textColor)
104             format(StringAlignment.nearStringAlignment.near)textHeight(0)
105         {
106             SetChanged();
107         }
108         [nodiscard]
109         public Result<bool> SetTextColor(const Color& textColor)
110         {
111             textBrush = SolidBrush(textColor);
112             auto result = Invalidate();
113             if (result.Error()) return result;
114             return Result<bool>(true);
115         }
116         [nodiscard]
117         public Result<bool> SetFrameColor(const Color& frameColor)
118         {
119             framePen = Pen(frameColor);
120             auto result = Invalidate();
121             if (result.Error()) return result;
122             return Result<bool>(true);
123         }
124         [nodiscard]
125         public Result<bool> SetFont(const Font& font_)
126         {
127             font = font_;
128             SetChanged();
129             auto result = Invalidate();
130             if (result.Error()) return result;
131             return Result<bool>(true);
132         }
133         [nodiscard]
134         public Result<bool> MeasureExtent()
135         {
136             auto graphicsResult = Graphics.FromWindowHandle(Handle());
137             if (graphicsResult.Error())
138             {
139                 return Result<bool>(ErrorId(graphicsResult.GetErrorId()));
140             }
141             Graphics& graphics = graphicsResult.Value();
142             auto measureResult = Measure(graphics);
143             if (measureResult.Error())
144             {
145                 return Result<bool>(ErrorId(measureResult.GetErrorId()));
146             }
147             return Result<bool>(true);
148         }
149         [nodiscard]
150         protected override Result<bool> OnTextChanged()
151         {
152             auto result = base->OnTextChanged();
153             if (result.Error()) return result;
154             lines = SplitIntoLines(Text());
155             SetChanged();
156             result = Invalidate();
157             if (result.Error()) return result;
158             return Result<bool>(true);
159         }
160         [nodiscard]
161         protected override Result<bool> OnPaint(PaintEventArgs& args)
162         {
163             if (Changed())
164             {
165                 ResetChanged();
166                 auto measureResult = Measure(args.graphics);
167                 if (measureResult.Error())
168                 {
169                     return Result<bool>(ErrorId(measureResult.GetErrorId()));
170                 }
171             }
172             Rect r(Point()GetSize());
173             r.size.w = r.size.w - 1;
174             r.size.h = r.size.h - 1;
175             auto clearResult = args.graphics.Clear(BackgroundColor());
176             if (clearResult.Error())
177             {
178                 return Result<bool>(ErrorId(clearResult.GetErrorId()));
179             }
180             auto drawResult = args.graphics.DrawRectangle(framePenr);
181             if (drawResult.Error())
182             {
183                 return Result<bool>(ErrorId(drawResult.GetErrorId()));
184             }
185             PointF pt(11);
186             for (const string& line : lines)
187             {
188                 auto drawStringResult = args.graphics.DrawString(linefontpttextBrush);
189                 if (drawResult.Error())
190                 {
191                     return Result<bool>(ErrorId(drawResult.GetErrorId()));
192                 }
193                 pt.y = pt.y + textHeight;
194             }
195             return base->OnPaint(args);
196         }
197         private Result<bool> Measure(Graphics& graphics)
198         {
199             Size size;
200             for (const string& line : lines)
201             {
202                 auto measureStringResult = graphics.MeasureStringRectF(linefontPointF(00)format);
203                 if (measureStringResult.Error())
204                 {
205                     return Result<bool>(ErrorId(measureStringResult.GetErrorId()));
206                 }
207                 RectF textRect = measureStringResult.Value();
208                 textHeight = Max(textHeightcast<int>(textRect.size.h));
209                 size.w = Max(size.wcast<int>(textRect.size.w));
210             }
211             size.h = cast<int>(lines.Count()) * textHeight;
212             size.w = size.w + 1;
213             size.h = size.h + 1;
214             auto result = SetSize(size);
215             if (result.Error()) return result;
216             return Result<bool>(true);
217         }
218         private inline bool Changed() const
219         {
220             return (flags & Flags.changed) != Flags.none;
221         }
222         private inline void SetChanged()
223         {
224             flags = cast<Flags>(flags | Flags.changed);
225         }
226         private inline void ResetChanged()
227         {
228             flags = cast<Flags>(flags & ~Flags.changed);
229         }
230         private Flags flags;
231         private Pen framePen;
232         private Font font;
233         private SolidBrush textBrush;
234         private StringFormat format;
235         private List<string> lines;
236         private int textHeight;
237     }
238 
239     public List<string> SplitIntoLines(const string& text)
240     {
241         List<string> lines;
242         string line;
243         int state = 0;
244         for (char c : text)
245         {
246             switch (state)
247             {
248                 case 0:
249                 {
250                     switch (c)
251                     {
252                         case '\n':
253                         {
254                             lines.Add(line);
255                             line.Clear();
256                             break;
257                         }
258                         case '\r':
259                         {
260                             state = 1;
261                             break;
262                         }
263                         default:
264                         {
265                             line.Append(c);
266                             break;
267                         }
268                     }
269                     break;
270                 }
271                 case 1:
272                 {
273                     switch (c)
274                     {
275                         case '\n':
276                         {
277                             lines.Add(line);
278                             line.Clear();
279                             state = 0;
280                             break;
281                         }
282                     }
283                     break;
284                 }
285             }
286         }
287         if (!line.IsEmpty())
288         {
289             lines.Add(line);
290         }
291         return lines;
292     }