1 // =================================
  2 // Copyright (c) 2022 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.Screen
 10 {
 11     public class ListBoxCreateParams
 12     {
 13         public ListBoxCreateParams() : 
 14             controlCreateParams()
 15             selectedItemForeColor(ConsoleColor.defaultColor)
 16             selectedItemBackColor(ConsoleColor.defaultColor)
 17             selectedItemFocusedForeColor(ConsoleColor.defaultColor)
 18             selectedItemFocusedBackColor(ConsoleColor.defaultColor)
 19         {
 20         }
 21         public nothrow ListBoxCreateParams& SetLocation(const Point& loc)
 22         {
 23             controlCreateParams.SetLocation(loc);
 24             return *this;
 25         }
 26         public nothrow ListBoxCreateParams& SetSize(const Size& size_)
 27         {
 28             controlCreateParams.SetSize(size_);
 29             return *this;
 30         }
 31         public nothrow ListBoxCreateParams& SetForeColor(ConsoleColor foreColor_)
 32         {
 33             controlCreateParams.SetForeColor(foreColor_);
 34             return *this;
 35         }
 36         public nothrow ListBoxCreateParams& SetBackColor(ConsoleColor backColor_)
 37         {
 38             controlCreateParams.SetBackColor(backColor_);
 39             return *this;
 40         }
 41         public nothrow ListBoxCreateParams& SetSelectedItemForeColor(ConsoleColor selectedItemForeColor_)
 42         {
 43             selectedItemForeColor = selectedItemForeColor_;
 44             return *this;
 45         }
 46         public nothrow ListBoxCreateParams& SetSelectedItemBackColor(ConsoleColor selectedItemBackColor_)
 47         {
 48             selectedItemBackColor = selectedItemBackColor_;
 49             return *this;
 50         }
 51         public nothrow ListBoxCreateParams& SetSelectedItemFocusedForeColor(ConsoleColor selectedItemFocusedForeColor_)
 52         {
 53             selectedItemFocusedForeColor = selectedItemFocusedForeColor_;
 54             return *this;
 55         }
 56         public nothrow ListBoxCreateParams& SetSelectedItemFocusedBackColor(ConsoleColor selectedItemFocusedBackColor_)
 57         {
 58             selectedItemFocusedBackColor = selectedItemFocusedBackColor_;
 59             return *this;
 60         }
 61         public ControlCreateParams controlCreateParams;
 62         public ConsoleColor selectedItemForeColor;
 63         public ConsoleColor selectedItemBackColor;
 64         public ConsoleColor selectedItemFocusedForeColor;
 65         public ConsoleColor selectedItemFocusedBackColor;
 66     }
 67     
 68     public class ListBox : Control
 69     {
 70         public nothrow ListBox(ListBoxCreateParams& createParams) : 
 71             base(createParams.controlCreateParams)
 72             selectedItemForeColor(createParams.selectedItemForeColor)
 73             selectedItemBackColor(createParams.selectedItemBackColor)
 74             selectedItemFocusedForeColor(createParams.selectedItemFocusedForeColor)
 75             selectedItemFocusedBackColor(createParams.selectedItemFocusedBackColor)
 76             topIndex(0)
 77             selectedIndex(0)
 78         {
 79             InvalidateGuard invalidateGuard(thisInvalidateKind.dontInvalidate);
 80             if (ForeColor() == ConsoleColor.defaultColor)
 81             {
 82                 SetForeColor(ConsoleColor.black);
 83             }
 84             if (BackColor() == ConsoleColor.defaultColor)
 85             {
 86                 SetBackColor(ConsoleColor.cyan);
 87             }
 88             if (selectedItemForeColor == ConsoleColor.defaultColor)
 89             {
 90                 selectedItemForeColor = ConsoleColor.gray;
 91             }
 92             if (selectedItemBackColor == ConsoleColor.defaultColor)
 93             {
 94                 selectedItemBackColor = ConsoleColor.black;
 95             }
 96             if (selectedItemFocusedForeColor == ConsoleColor.defaultColor)
 97             {
 98                 selectedItemFocusedForeColor = ConsoleColor.white;
 99             }
100             if (selectedItemFocusedBackColor == ConsoleColor.defaultColor)
101             {
102                 selectedItemFocusedBackColor = ConsoleColor.darkBlue;
103             }
104             SetControlCursorPos(Location());
105         }
106         public override nothrow Rect FocusRect() const
107         {
108             return LineRect(selectedIndex - topIndex);
109         }
110         public override void OnWriteScreen(WriteScreenEventArgs& args)
111         {
112             base->OnWriteScreen(args);
113             bool focused = IsFocused();
114             Rect updateRect = GetRect();
115             if (!args.GetRect().IsDefault())
116             {
117                 updateRect = Rect.Intersection(updateRectargs.GetRect());
118             }
119             if (updateRect.IsEmpty()) return;
120             Clear(updateRectForeColor()BackColor());
121             Point loc = Location();
122             Rect rect = GetRect();
123             int n = Min(rect.size.hMax(cast<int>(0)cast<int>(items.Count() - topIndex)));
124             for (int i = 0; i < n; ++i;)
125             {
126                 int index = topIndex + i;
127                 Rect lineRect = LineRect(i);
128                 if (lineRect.IntersectsWith(updateRect))
129                 {
130                     SetCursorPos(loc.xloc.y + i);
131                     ConsoleColor foreColor = ForeColor();
132                     ConsoleColor backColor = BackColor();
133                     if (index == selectedIndex)
134                     {
135                         if (focused)
136                         {
137                             foreColor = selectedItemFocusedForeColor;
138                             backColor = selectedItemFocusedBackColor;
139                         }
140                         else
141                         {
142                             foreColor = selectedItemForeColor;
143                             backColor = selectedItemBackColor;
144                         }
145                     }
146                     Terminal.Out() << SetColors(foreColorbackColor) << items[index];
147                 }
148             }
149         }
150         public override void OnKeyPressed(KeyEventArgs& args)
151         {
152             base->OnKeyPressed(args);
153             Size sz = GetSize();
154             if (!args.Handled())
155             {
156                 int prevTopIndex = topIndex;
157                 if (args.Key() == keyNewline)
158                 {
159                     topIndex = 0;
160                     OnItemSelected();
161                     args.SetHandled();
162                 }
163                 else
164                 {
165                     switch (args.Key())
166                     {
167                         case keyDown:
168                         {
169                             if (selectedIndex < items.Count() - 1)
170                             {
171                                 Rect updateRect = LineRect(selectedIndex - topIndex);
172                                 ++selectedIndex;
173                                 if (selectedIndex >= topIndex + sz.h)
174                                 {
175                                     ++topIndex;
176                                 }
177                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
178                                 OnSelectedIndexChanged();
179                                 if (topIndex == prevTopIndex)
180                                 {
181                                     Invalidate(updateRect);
182                                 }
183                                 else
184                                 {
185                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
186                                     Invalidate();
187                                 }
188                                 args.SetHandled();
189                             }
190                             break;
191                         }
192                         case keyUp:
193                         {
194                             if (selectedIndex > 0)
195                             {
196                                 Rect updateRect = LineRect(selectedIndex - topIndex);
197                                 --selectedIndex;
198                                 if (selectedIndex < topIndex)
199                                 {
200                                     --topIndex;
201                                 }
202                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
203                                 OnSelectedIndexChanged();
204                                 if (topIndex == prevTopIndex)
205                                 {
206                                     Invalidate(updateRect);
207                                 }
208                                 else
209                                 {
210                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
211                                     Invalidate();
212                                 }
213                                 args.SetHandled();
214                             }
215                             break;
216                         }
217                         case keyHome:
218                         {
219                             if (selectedIndex != 0)
220                             {
221                                 Rect updateRect = LineRect(selectedIndex - topIndex);
222                                 selectedIndex = 0;
223                                 topIndex = 0;
224                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
225                                 OnSelectedIndexChanged();
226                                 if (topIndex == prevTopIndex)
227                                 {
228                                     Invalidate(updateRect);
229                                 }
230                                 else
231                                 {
232                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
233                                     Invalidate();
234                                 }
235                                 args.SetHandled();
236                             }
237                             break;
238                         }
239                         case keyEnd:
240                         {
241                             if (selectedIndex != items.Count() - 1)
242                             {
243                                 Rect updateRect = LineRect(selectedIndex - topIndex);
244                                 selectedIndex = cast<int>(items.Count() - 1);
245                                 topIndex = cast<int>(items.Count() - sz.h);
246                                 if (topIndex < 0)
247                                 {
248                                     topIndex = 0;
249                                 }
250                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
251                                 OnSelectedIndexChanged();
252                                 if (topIndex == prevTopIndex)
253                                 {
254                                     Invalidate(updateRect);
255                                 }
256                                 else
257                                 {
258                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
259                                     Invalidate();
260                                 }
261                                 args.SetHandled();
262                             }
263                             break;
264                         }
265                         case keyPgDown:
266                         {
267                             if (selectedIndex < items.Count() - 1)
268                             {
269                                 Rect updateRect = LineRect(selectedIndex - topIndex);
270                                 selectedIndex = Max(cast<int>(0)Min(cast<int>(items.Count() - 1)selectedIndex + sz.h));
271                                 topIndex = topIndex + sz.h;
272                                 if (topIndex + sz.h > items.Count())
273                                 {
274                                     topIndex = Max(cast<int>(0)cast<int>(items.Count() - sz.h));
275                                 }
276                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
277                                 OnSelectedIndexChanged();
278                                 if (topIndex == prevTopIndex)
279                                 {
280                                     Invalidate(updateRect);
281                                 }
282                                 else
283                                 {
284                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
285                                     Invalidate();
286                                 }
287                                 args.SetHandled();
288                             }
289                             break;
290                         }
291                         case keyPgUp:
292                         {
293                             if (selectedIndex > 0)
294                             {
295                                 Rect updateRect = LineRect(selectedIndex - topIndex);
296                                 selectedIndex = Max(cast<int>(0)selectedIndex - sz.h);
297                                 topIndex = Max(cast<int>(0)topIndex - sz.h);
298                                 updateRect = Rect.Union(updateRectLineRect(selectedIndex - topIndex));
299                                 OnSelectedIndexChanged();
300                                 if (topIndex == prevTopIndex)
301                                 {
302                                     Invalidate(updateRect);
303                                 }
304                                 else
305                                 {
306                                     InvalidateGuard invalidateGuard(thisInvalidateKind.forceInvalidate);
307                                     Invalidate();
308                                 }
309                                 args.SetHandled();
310                             }
311                             break;
312                         }
313                     }
314                 }
315             }
316         }
317         public nothrow long ItemCount() const
318         {
319             return items.Count();
320         }
321         public void AddItem(const ustring& item)
322         {
323             items.Add(item);
324         }
325         public const ustring& GetItem(int index) const
326         {
327             return items[index];
328         }
329         public void Clear()
330         {
331             items.Clear();
332         }
333         public nothrow int SelectedIndex() const
334         {
335             return selectedIndex;
336         }
337         public nothrow void SetSelectedIndex(int selectedIndex_)
338         {
339             if (selectedIndex != selectedIndex_)
340             {
341                 Size sz = GetSize();
342                 selectedIndex = selectedIndex_;
343                 topIndex = selectedIndex;
344                 if (topIndex + sz.h > items.Count())
345                 {
346                     topIndex = Max(cast<int>(0)cast<int>(items.Count() - sz.h));
347                 }
348             }
349         }
350         public virtual void OnSelectedIndexChanged()
351         {
352             Point loc = Location();
353             int index = selectedIndex - topIndex;
354             loc.y = loc.y + index;
355             SetControlCursorPos(loc);
356             selectedIndexChangedEvent.Fire();
357         }
358         public virtual void OnItemSelected()
359         {
360             itemSelectedEvent.Fire();
361         }
362         public nothrow Event<ChangedEventHandler>& SelectedIndexChangedEvent()
363         {
364             return selectedIndexChangedEvent;
365         }
366         public nothrow Event<SelectEventHandler>& ItemSelectedEvent()
367         {
368             return itemSelectedEvent;
369         }
370         private nothrow Rect LineRect(int i) const
371         {
372             Point loc = Location();
373             loc.y = loc.y + i;
374             Size sz = GetSize();
375             sz.h = 1;
376             return Rect(locsz);
377         }
378         private ConsoleColor selectedItemForeColor;
379         private ConsoleColor selectedItemBackColor;
380         private ConsoleColor selectedItemFocusedForeColor;
381         private ConsoleColor selectedItemFocusedBackColor;
382         private List<ustring> items;
383         private int topIndex;
384         private int selectedIndex;
385         private Event<ChangedEventHandler> selectedIndexChangedEvent;
386         private Event<SelectEventHandler> itemSelectedEvent;
387     }
388 }
389