1 // =================================
  2 // Copyright (c) 2025 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 enum ButtonStyle : long
 12     {
 13         BS_PUSHBUTTON = 0, 
 14         BS_DEFPUSHBUTTON = 1, 
 15         BS_3STATE = 5, 
 16         BS_AUTO3STATE = 6, 
 17         BS_AUTOCHECKBOX = 3, 
 18         BS_AUTORADIOBUTTON = 9, 
 19         BS_BITMAP = 128u, 
 20         BS_BOTTOM = 2048, 
 21         BS_CENTER = 768, 
 22         BS_CHECKBOX = 2, 
 23         BS_GROUPBOX = 7, 
 24         BS_ICON = 64, 
 25         BS_FLAT = 32768u, 
 26         BS_LEFT = 256, 
 27         BS_LEFTTEXT = 32, 
 28         BS_MULTILINE = 8192, 
 29         BS_NOTIFY = 16384, 
 30         BS_OWNERDRAW = 11, 
 31         BS_PUSHLIKE = 4096, 
 32         BS_RADIOBUTTON = 4, 
 33         BS_RIGHT = 512, 
 34         BS_TOP = 1024, 
 35         BS_TYPEMASK = 15, 
 36         BS_VCENTER = 3072
 37     }
 38 
 39     public abstract class ButtonBase : Control
 40     {
 41         public ButtonBase(const string& windowClassName, WindowClassStyle windowClassStyle, WindowStyle style, ExtendedWindowStyle exStyle, 
 42             const Color& backgroundColor, const string& text, const Point& location, const Size& size, Dock dock, Anchors anchors) : 
 43             base(windowClassName, windowClassStyle, style, exStyle, backgroundColor, text, location, size, dock, anchors)
 44         {
 45         }
 46         public ButtonBase(ControlCreateParams& controlCreateParams) : base(controlCreateParams)
 47         {
 48         }
 49         [nodiscard]
 50         internal Result<bool> OnClickInternal()
 51         {
 52             ClickEventArgs args;
 53             auto result = OnClick(args);
 54             if (result.Error()) return result;
 55             if (args.errorId != 0)
 56             {
 57                 return Result<bool>(ErrorId(args.errorId));
 58             }
 59             return Result<bool>(true);
 60         }
 61         [nodiscard]
 62         public Result<bool> DoClick()
 63         {
 64             ClickEventArgs args;
 65             auto result = OnClick(args);
 66             if (result.Error()) return result;
 67             if (args.errorId != 0)
 68             {
 69                 return Result<bool>(ErrorId(args.errorId));
 70             }
 71             return Result<bool>(true);
 72         }
 73     }
 74 
 75     public ControlCreateParams& ButtonControlCreateParams(ControlCreateParams& controlCreateParams, ButtonStyle buttonStyle)
 76     {
 77         controlCreateParams.SetWindowClassName("BUTTON");
 78         controlCreateParams.SetWindowStyle(cast<WindowStyle>(DefaultChildWindowStyle() | WindowStyle.WS_TABSTOP | buttonStyle));
 79         controlCreateParams.SetWindowClassBackgroundColor(SystemColor.COLOR_BTNFACE);
 80         controlCreateParams.SetBackgroundColor(GetSystemColor(SystemColor.COLOR_BTNFACE));
 81         return controlCreateParams;
 82     }
 83 
 84     public ControlCreateParams& ButtonControlCreateParams(ControlCreateParams& controlCreateParams, bool setDefault)
 85     {
 86         ButtonStyle buttonStyle = ButtonStyle();
 87         if (setDefault)
 88         {
 89             buttonStyle = cast<ButtonStyle>(buttonStyle | ButtonStyle.BS_DEFPUSHBUTTON | ButtonStyle.BS_NOTIFY);
 90         }
 91         else
 92         {
 93             buttonStyle = cast<ButtonStyle>(buttonStyle | ButtonStyle.BS_PUSHBUTTON | ButtonStyle.BS_NOTIFY);
 94         }
 95         return ButtonControlCreateParams(controlCreateParams, buttonStyle);
 96     }
 97 
 98     public ControlCreateParams& ButtonControlCreateParams(ControlCreateParams& controlCreateParams)
 99     {
100         return ButtonControlCreateParams(controlCreateParams, false);
101     }
102 
103     public class ButtonCreateParams
104     {
105         public ButtonCreateParams(ControlCreateParams& controlCreateParams_) : controlCreateParams(controlCreateParams_)
106         {
107         }
108         public ButtonCreateParams& Defaults()
109         {
110             return *this;
111         }
112         public ControlCreateParams& controlCreateParams;
113     }
114 
115     public class Button : ButtonBase
116     {
117         private enum Flags : sbyte
118         {
119             none = 0, defaultButton = 1 << 0
120         }
121         public Button(ButtonStyle buttonStyle, const Color& backgroundColor, const string& text, const Point& location, const Size& size, 
122             Dock dock, Anchors anchors) : 
123             base("BUTTON", DefaultWindowClassStyle(), cast<WindowStyle>(DefaultChildWindowStyle() | WindowStyle.WS_TABSTOP | buttonStyle), 
124             DefaultExtendedWindowStyle(), 
125             backgroundColor, text, location, size, dock, anchors), flags(Flags.none), dialogResult(DialogResult.none)
126         {
127             SetFlagsFromButtonStyle(buttonStyle);
128         }
129         public Button(const string& text, const Point& location, const Size& size, Dock dock, Anchors anchors) : 
130             this(cast<ButtonStyle>(ButtonStyle.BS_PUSHBUTTON | ButtonStyle.BS_NOTIFY), GetSystemColor(SystemColor.COLOR_BTNFACE), text, location, size, dock, anchors)
131         {
132         }
133         public Button(ButtonStyle buttonStyle, const string& text, const Point& location, const Size& size) : 
134             this(buttonStyle, GetSystemColor(SystemColor.COLOR_BTNFACE), text, location, size, Dock.none, cast<Anchors>(Anchors.top | Anchors.left))
135         {
136         }
137         public Button(const string& text, const Point& location, const Size& size) : 
138             this(cast<ButtonStyle>(ButtonStyle.BS_PUSHBUTTON | ButtonStyle.BS_NOTIFY), text, location, size)
139         {
140         }
141         public Button(ButtonCreateParams& createParams) : 
142             base(createParams.controlCreateParams), flags(Flags.none), dialogResult(DialogResult.none)
143         {
144             SetFlagsFromButtonStyle(cast<ButtonStyle>(cast<long>(createParams.controlCreateParams.windowStyle)));
145         }
146         protected override Result<bool> OnCreated()
147         {
148             auto result = base->OnCreated();
149             if (result.Error())
150             {
151                 return Result<bool>(ErrorId(result.GetErrorId()));
152             }
153             auto graphicsResult = Graphics.FromWindowHandle(Handle());
154             if (graphicsResult.Error())
155             {
156                 return Result<bool>(ErrorId(graphicsResult.GetErrorId()));
157             }
158             else
159             {
160                 Graphics& graphics = graphicsResult.Value();
161                 Result<FontHandle*> fontHandleResult = GetFontHandle(graphics);
162                 if (fontHandleResult.Error())
163                 {
164                     return Result<bool>(ErrorId(fontHandleResult.GetErrorId()));
165                 }
166                 else
167                 {
168                     FontHandle* fontHandle = fontHandleResult.Value();
169                     if (!fontHandle->IsNull())
170                     {
171                         SendSetFontMessage(*fontHandle);
172                     }
173                 }
174             }
175             return Result<bool>(true);
176         }
177         [nodiscard]
178         protected override Result<bool> OnGotFocus()
179         {
180             auto result = base->OnGotFocus();
181             if (result.Error()) return result;
182             if (!IsDefault())
183             {
184                 Window* window = GetWindow();
185                 if (window != null)
186                 {
187                     Button* defaultButton = window->DefaultButton();
188                     if (defaultButton != null)
189                     {
190                         defaultButton->ResetDefaultButtonStyle();
191                     }
192                 }
193                 SetDefaultButtonStyle();
194             }
195             return Result<bool>(true);
196         }
197         [nodiscard]
198         protected override Result<bool> OnLostFocus()
199         {
200             auto result = base->OnLostFocus();
201             if (result.Error()) return result;
202             if (!IsDefault())
203             {
204                 ResetDefaultButtonStyle();
205                 Window* window = GetWindow();
206                 if (window != null)
207                 {
208                     Button* defaultButton = window->DefaultButton();
209                     if (defaultButton != null)
210                     {
211                         defaultButton->SetDefaultButtonStyle();
212                     }
213                 }
214             }
215             return Result<bool>(true);
216         }
217         [nodiscard]
218         protected override Result<bool> OnClick(ClickEventArgs& args)
219         {
220             auto result = base->OnClick(args);
221             if (result.Error()) return result;
222             if (dialogResult != DialogResult.none)
223             {
224                 Window* window = GetWindow();
225                 if (window != null)
226                 {
227                     window->SetDialogResult(dialogResult);
228                 }
229             }
230             return Result<bool>(true);
231         }
232         [nodiscard]
233         protected override Result<bool> OnKeyDown(KeyEventArgs& args)
234         {
235             auto result = base->OnKeyDown(args);
236             if (result.Error()) return result;
237             if (!args.handled)
238             {
239                 switch (args.key)
240                 {
241                     case Keys.enter:
242                     {
243                         ClickEventArgs clickEventArgs;
244                         result = OnClick(clickEventArgs);
245                         if (result.Error()) return result;
246                         if (clickEventArgs.errorId != 0)
247                         {
248                             return Result<bool>(ErrorId(clickEventArgs.errorId));
249                         }
250                         break;
251                     }
252                 }
253             }
254             return Result<bool>(true);
255         }
256         [nodiscard]
257         public override Result<bool> PrintWindowTree(int level)
258         {
259             LogView* log = Application.GetLogView();
260             if (log != null)
261             {
262                 auto handleResult = ToHexString(cast<ulong>(Handle()));
263                 if (handleResult.Error())
264                 {
265                     return Result<bool>(ErrorId(handleResult.GetErrorId()));
266                 }
267                 const string& handleStr = handleResult.Value();
268                 auto parentTextResult = ParentText();
269                 if (parentTextResult.Error())
270                 {
271                     return Result<bool>(ErrorId(parentTextResult.GetErrorId()));
272                 }
273                 const string& parentText = parentTextResult.Value();
274                 auto result = log->WriteLine(string(' ', level) + "Button." + Text() + ".handle=" + handleStr + " " + parentText + 
275                     "[" + Rect(Point(), GetSize()).ToString() + "]");
276                 if (result.Error()) return result;
277             }
278             return Result<bool>(true);
279         }
280         public inline DialogResult GetDialogResult() const
281         {
282             return dialogResult;
283         }
284         public inline void SetDialogResult(DialogResult dialogResult_)
285         {
286             dialogResult = dialogResult_;
287         }
288         private void SetFlagsFromButtonStyle(ButtonStyle buttonStyle)
289         {
290             if ((buttonStyle & ButtonStyle.BS_DEFPUSHBUTTON) != 0)
291             {
292                 flags = cast<Flags>(flags | Flags.defaultButton);
293             }
294         }
295         public inline bool IsDefault() const
296         {
297             return (flags & Flags.defaultButton) != Flags.none;
298         }
299         public void SetDefault()
300         {
301             flags = cast<Flags>(flags | Flags.defaultButton);
302             SetDefaultButtonStyle();
303         }
304         public void ResetDefault()
305         {
306             flags = cast<Flags>(flags & ~Flags.defaultButton);
307             ResetDefaultButtonStyle();
308         }
309         private void SetDefaultButtonStyle()
310         {
311             if (Handle() != null)
312             {
313                 WinSendMessage(Handle(), BM_SETSTYLE, cast<uint>(GetWindowStyle() | ButtonStyle.BS_DEFPUSHBUTTON), cast<long>(true));
314             }
315         }
316         private void ResetDefaultButtonStyle()
317         {
318             if (Handle() != null)
319             {
320                 WinSendMessage(Handle(), BM_SETSTYLE, cast<uint>(GetWindowStyle() & ~ButtonStyle.BS_DEFPUSHBUTTON), cast<long>(true));
321             }
322         }
323         private Flags flags;
324         private DialogResult dialogResult;
325     }