--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/winui/button.cpp Sat Jan 04 16:38:48 2025 +0100 @@ -0,0 +1,408 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2023 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pch.h" + +#include "button.h" + +#include "util.h" +#include "container.h" +#include "icons.h" + +#include "../common/object.h" +#include "../common/context.h" + +using namespace winrt; +using namespace Microsoft::UI::Xaml; +using namespace Microsoft::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Interop; +using namespace winrt::Windows::Foundation; +using namespace winrt::Microsoft::UI::Xaml::Controls::Primitives; + + + +void ui_set_button_label(ButtonBase button, const char* label, const char* stockid, const char *icon, UiLabelType type) { + // TODO: stockid + + if (type == UI_LABEL_ICON) { + label = NULL; + } + else if (type == UI_LABEL_TEXT) { + icon = NULL; + } + + IconElement icon_elm = { nullptr }; + if (icon) { + icon_elm = ui_get_icon(icon); + } + + if (label && icon_elm) { + StackPanel panel = StackPanel(); + panel.Orientation(Orientation::Horizontal); + panel.Spacing(5); + + panel.Children().Append(icon_elm); + + wchar_t* wlabel = str2wstr(label, nullptr); + TextBlock label = TextBlock(); + label.Text(wlabel); + panel.Children().Append(label); + free(wlabel); + + button.Content(panel); + } + else if (label) { + wchar_t* wlabel = str2wstr(label, nullptr); + button.Content(box_value(wlabel)); + free(wlabel); + } + else if (icon_elm) { + button.Content(ui_get_icon(icon)); + } +} + + +UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) { + UiObject* current = uic_current_obj(obj); + + // create button with label + Button button = Button(); + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // register callback + if (args.onclick) { + ui_callback cbfunc = args.onclick; + void* cbdata = args.onclickdata; + button.Click([cbfunc, cbdata, obj](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = 0; + cbfunc(&evt, cbdata); + }); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + + +void togglebutton_register_checked_observers(ToggleButton button, UiObject* obj, UiVar* var) { + button.Checked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +void togglebutton_register_unchecked_observers(ToggleButton button, UiObject* obj, UiVar* var) { + button.Unchecked([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +void togglebutton_register_callback(ToggleButton button, UiObject *obj, UiToggleArgs& args) { + ui_callback callback = args.onchange; + void* cbdata = args.onchangedata; + if (callback) { + button.Checked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, true); + callback(&evt, cbdata); + }); + button.Unchecked([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, false); + callback(&evt, cbdata); + }); + } +} + +// for some stupid reason the ToggleSwitch is not a ToggleButton and we need to write the same +// code again, because although everything is basically the same, it is named differently +static void switch_register_observers(ToggleSwitch button, UiObject* obj, UiVar* var) { + button.Toggled([button, obj, var](IInspectable const& sender, RoutedEventArgs) { + UiInteger* i = (UiInteger*)var->value; + UiEvent evt = ui_create_int_event(obj, i->get(i)); + ui_notify_evt(i->observers, &evt); + }); +} + +static void switch_register_callback(ToggleSwitch button, UiObject* obj, UiToggleArgs& args) { + ui_callback callback = args.onchange; + void* cbdata = args.onchangedata; + if (callback) { + button.Toggled([button, obj, callback, cbdata](IInspectable const& sender, RoutedEventArgs) { + UiEvent evt = ui_create_int_event(obj, button.IsOn()); + callback(&evt, cbdata); + }); + } +} + +static void togglebutton_changed(UiObject *obj, bool checked, ui_callback onchange, void *onchangedata, int enable_state) { + if (onchange) { + UiEvent evt; + evt.obj = obj; + evt.window = obj->window; + evt.document = obj->ctx->document; + evt.eventdata = nullptr; + evt.intval = checked; + onchange(&evt, onchangedata); + } + if (enable_state > 0) { + if (checked) { + ui_set_group(obj->ctx, enable_state); + } else { + ui_unset_group(obj->ctx, enable_state); + } + } +} + +static UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) { + UiObject* current = uic_current_obj(obj); + + // set label + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + togglebutton_register_callback(button, obj, args); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind variable + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_INTEGER); + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + + // listener for notifying observers + togglebutton_register_checked_observers(button, obj, var); + togglebutton_register_unchecked_observers(button, obj, var); + } + + if (args.enable_group > 0 || args.onchange) { + button.Checked([obj, args](IInspectable const& sender, RoutedEventArgs) { + togglebutton_changed(obj, true, args.onchange, args.onchangedata, args.enable_group); + }); + button.Unchecked([obj, args](IInspectable const& sender, RoutedEventArgs) { + togglebutton_changed(obj, false, args.onchange, args.onchangedata, args.enable_group); + }); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + +UIWIDGET ui_togglebutton_create(UiObject* obj, UiToggleArgs args) { + ToggleButton button = ToggleButton(); + return create_togglebutton(obj, button, args); +} + +UIWIDGET ui_checkbox_create(UiObject* obj, UiToggleArgs args) { + CheckBox button = CheckBox(); + return create_togglebutton(obj, button, args); +} + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs args) { + ToggleSwitch button = ToggleSwitch(); + if (args.label) { + wchar_t* wlabel = str2wstr(args.label, nullptr); + button.Header(box_value(wlabel)); + free(wlabel); + } + switch_register_callback(button, obj, args); + + UiObject* current = uic_current_obj(obj); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + // bind variable + UiVar* var = nullptr; + if (args.value) { + var = uic_create_value_var(current->ctx, args.value); + } + else if (args.varname) { + var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); + } + if (var) { + UiInteger* value = (UiInteger*)var->value; + value->obj = widget; + value->get = ui_toggle_button_get; + value->set = ui_toggle_button_set; + + // listener for notifying observers + switch_register_observers(button, obj, var); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + +UIWIDGET ui_radiobutton_create(UiObject* obj, UiToggleArgs args) { + RadioButton button = RadioButton(); + + UiObject* current = uic_current_obj(obj); + + // set label + ui_set_button_label(button, args.label, args.stockid, args.icon, args.labeltype); + togglebutton_register_callback(button, obj, args); + + // create toolkit wrapper object and register destructor + UIElement elm = button; + UiWidget* widget = new UiWidget(elm); + ui_context_add_widget_destructor(current->ctx, widget); + ui_set_widget_groups(current->ctx, widget, args.groups); + + UiVar* var = nullptr; + if (args.value) { + var = uic_create_value_var(current->ctx, args.value); + } + else if (args.varname) { + var = uic_create_var(obj->ctx, args.varname, UI_VAR_INTEGER); + } + + // bind radio button to the value + if (var) { + UiInteger* value = (UiInteger*)var->value; + + // store a list of radio buttons in the value + CxList* radioButtons = (CxList*) (value->obj ? value->obj : cxLinkedListCreate(current->ctx->allocator, NULL, CX_STORE_POINTERS)); + // get or create the group name + static int groupCount = 0; + winrt::hstring groupName; + if (cxListSize(radioButtons) == 0) { + groupName = winrt::to_hstring(groupCount++); + } else { + UiWidget* firstButtonWidget = (UiWidget*)cxListAt(radioButtons, 0); + RadioButton firstRadioButton = firstButtonWidget->uielement.as<RadioButton>(); + groupName = firstRadioButton.GroupName(); + } + + // set the group name for the new radiobutton + cxListAdd(radioButtons, widget); + button.GroupName(groupName); + + value->obj = radioButtons; + value->get = ui_radio_button_get; + value->set = ui_radio_button_set; + + // listener for notifying observers (only checked, not unchecked) + togglebutton_register_checked_observers(button, obj, var); + } + + // add button to current container + UI_APPLY_LAYOUT1(current, args); + + current->container->Add(button, false); + + return widget; +} + + +int64_t ui_toggle_button_get(UiInteger* integer) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleButton toggleButton = widget->uielement.as<ToggleButton>(); + int val = toggleButton.IsChecked().GetBoolean(); + integer->value = val; + return val; +} + +void ui_toggle_button_set(UiInteger* integer, int64_t value) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleButton toggleButton = widget->uielement.as<ToggleButton>(); + toggleButton.IsChecked((bool)value); + integer->value = value; +} + +int64_t ui_switch_get(UiInteger * integer) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>(); + int val = toggleButton.IsOn(); + integer->value = val; + return val; +} + +void ui_switch_set(UiInteger * integer, int64_t value) { + UiWidget* widget = (UiWidget*)integer->obj; + ToggleSwitch toggleButton = widget->uielement.as<ToggleSwitch>(); + toggleButton.IsOn((bool)value); + integer->value = value; +} + +int64_t ui_radio_button_get(UiInteger * integer) { + CxList* list = (CxList*)integer->obj; + CxIterator i = cxListIterator(list); + int selection = -1; + cx_foreach(UiWidget*, widget, i) { + ToggleButton button = widget->uielement.as<ToggleButton>(); + if (button.IsChecked().GetBoolean()) { + selection = i.index; + break; + } + } + integer->value = selection; + return selection; +} + +void ui_radio_button_set(UiInteger * integer, int64_t value) { + CxList* list = (CxList*)integer->obj; + UiWidget* widget = (UiWidget*)cxListAt(list, value); + if (widget) { + ToggleButton button = widget->uielement.as<ToggleButton>(); + button.IsChecked(true); + integer->value = value; + } +}