ui/winui/button.cpp

Thu, 14 Nov 2024 22:28:36 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Thu, 14 Nov 2024 22:28:36 +0100
changeset 84
57a359c52617
parent 83
a612adaee43d
permissions
-rw-r--r--

load properties after resource creation

/*
 * 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;
	}
}

mercurial