UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2023 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "pch.h" 30 31 #include "button.h" 32 33 #include "util.h" 34 #include "container.h" 35 #include "icons.h" 36 37 #include "../common/object.h" 38 #include "../common/context.h" 39 40 using namespace winrt; 41 using namespace Microsoft::UI::Xaml; 42 using namespace Microsoft::UI::Xaml::Controls; 43 using namespace Windows::UI::Xaml::Interop; 44 using namespace winrt::Windows::Foundation; 45 using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives; 46 47 48 49 void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) { 50 // TODO: stockid 51 52 if (type == UI_LABEL_ICON) { 53 label = NULL; 54 } 55 else if (type == UI_LABEL_TEXT) { 56 icon = NULL; 57 } 58 59 IconElement icon_elm = { nullptr }; 60 if (icon) { 61 icon_elm = ui_get_icon(icon); 62 } 63 64 if (label && icon_elm) { 65 StackPanel panel = StackPanel(); 66 panel.Orientation(Orientation::Horizontal); 67 panel.Spacing(5); 68 69 panel.Children().Append(icon_elm); 70 71 wchar_t* wlabel = str2wstr(label, nullptr); 72 TextBlock label = TextBlock(); 73 label.Text(wlabel); 74 panel.Children().Append(label); 75 free(wlabel); 76 77 button.Content(panel); 78 } 79 else if (label) { 80 wchar_t* wlabel = str2wstr(label, nullptr); 81 button.Content(box_value(wlabel)); 82 free(wlabel); 83 } 84 else if (icon_elm) { 85 button.Content(ui_get_icon(icon)); 86 } 87 } 88 89 90 UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) { 91 UiObject* current = uic_current_obj(obj); 92 93 // create button with label 94 Button button = Button(); 95 ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); 96 97 // create toolkit wrapper object and register destructor 98 UIElement elm = button; 99 UiWidget* widget = new UiWidget(elm); 100 ui_context_add_widget_destructor(current->ctx, widget); 101 ui_set_widget_groups(current->ctx, widget, args.groups); 102 103 // register callback 104 if (args.onclick) { 105 ui_callback cbfunc = args.onclick; 106 void* cbdata = args.onclickdata; 107 button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) { 108 UiEvent evt; 109 evt.obj = obj; 110 evt.window = obj->window; 111 evt.document = obj->ctx->document; 112 evt.eventdata = nullptr; 113 evt.intval = 0; 114 cbfunc(&evt, cbdata); 115 }); 116 } 117 118 // add button to current container 119 UI_APPLY_LAYOUT1(current, args); 120 121 current->container->Add(button, false); 122 123 return widget; 124 } 125 126 127 void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) { 128 button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { 129 UiInteger* i = (UiInteger*)var->value; 130 UiEvent evt = ui_create_int_event(obj, i->get(i)); 131 ui_notify_evt(i->observers, &evt); 132 }); 133 } 134 135 void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) { 136 button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { 137 UiInteger* i = (UiInteger*)var->value; 138 UiEvent evt = ui_create_int_event(obj, i->get(i)); 139 ui_notify_evt(i->observers, &evt); 140 }); 141 } 142 143 void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) { 144 ui_callback callback = args.onchange; 145 void* cbdata = args.onchangedata; 146 if (callback) { 147 button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { 148 UiEvent evt = ui_create_int_event(obj, true); 149 callback(&evt, cbdata); 150 }); 151 button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { 152 UiEvent evt = ui_create_int_event(obj, false); 153 callback(&evt, cbdata); 154 }); 155 } 156 } 157 158 // for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same 159 // code again, because although everything is basically the same, it is named differently 160 static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) { 161 button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) { 162 UiInteger* i = (UiInteger*)var->value; 163 UiEvent evt = ui_create_int_event(obj, i->get(i)); 164 ui_notify_evt(i->observers, &evt); 165 }); 166 } 167 168 static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) { 169 ui_callback callback = args.onchange; 170 void* cbdata = args.onchangedata; 171 if (callback) { 172 button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { 173 UiEvent evt = ui_create_int_event(obj, button.IsOn()); 174 callback(&evt, cbdata); 175 }); 176 } 177 } 178 179 static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) { 180 if (onchange) { 181 UiEvent evt; 182 evt.obj = obj; 183 evt.window = obj->window; 184 evt.document = obj->ctx->document; 185 evt.eventdata = nullptr; 186 evt.intval = checked; 187 onchange(&evt, onchangedata); 188 } 189 if (enable_state > 0) { 190 if (checked) { 191 ui_set_group(obj->ctx, enable_state); 192 } else { 193 ui_unset_group(obj->ctx, enable_state); 194 } 195 } 196 } 197 198 static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) { 199 UiObject* current = uic_current_obj(obj); 200 201 // set label 202 ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); 203 togglebutton_register_callback(button, obj, args); 204 205 // create toolkit wrapper object and register destructor 206 UIElement elm = button; 207 UiWidget* widget = new UiWidget(elm); 208 ui_context_add_widget_destructor(current->ctx, widget); 209 ui_set_widget_groups(current->ctx, widget, args.groups); 210 211 // bind variable 212 UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); 213 if (var) { 214 UiInteger* value = (UiInteger*)var->value; 215 value->obj = widget; 216 value->get = ui_toggle_button_get; 217 value->set = ui_toggle_button_set; 218 219 // listener for notifying observers 220 togglebutton_register_checked_observers(button, obj, var); 221 togglebutton_register_unchecked_observers(button, obj, var); 222 } 223 224 if (args.enable_group > 0 || args.onchange) { 225 button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) { 226 togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group); 227 }); 228 button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) { 229 togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group); 230 }); 231 } 232 233 // add button to current container 234 UI_APPLY_LAYOUT1(current, args); 235 236 current->container->Add(button, false); 237 238 return widget; 239 } 240 241 UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { 242 ToggleButton button = ToggleButton(); 243 return create_togglebutton(obj, button, args); 244 } 245 246 UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { 247 CheckBox button = CheckBox(); 248 return create_togglebutton(obj, button, args); 249 } 250 251 UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { 252 ToggleSwitch button = ToggleSwitch(); 253 if (args.label) { 254 wchar_t* wlabel = str2wstr(args.label, nullptr); 255 button.Header(box_value(wlabel)); 256 free(wlabel); 257 } 258 switch_register_callback(button, obj, args); 259 260 UiObject* current = uic_current_obj(obj); 261 262 // create toolkit wrapper object and register destructor 263 UIElement elm = button; 264 UiWidget* widget = new UiWidget(elm); 265 ui_context_add_widget_destructor(current->ctx, widget); 266 ui_set_widget_groups(current->ctx, widget, args.groups); 267 268 // bind variable 269 UiVar* var = nullptr; 270 if (args.value) { 271 var = uic_create_value_var(current->ctx, args.value); 272 } 273 else if (args.varname) { 274 var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); 275 } 276 if (var) { 277 UiInteger* value = (UiInteger*)var->value; 278 value->obj = widget; 279 value->get = ui_toggle_button_get; 280 value->set = ui_toggle_button_set; 281 282 // listener for notifying observers 283 switch_register_observers(button, obj, var); 284 } 285 286 // add button to current container 287 UI_APPLY_LAYOUT1(current, args); 288 289 current->container->Add(button, false); 290 291 return widget; 292 } 293 294 UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { 295 RadioButton button = RadioButton(); 296 297 UiObject* current = uic_current_obj(obj); 298 299 // set label 300 ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); 301 togglebutton_register_callback(button, obj, args); 302 303 // create toolkit wrapper object and register destructor 304 UIElement elm = button; 305 UiWidget* widget = new UiWidget(elm); 306 ui_context_add_widget_destructor(current->ctx, widget); 307 ui_set_widget_groups(current->ctx, widget, args.groups); 308 309 UiVar* var = nullptr; 310 if (args.value) { 311 var = uic_create_value_var(current->ctx, args.value); 312 } 313 else if (args.varname) { 314 var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); 315 } 316 317 // bind radio button to the value 318 if (var) { 319 UiInteger* value = (UiInteger*)var->value; 320 321 // store a list of radio buttons in the value 322 CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS)); 323 // get or create the group name 324 static int groupCount = 0; 325 winrt::hstring groupName; 326 if (cxListSize(radioButtons) == 0) { 327 groupName = winrt::to_hstring(groupCount++); 328 } else { 329 UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0); 330 RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>(); 331 groupName = firstRadioButton.GroupName(); 332 } 333 334 // set the group name for the new radiobutton 335 cxListAdd(radioButtons, widget); 336 button.GroupName(groupName); 337 338 value->obj = radioButtons; 339 value->get = ui_radio_button_get; 340 value->set = ui_radio_button_set; 341 342 // listener for notifying observers (only checked, not unchecked) 343 togglebutton_register_checked_observers(button, obj, var); 344 } 345 346 // add button to current container 347 UI_APPLY_LAYOUT1(current, args); 348 349 current->container->Add(button, false); 350 351 return widget; 352 } 353 354 355 int64_t ui_toggle_button_get(UiInteger* integer) { 356 UiWidget* widget = (UiWidget*)integer->obj; 357 ToggleButton toggleButton = widget->uielement.as<ToggleButton>(); 358 int val = toggleButton.IsChecked().GetBoolean(); 359 integer->value = val; 360 return val; 361 } 362 363 void ui_toggle_button_set(UiInteger* integer, int64_t value) { 364 UiWidget* widget = (UiWidget*)integer->obj; 365 ToggleButton toggleButton = widget->uielement.as<ToggleButton>(); 366 toggleButton.IsChecked((bool)value); 367 integer->value = value; 368 } 369 370 int64_t ui_switch_get(UiInteger * integer) { 371 UiWidget* widget = (UiWidget*)integer->obj; 372 ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>(); 373 int val = toggleButton.IsOn(); 374 integer->value = val; 375 return val; 376 } 377 378 void ui_switch_set(UiInteger * integer, int64_t value) { 379 UiWidget* widget = (UiWidget*)integer->obj; 380 ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>(); 381 toggleButton.IsOn((bool)value); 382 integer->value = value; 383 } 384 385 int64_t ui_radio_button_get(UiInteger * integer) { 386 CxList* list = (CxList*)integer->obj; 387 CxIterator i = cxListIterator(list); 388 int selection = -1; 389 cx_foreach(UiWidget*, widget, i) { 390 ToggleButton button = widget->uielement.as<ToggleButton>(); 391 if (button.IsChecked().GetBoolean()) { 392 selection = i.index; 393 break; 394 } 395 } 396 integer->value = selection; 397 return selection; 398 } 399 400 void ui_radio_button_set(UiInteger * integer, int64_t value) { 401 CxList* list = (CxList*)integer->obj; 402 UiWidget* widget = (UiWidget*)cxListAt(list, value); 403 if (widget) { 404 ToggleButton button = widget->uielement.as<ToggleButton>(); 405 button.IsChecked(true); 406 integer->value = value; 407 } 408 } 409