1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 using System.Windows;
  9 using System.Windows.API;
 10 
 11 namespace System.Windows
 12 {
 13     public System.Windows.Color DefaultIconListViewTextColor()
 14     {
 15         return System.Windows.Color.Black();
 16     }
 17 
 18     public System.Windows.Color DefaultIconListViewIconSelectedColor()
 19     {
 20         return System.Windows.Color(64u96u156u210u);
 21     }
 22 
 23     public Padding DefaultIconListViewIconPadding()
 24     {
 25         return Padding(8888);
 26     }
 27 
 28     public Padding DefaultIconListViewTextPadding()
 29     {
 30         return Padding(4444);
 31     }
 32 
 33     public Padding DefaultIconListViewInternalPadding()
 34     {
 35         return Padding(4444);
 36     }
 37 
 38     public string DefaultIconListViewFontFamilyName()
 39     {
 40         return "Segoe UI";
 41     }
 42 
 43     public float DefaultIconListViewFontSize()
 44     {
 45         return 9.000000f;
 46     }
 47 
 48     public class delegate void IconListViewSelectedIndexChangedEventHandler();
 49     public class delegate void IconListViewIconDoubleClickedEventHandler();
 50 
 51     public class IconListViewItem : Component
 52     {
 53         public enum Flags : sbyte
 54         {
 55             none = 0selected = 1 << 0
 56         }
 57         public IconListViewItem(const string& iconName_const string& itemName_) : index(-1)iconName(iconName_)itemName(itemName_)location()size()
 58         {
 59         }
 60         [nodiscard]
 61         public Result<bool> Measure(Graphics& graphicsconst Padding& iconPaddingconst Padding& textPaddingconst Font& font)
 62         {
 63             iconSize = Size(cast<int>(bitmap->GetWidth())cast<int>(bitmap->GetHeight()));
 64             PointF origin;
 65             StringFormat format(StringAlignment.nearStringAlignment.near);
 66             if (format.Error())
 67             {
 68                 return Result<bool>(ErrorId(format.GetErrorId()));
 69             }
 70             auto measureResult = graphics.MeasureStringRectF(itemNamefontoriginformat);
 71             if (measureResult.Error())
 72             {
 73                 return Result<bool>(ErrorId(measureResult.GetErrorId()));
 74             }
 75             RectF textRect = measureResult.Value();
 76             textSize = textRect.size;
 77             size.w = Max(cast<int>(iconSize.w + iconPadding.Horizontal())cast<int>(textSize.w + textPadding.Horizontal()));
 78             size.h = cast<int>(iconSize.h + iconPadding.Vertical() + textSize.h + textPadding.Vertical());
 79             return Result<bool>(true);
 80         }
 81         public void SetLocations(const Point& locconst Padding& iconPaddingconst Padding& textPadding)
 82         {
 83             location = loc;
 84             if (textSize.w > iconSize.w)
 85             {
 86                 iconLocation = Point(cast<int>(location.x + iconPadding.left + ((textSize.w + textPadding.Horizontal()) - (iconSize.w + iconPadding.Horizontal())) / 2)location.y + iconPadding.top);
 87                 textLocation = PointF(location.x + textPadding.leftlocation.y + (size.h - textSize.h - textPadding.Vertical()) + textPadding.top);
 88             }
 89             else
 90             {
 91                 iconLocation = Point(location.x + iconPadding.leftlocation.y + iconPadding.top);
 92                 textLocation = PointF(location.x + textPadding.left + ((iconSize.w + iconPadding.Horizontal()) - (textSize.w + textPadding.Horizontal())) / 2location.y + (size.h - textSize.h - textPadding.Vertical()) + textPadding.top);
 93             }
 94         }
 95         [nodiscard]
 96         public Result<bool> Draw(Graphics& graphicsIconListView& iconListView)
 97         {
 98             if (IsSelected())
 99             {
100                 auto selectedBrushResult = iconListView.GetSelectedBrush();
101                 if (selectedBrushResult.Error())
102                 {
103                     return Result<bool>(ErrorId(selectedBrushResult.GetErrorId()));
104                 }
105                 SolidBrush* selectedBrush = selectedBrushResult.Value();
106                 RectF rect(PointF(location.xlocation.y)SizeF(size.wsize.h));
107                 auto result = graphics.FillRectangle(*selectedBrushrect);
108                 if (result.Error())
109                 {
110                     return Result<bool>(ErrorId(result.GetErrorId()));
111                 }
112             }
113             Rect r(iconLocationiconSize);
114             auto result = graphics.DrawImage(*bitmapr00iconSize.wiconSize.hUnit.pixeliconListView.GetImageAttributes());
115             if (result.Error())
116             {
117                 return Result<bool>(ErrorId(result.GetErrorId()));
118             }
119             RectF textRect(textLocationtextSize);
120             auto textBrushResult = iconListView.GetTextBrush();
121             if (textBrushResult.Error())
122             {
123                 return Result<bool>(ErrorId(textBrushResult.GetErrorId()));
124             }
125             result = graphics.DrawString(itemNameiconListView.GetFont()textRecticonListView.GetStringFormat()*textBrushResult.Value());
126             if (result.Error())
127             {
128                 return Result<bool>(ErrorId(result.GetErrorId()));
129             }
130             return Result<bool>(true);
131         }
132         public void SetSelected()
133         {
134             flags = cast<Flags>(flags | Flags.selected);
135         }
136         public void ResetSelected()
137         {
138             flags = cast<Flags>(flags & ~Flags.selected);
139         }
140         public bool IsSelected() const
141         {
142             return (flags & Flags.selected) != Flags.none;
143         }
144         private Flags flags;
145         public int index;
146         public string iconName;
147         public string itemName;
148         public Point location;
149         public Size size;
150         public Point iconLocation;
151         public Size iconSize;
152         public PointF textLocation;
153         public SizeF textSize;
154         public Bitmap* bitmap;
155     }
156 
157     public ControlCreateParams& IconListViewControlCreateParams(ControlCreateParams& controlCreateParams)
158     {
159         controlCreateParams.SetWindowClassName("System.Windows.IconListView");
160         controlCreateParams.SetWindowClassStyle(DoubleClickWindowClassStyle());
161         controlCreateParams.SetWindowClassBackgroundColor(SystemColor.COLOR_WINDOW);
162         controlCreateParams.SetBackgroundColor(Color.White());
163         return controlCreateParams;
164     }
165 
166     public class IconListViewCreateParams
167     {
168         public IconListViewCreateParams(ControlCreateParams& controlCreateParams_) : 
169             controlCreateParams(controlCreateParams_)
170             fontFamilyName(DefaultIconListViewFontFamilyName())
171             fontSize(DefaultIconListViewFontSize())
172             textColor(DefaultIconListViewTextColor())
173             iconSelectedColor(DefaultIconListViewIconSelectedColor())
174             bitmapTransparentColor(System.Windows.Color.DefaultBitmapTransparent())
175             iconPadding(DefaultIconListViewIconPadding())
176             textPadding(DefaultIconListViewTextPadding())
177             internalPadding(DefaultIconListViewInternalPadding())
178         {
179         }
180         public IconListViewCreateParams& SetFontFamilyName(const string& fontFamilyName_)
181         {
182             fontFamilyName = fontFamilyName_;
183             return *this;
184         }
185         public IconListViewCreateParams& SetFontSize(float fontSize_)
186         {
187             fontSize = fontSize_;
188             return *this;
189         }
190         public IconListViewCreateParams& SetTextColor(const Color& textColor_)
191         {
192             textColor = textColor_;
193             return *this;
194         }
195         public IconListViewCreateParams& SetIconSelectedColor(const Color& iconSelectedColor_)
196         {
197             iconSelectedColor = iconSelectedColor_;
198             return *this;
199         }
200         public IconListViewCreateParams& SetIconPadding(const Padding& iconPadding_)
201         {
202             iconPadding = iconPadding_;
203             return *this;
204         }
205         public IconListViewCreateParams& SetTextPadding(const Padding& textPadding_)
206         {
207             textPadding = textPadding_;
208             return *this;
209         }
210         public IconListViewCreateParams& SetInternalPadding(const Padding& internalPadding_)
211         {
212             internalPadding = internalPadding_;
213             return *this;
214         }
215         public ControlCreateParams& controlCreateParams;
216         public string fontFamilyName;
217         public float fontSize;
218         public Color textColor;
219         public Color iconSelectedColor;
220         public Color bitmapTransparentColor;
221         public Padding iconPadding;
222         public Padding textPadding;
223         public Padding internalPadding;
224     }
225 
226     public class IconListView : Control
227     {
228         private enum Flags : sbyte
229         {
230             none = 0changed = 1 << 0
231         }
232 
233         public IconListView(const System.Windows.Color& backgroundColorconst Point& locationconst Size& sizeDock dockAnchors anchors) : 
234             base("System.Windows.IconListView"DoubleClickWindowClassStyle()DefaultChildWindowStyle()DefaultExtendedWindowStyle()
235             backgroundColor"iconListView"locationsizedockanchors)items(this)numberOfItems(0)
236             fontFamily(DefaultIconListViewFontFamilyName())
237             fontSize(DefaultIconListViewFontSize())
238             font(fontFamilyfontSize)
239             bitmapTransparentColor(System.Windows.Color.DefaultBitmapTransparent())
240             iconSelectedColor(DefaultIconListViewIconSelectedColor())
241             textColor(DefaultIconListViewTextColor())
242             iconPadding(DefaultIconListViewIconPadding())
243             textPadding(DefaultIconListViewTextPadding())
244             internalPadding(DefaultIconListViewInternalPadding())
245             imageAttributes()stringFormat(StringAlignment.centerStringAlignment.center)selectedItem(null)
246         {
247             if (imageAttributes.Error())
248             {
249                 SetErrorId(imageAttributes.GetErrorId());
250                 return;
251             }
252             if (stringFormat.Error())
253             {
254                 SetErrorId(stringFormat.GetErrorId());
255                 return;
256             }
257             auto result = imageAttributes.SetColorKey(bitmapTransparentColorbitmapTransparentColorColorAdjustType.default_);
258             if (result.Error())
259             {
260                 SetErrorId(result.GetErrorId());
261                 return;
262             }
263             SetChanged();
264         }
265         public IconListView(const Point& locationconst Size& sizeDock dockAnchors anchors) : 
266             this(System.Windows.Color.White()locationsizedockanchors)
267         {
268         }
269         public IconListView(IconListViewCreateParams& createParams) : 
270             base(createParams.controlCreateParams)
271             items(this)numberOfItems(0)
272             fontFamily(createParams.fontFamilyName)
273             fontSize(createParams.fontSize)
274             font(fontFamilyfontSize)
275             bitmapTransparentColor(createParams.bitmapTransparentColor)
276             iconSelectedColor(createParams.iconSelectedColor)
277             textColor(createParams.textColor)
278             iconPadding(createParams.iconPadding)
279             textPadding(createParams.textPadding)
280             internalPadding(createParams.internalPadding)
281             imageAttributes()stringFormat(StringAlignment.centerStringAlignment.center)selectedItem(null)
282         {
283             if (imageAttributes.Error())
284             {
285                 SetErrorId(imageAttributes.GetErrorId());
286                 return;
287             }
288             if (stringFormat.Error())
289             {
290                 SetErrorId(stringFormat.GetErrorId());
291                 return;
292             }
293             auto result = imageAttributes.SetColorKey(bitmapTransparentColorbitmapTransparentColorColorAdjustType.default_);
294             if (result.Error())
295             {
296                 SetErrorId(result.GetErrorId());
297                 return;
298             }
299             SetChanged();
300         }
301         [nodiscard]
302         public Result<bool> AddItem(const string& iconNameconst string& itemName)
303         {
304             UniquePtr<IconListViewItem> item(new IconListViewItem(iconNameitemName));
305             if (item->Error())
306             {
307                 return Result<bool>(ErrorId(item->GetErrorId()));
308             }
309             item->index = numberOfItems;
310             auto insertResult = GetOrInsertBitmap(iconName);
311             if (insertResult.Error())
312             {
313                 return Result<bool>(ErrorId(insertResult.GetErrorId()));
314             }
315             item->bitmap = insertResult.Value();
316             auto result = items.AddChild(item.Release());
317             if (result.Error())
318             {
319                 return Result<bool>(ErrorId(result.GetErrorId()));
320             }
321             ++numberOfItems;
322             SetChanged();
323             result = Invalidate();
324             if (result.Error()) return result;
325             return Result<bool>(true);
326         }
327         [nodiscard]
328         protected override Result<bool> OnMouseDown(MouseEventArgs& args)
329         {
330             if (selectedItem != null)
331             {
332                 selectedItem->ResetSelected();
333             }
334             Point loc = args.location;
335             Component* component = items.FirstChild();
336             while (component != null)
337             {
338                 if (component is IconListViewItem*)
339                 {
340                     IconListViewItem* item = cast<IconListViewItem*>(component);
341                     Rect rect(item->locationitem->size);
342                     if (rect.Contains(loc))
343                     {
344                         item->SetSelected();
345                         selectedItem = item;
346                         OnSelectedIndexChanged();
347                         break;
348                     }
349                 }
350                 component = component->NextSibling();
351             }
352             return Invalidate();
353         }
354         [nodiscard]
355         protected override Result<bool> OnMouseDoubleClick(MouseEventArgs& args)
356         {
357             Point loc = args.location;
358             Component* component = items.FirstChild();
359             while (component != null)
360             {
361                 if (component is IconListViewItem*)
362                 {
363                     IconListViewItem* item = cast<IconListViewItem*>(component);
364                     Rect rect(item->locationitem->size);
365                     if (rect.Contains(loc))
366                     {
367                         item->SetSelected();
368                         selectedItem = item;
369                         OnDoubleClicked();
370                         break;
371                     }
372                 }
373                 component = component->NextSibling();
374             }
375             return Invalidate();
376         }
377         [nodiscard]
378         public Result<bool> SetBitmapTransparentColor(const System.Windows.Color& bitmapTransparentColor_)
379         {
380             bitmapTransparentColor = bitmapTransparentColor_;
381             auto result = imageAttributes.SetColorKey(bitmapTransparentColorbitmapTransparentColorColorAdjustType.default_);
382             if (result.Error())
383             {
384                 return Result<bool>(ErrorId(result.GetErrorId()));
385             }
386             return Result<bool>(true);
387         }
388         public void SetIconSelectedColor(const System.Windows.Color& iconSelectedColor_)
389         {
390             iconSelectedColor = iconSelectedColor_;
391         }
392         public void SetTextColor(const System.Windows.Color& textColor_)
393         {
394             textColor = textColor_;
395         }
396         public void SetIconPadding(const Padding& iconPadding_)
397         {
398             iconPadding = iconPadding_;
399         }
400         public void SetTextPadding(const Padding& textPadding_)
401         {
402             textPadding = textPadding_;
403         }
404         public void SetInternalPadding(const Padding& internalPadding_)
405         {
406             internalPadding = internalPadding_;
407         }
408         public void SetFont(const Font& font_)
409         {
410             font = font_;
411             SetChanged();
412         }
413         internal const Font& GetFont() const
414         {
415             return font;
416         }
417         internal const StringFormat& GetStringFormat() const
418         {
419             return stringFormat;
420         }
421         public Event<IconListViewSelectedIndexChangedEventHandler>& SelectedIndexChangedEvent()
422         {
423             return selectedIndexChangedEvent;
424         }
425         public Event<IconListViewIconDoubleClickedEventHandler>& IconDoubleClickedEvent()
426         {
427             return iconDoubleClickedEvent;
428         }
429         protected virtual void OnSelectedIndexChanged()
430         {
431             selectedIndexChangedEvent.Fire();
432         }
433         protected virtual void OnDoubleClicked()
434         {
435             iconDoubleClickedEvent.Fire();
436         }
437         public int GetSelectedIndex() const
438         {
439             if (selectedItem != null)
440             {
441                 return selectedItem->index;
442             }
443             else
444             {
445                 return -1;
446             }
447         }
448         [nodiscard]
449         protected override Result<bool> OnPaint(PaintEventArgs& args)
450         {
451             if (Changed())
452             {
453                 ResetChanged();
454                 auto result = Measure(args.graphics);
455                 if (result.Error())
456                 {
457                     return Result<bool>(ErrorId(result.GetErrorId()));
458                 }
459             }
460             auto result = args.graphics.Clear(BackgroundColor());
461             if (result.Error())
462             {
463                 return Result<bool>(ErrorId(result.GetErrorId()));
464             }
465             Component* component = items.FirstChild();
466             while (component != null)
467             {
468                 if (component is IconListViewItem*)
469                 {
470                     IconListViewItem* item = cast<IconListViewItem*>(component);
471                     result = item->Draw(args.graphics*this);
472                     if (result.Error())
473                     {
474                         return Result<bool>(ErrorId(result.GetErrorId()));
475                     }
476                 }
477                 component = component->NextSibling();
478             }
479             return base->OnPaint(args);
480         }
481         private Result<bool> Measure(Graphics& graphics)
482         {
483             Point loc(internalPadding.leftinternalPadding.top);
484             Size size;
485             Component* component = items.FirstChild();
486             while (component != null)
487             {
488                 if (component is IconListViewItem*)
489                 {
490                     IconListViewItem* item = cast<IconListViewItem*>(component);
491                     auto result = item->Measure(graphicsiconPaddingtextPaddingfont);
492                     if (result.Error())
493                     {
494                         return Result<bool>(ErrorId(result.GetErrorId()));
495                     }
496                     item->SetLocations(lociconPaddingtextPadding);
497                     loc.x = loc.x + item->size.w;
498                     size.w = size.w + item->size.w;
499                     size.h = Max(size.hitem->size.h);
500                 }
501                 component = component->NextSibling();
502             }
503             size.w = size.w + internalPadding.Horizontal();
504             size.h = size.h + internalPadding.Vertical();
505             auto result = SetSize(size);
506             if (result.Error()) return result;
507             return Result<bool>(true);
508         }
509         internal ImageAttributes& GetImageAttributes()
510         {
511             return imageAttributes;
512         }
513         internal Result<SolidBrush*> GetTextBrush()
514         {
515             return GetOrInsertSolidBrush(textColor);
516         }
517         internal Result<SolidBrush*> GetSelectedBrush()
518         {
519             return GetOrInsertSolidBrush(iconSelectedColor);
520         }
521         private Result<Bitmap*> GetOrInsertBitmap(const string& bitmapName)
522         {
523             auto it = bitmapMap.Find(bitmapName);
524             if (it != bitmapMap.End())
525             {
526                 return it->second;
527             }
528             auto bitmapResult = Bitmap.FromResource(bitmapName);
529             if (bitmapResult.Error())
530             {
531                 return Result<Bitmap*>(ErrorId(bitmapResult.GetErrorId()));
532             }
533             UniquePtr<Bitmap> bitmapPtr(new Bitmap(Rvalue(bitmapResult.Value())));
534             if (bitmapPtr->Error())
535             {
536                 return Result<Bitmap*>(ErrorId(bitmapPtr->GetErrorId()));
537             }
538             Bitmap* bitmap = bitmapPtr.Get();
539             bitmapMap[bitmapName] = bitmap;
540             bitmaps.Add(Rvalue(bitmapPtr));
541             return Result<Bitmap*>(bitmap);
542         }
543         private Result<SolidBrush*> GetOrInsertSolidBrush(const System.Windows.Color& color)
544         {
545             auto it = brushMap.Find(color);
546             if (it != brushMap.End())
547             {
548                 return it->second;
549             }
550             else
551             {
552                 UniquePtr<SolidBrush> solidBrushPtr(new SolidBrush(color));
553                 if (solidBrushPtr->Error())
554                 {
555                     return Result<SolidBrush*>(ErrorId(solidBrushPtr->GetErrorId()));
556                 }
557                 SolidBrush* solidBrush = solidBrushPtr.Get();
558                 brushMap[color] = solidBrush;
559                 brushes.Add(Rvalue(solidBrushPtr));
560                 return Result<SolidBrush*>(solidBrush);
561             }
562         }
563         private void SetChanged()
564         {
565             flags = cast<Flags>(flags | Flags.changed);
566         }
567         private void ResetChanged()
568         {
569             flags = cast<Flags>(flags & ~Flags.changed);
570         }
571         private bool Changed() const
572         {
573             return (flags & Flags.changed) != Flags.none;
574         }
575         private Flags flags;
576         private ComponentContainer items;
577         private int numberOfItems;
578         private FontFamily fontFamily;
579         private float fontSize;
580         private Font font;
581         private List<UniquePtr<Bitmap>> bitmaps;
582         private HashMap<stringBitmap*> bitmapMap;
583         private System.Windows.Color bitmapTransparentColor;
584         private System.Windows.Color iconSelectedColor;
585         private System.Windows.Color textColor;
586         private List<UniquePtr<SolidBrush>> brushes;
587         private HashMap<System.Windows.ColorSolidBrush*> brushMap;
588         private Padding iconPadding;
589         private Padding textPadding;
590         private Padding internalPadding;
591         private ImageAttributes imageAttributes;
592         private StringFormat stringFormat;
593         private IconListViewItem* selectedItem;
594         private Event<IconListViewSelectedIndexChangedEventHandler> selectedIndexChangedEvent;
595         private Event<IconListViewIconDoubleClickedEventHandler> iconDoubleClickedEvent;
596     }