ui/winui/button.cpp

changeset 0
2483f517c562
child 3
f154867f54dc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/winui/button.cpp	Sun Jan 21 16:30:18 2024 +0100
@@ -0,0 +1,346 @@
+/*
+ * 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;
+
+
+
+static void set_button_label(ButtonBase button, const char* label, const char* stockid) {
+	if (label) {
+		wchar_t* wlabel = str2wstr(label, nullptr);
+		button.Content(box_value(wlabel));
+		free(wlabel);
+	}
+	// TODO: stockid
+}
+
+
+UIWIDGET ui_button_create(UiObject* obj, UiButtonArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// create button with label
+	Button button = Button();
+	set_button_label(button, args.label, args.stockid);
+
+	// create toolkit wrapper object and register destructor
+	UIElement elm = button;
+	UiWidget* widget = new UiWidget(elm);
+	ui_context_add_widget_destructor(current->ctx, widget);
+
+	// 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 UIWIDGET create_togglebutton(UiObject *obj, ToggleButton button, UiToggleArgs args) {
+	UiObject* current = uic_current_obj(obj);
+
+	// set label
+	set_button_label(button, args.label, args.stockid);
+	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);
+
+	// 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);
+	}
+
+	// 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);
+
+	// 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
+	set_button_label(button, args.label, args.stockid);
+	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);
+
+	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 (radioButtons->size == 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;
+	}
+}

mercurial