ui/winui/container.cpp

Sun, 28 Jan 2024 19:33:56 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 28 Jan 2024 19:33:56 +0100
branch
newapi
changeset 234
9036b346cd66
parent 221
a82d9beaa94a
child 374
eae5d6623fd3
permissions
-rw-r--r--

implement ui_job() and add ui_call_mainthread (WinUI3)

/*
 * 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 "container.h"

#include "../common/context.h"
#include "../common/object.h"

#include "util.h"


void ui_container_begin_close(UiObject* obj) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->close = 1;
}

int ui_container_finish(UiObject* obj) {
	UiContainer* ct = uic_get_current_container(obj);
	if (ct->close) {
		ui_end(obj);
		return 0;
	}
	return 1;
}


// --------------------- UiBoxContainer ---------------------

static UIWIDGET ui_box(UiObject* obj, UiContainerArgs args, UiBoxContainerType type) {
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);

	Grid grid = Grid();
	current->container->Add(grid, true);

	UIElement elm = grid;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = new UiBoxContainer(grid, type, args.margin, args.spacing);
	ui_context_add_container_destructor(current->ctx, newobj->container);
	uic_obj_add(obj, newobj);

	return widget;
}

UIWIDGET ui_vbox_create(UiObject* obj, UiContainerArgs args) {
	return ui_box(obj, args, UI_BOX_CONTAINER_VBOX);
}

UIWIDGET ui_hbox_create(UiObject* obj, UiContainerArgs args) {
	return ui_box(obj, args, UI_BOX_CONTAINER_HBOX);
}

UiBoxContainer::UiBoxContainer(Grid grid, enum UiBoxContainerType type, int margin, int spacing) {
	this->grid = grid;
	this->type = type;

	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
	grid.Margin(t);
	grid.ColumnSpacing((double)spacing);
	grid.RowSpacing((double)spacing);

	GridLength gl;
	gl.Value = 1;
	gl.GridUnitType = GridUnitType::Star;

	// hbox needs one row def, vbox needs one col def
	// all other col/row defs are created when elements are added
	if (type == UI_BOX_CONTAINER_HBOX) {
		boxRowDef = RowDefinition();
		boxRowDef.Height(gl);
		grid.RowDefinitions().Append(boxRowDef);
	} else {
		boxColDef = ColumnDefinition();
		boxColDef.Width(gl);
		grid.ColumnDefinitions().Append(boxColDef);
	}

	ui_reset_layout(layout);
}

void UiBoxContainer::Add(FrameworkElement control, UiBool fill) {
	if (this->layout.fill != UI_LAYOUT_UNDEFINED) {
		fill = ui_lb2bool(this->layout.fill);
	}

	GridLength gl;
	if (fill) {
		gl.Value = 1;
		gl.GridUnitType = GridUnitType::Star;
	}
	else {
		gl.Value = 0;
		gl.GridUnitType = GridUnitType::Auto;
	}
	
	control.HorizontalAlignment(HorizontalAlignment::Stretch);
	control.VerticalAlignment(VerticalAlignment::Stretch);

	if (type == UI_CONTAINER_HBOX) {
		ColumnDefinition coldef = ColumnDefinition();
		coldef.Width(gl);
		grid.ColumnDefinitions().Append(coldef);
		grid.SetColumn(control, grid.Children().Size());
		grid.SetRow(control, 0);
	} else {
		RowDefinition rowdef = RowDefinition();
		rowdef.Height(gl);
		grid.RowDefinitions().Append(rowdef);
		grid.SetRow(control, grid.Children().Size());
		grid.SetColumn(control, 0);
	}

	grid.Children().Append(control);

	ui_reset_layout(layout);
}


// --------------------- UiGridContainer ---------------------

UIWIDGET ui_grid_create(UiObject* obj, UiContainerArgs args) {
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);

	Grid grid = Grid();
	current->container->Add(grid, true);

	UIElement elm = grid;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = new UiGridContainer(grid, args.margin, args.columnspacing, args.rowspacing);
	ui_context_add_container_destructor(current->ctx, newobj->container);
	uic_obj_add(obj, newobj);

	return widget;
}

UiGridContainer::UiGridContainer(Grid grid, int margin, int columnspacing, int rowspacing) {
	this->grid = grid;
	Thickness t = { (double)margin, (double)margin, (double)margin, (double)margin };
	grid.Margin(t);
	grid.ColumnSpacing((double)columnspacing);
	grid.RowSpacing((double)rowspacing);
	ui_reset_layout(layout);
}

void UiGridContainer::Add(FrameworkElement control, UiBool fill) {
	GridLength gl;

	int hexpand = FALSE;
	int vexpand = FALSE;
	if (layout.hexpand != UI_LAYOUT_UNDEFINED) {
		hexpand = layout.hexpand;
	}
	if (layout.vexpand != UI_LAYOUT_UNDEFINED) {
		vexpand = layout.vexpand;
	}

	// create new RowDefinition for the new line
	if (layout.newline || y == -1) {
		x = 0;
		y++;
		RowDefinition rowdef = RowDefinition();
		if (vexpand) {
			gl.GridUnitType = GridUnitType::Star;
			gl.Value = 1;
		}
		else {
			gl.GridUnitType = GridUnitType::Auto;
			gl.Value = 0;
		}
		rowdef.Height(gl);
		grid.RowDefinitions().Append(rowdef);
	}

	// create new columndefinition, if a new column is added
	if (x == cols) {
		if (hexpand) {
			gl.GridUnitType = GridUnitType::Star;
			gl.Value = 1;
		}
		else {
			gl.GridUnitType = GridUnitType::Auto;
			gl.Value = 0;
		}
		ColumnDefinition coldef = ColumnDefinition();
		coldef.Width(gl);
		grid.ColumnDefinitions().Append(coldef);
		cols++;
	}

	// add control
	control.HorizontalAlignment(HorizontalAlignment::Stretch);
	control.VerticalAlignment(VerticalAlignment::Stretch);

	if (layout.colspan > 0) {
		grid.SetColumnSpan(control, layout.colspan);
	}
	if (layout.rowspan > 0) {
		grid.SetRowSpan(control, layout.rowspan);
	}

	grid.SetRow(control, y);
	grid.SetColumn(control, x);
	grid.Children().Append(control);

	x++;

	ui_reset_layout(layout);
}

// --------------------- UI Frame ---------------------

UIWIDGET ui_frame_create(UiObject* obj, UiFrameArgs args) {
	// create a grid for the frame, that contains the label and a sub-frame
	Grid frame = Grid();

	GridLength gl;
	gl.GridUnitType = GridUnitType::Star;
	gl.Value = 1;

	ColumnDefinition coldef = ColumnDefinition();
	coldef.Width(gl);
	frame.ColumnDefinitions().Append(coldef);

	RowDefinition rowdefFrame = RowDefinition();
	rowdefFrame.Height(gl);

	// label
	int row = 0;
	if (args.label) {
		RowDefinition rowdefLabel = RowDefinition();
		gl.GridUnitType = GridUnitType::Auto;
		gl.Value = 0;
		rowdefLabel.Height(gl);
		frame.RowDefinitions().Append(rowdefLabel);

		TextBlock label = TextBlock();
		wchar_t* wlabel = str2wstr(args.label, nullptr);
		winrt::hstring hstr(wlabel);
		label.Text(hstr);
		free(wlabel);

		frame.SetRow(label, row++);
		frame.SetColumn(label, 0);
		frame.Children().Append(label);
	}
	
	// workarea frame
	frame.RowDefinitions().Append(rowdefFrame);

	Grid workarea = Grid();
	frame.SetRow(workarea, row);
	frame.SetColumn(workarea, 0);
	frame.Children().Append(workarea);

	// some styling for the workarea
	winrt::Microsoft::UI::Xaml::Media::SolidColorBrush brush{ winrt::Microsoft::UI::ColorHelper::FromArgb(150, 150, 150, 150) };
	workarea.BorderBrush(brush);
	CornerRadius radius{ 8, 8, 8, 8 };
	Thickness t = { 1, 1, 1, 1 };
	workarea.CornerRadius(radius);
	workarea.BorderThickness(t);

	Thickness padding = { 10, 10, 10, 10 };
	workarea.Padding(padding);

	// add frame to the parent container
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);
	current->container->Add(frame, true);

	UIElement elm = frame;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);

	// sub container
	UiContainer* ctn = nullptr;
	switch (args.subcontainer) {
		default:
		case UI_CONTAINER_VBOX: {
			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_HBOX: {
			ctn = new UiBoxContainer(workarea, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_GRID: {
			ctn = new UiGridContainer(workarea, args.margin, args.columnspacing, args.rowspacing);
			break;
		}
	}
	ui_context_add_container_destructor(current->ctx, ctn);

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = ctn;
	uic_obj_add(obj, newobj);

	return widget;
}

// --------------------- UI Expander ---------------------

UIWIDGET ui_expander_create(UiObject* obj, UiFrameArgs args) {
	Expander expander = Expander();
	if (args.label) {
		wchar_t* wlabel = str2wstr(args.label, nullptr);
		expander.Header(box_value(wlabel));
		free(wlabel);
	}
	expander.IsExpanded(args.isexpanded);

	// add frame to the parent container
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);
	current->container->Add(expander, true);

	UIElement elm = expander;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);

	Grid content = Grid();
	expander.Content(content);

	UiContainer* ctn = nullptr;
	switch (args.subcontainer) {
		default: 
		case UI_CONTAINER_VBOX: {
			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_HBOX: {
			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_GRID: {
			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
			break;
		}
	}
	ui_context_add_container_destructor(current->ctx, ctn);

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = ctn;
	uic_obj_add(obj, newobj);

	return widget;
}

// --------------------- UI ScrolledWindow ---------------------

UIWIDGET ui_scrolledwindow_create(UiObject* obj, UiFrameArgs args) {
	ScrollViewer scrollW = ScrollViewer();

	// add frame to the parent container
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);
	current->container->Add(scrollW, true);

	UIElement elm = scrollW;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);

	// create child container
	Grid content = Grid();
	scrollW.Content(content);

	UiContainer* ctn = nullptr;
	switch (args.subcontainer) {
		default:
		case UI_CONTAINER_VBOX: {
			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_VBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_HBOX: {
			ctn = new UiBoxContainer(content, UI_BOX_CONTAINER_HBOX, args.margin, args.spacing);
			break;
		}
		case UI_CONTAINER_GRID: {
			ctn = new UiGridContainer(content, args.margin, args.columnspacing, args.rowspacing);
			break;
		}
	}
	ui_context_add_container_destructor(current->ctx, ctn);

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = ctn;
	uic_obj_add(obj, newobj);

	return widget;
}

// --------------------- UI TabView ---------------------

UiTabViewContainer::UiTabViewContainer(UiTabView* tabview) {
	this->tabview = tabview;
}

void UiTabViewContainer::Add(FrameworkElement control, UiBool fill) {
	// noop
}

static UiObject* create_subcontainer_obj(UiObject* current, Grid subcontainer, UiSubContainerType type, int margin, int spacing, int columnspacing, int rowspacing) {
	UiContainer* ctn = nullptr;
	switch (type) {
		default:
		case UI_CONTAINER_VBOX: {
			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_VBOX, margin, spacing);
			break;
		}
		case UI_CONTAINER_HBOX: {
			ctn = new UiBoxContainer(subcontainer, UI_BOX_CONTAINER_HBOX, margin, spacing);
			break;
		}
		case UI_CONTAINER_GRID: {
			ctn = new UiGridContainer(subcontainer, margin, columnspacing, rowspacing);
			break;
		}
	}
	ui_context_add_container_destructor(current->ctx, ctn);

	UIElement elm = subcontainer;
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);
	UiObject* newobj = uic_object_new(current, widget);
	newobj->container = ctn;
	return newobj;
}

UiPivotTabView::UiPivotTabView(UiObject* obj, Pivot pivot, UiTabViewArgs args) {
	this->current = obj;
	this->pivot = pivot;
	this->margin = args.margin;
	this->spacing = args.spacing;
	this->columnspacing = args.columnspacing;
	this->rowspacing = args.rowspacing;
}

UiObject* UiPivotTabView::AddTab(const char* label) {
	TextBlock text = TextBlock();
	wchar_t* wlabel = str2wstr(label, nullptr);
	winrt::hstring hstr(wlabel);
	text.Text(hstr);
	free(wlabel);

	PivotItem item = PivotItem();
	item.Header(text);

	// sub container
	Grid subcontainer = Grid();
	item.Content(subcontainer);
	pivot.Items().Append(item);

	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
}

FrameworkElement UiPivotTabView::GetFrameworkElement() {
	return pivot;
}

static UiTabView* tabview_pivot_create(UiObject* obj, UiTabViewArgs args) {
	Pivot pivot = Pivot();
	UiPivotTabView* tabview = new UiPivotTabView(obj, pivot, args);

	return tabview;
}

UiMainTabView::UiMainTabView(UiObject* obj, TabView tabview, UiTabViewArgs args) {
	this->current = obj;
	this->tabview = tabview;
	this->margin = args.margin;
	this->spacing = args.spacing;
	this->columnspacing = args.columnspacing;
	this->rowspacing = args.rowspacing;
}

UiObject* UiMainTabView::AddTab(const char* label) {
	TextBlock text = TextBlock();
	wchar_t* wlabel = str2wstr(label, nullptr);
	winrt::hstring hstr(wlabel);
	text.Text(hstr);
	free(wlabel);

	TabViewItem item = TabViewItem();
	item.Header(text);
	item.CanDrag(false);
	item.IsClosable(false);

	// sub container
	Grid subcontainer = Grid();
	item.Content(subcontainer);
	tabview.TabItems().Append(item);

	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
}

FrameworkElement UiMainTabView::GetFrameworkElement() {
	return tabview;
}

static UiTabView* tabview_main_create(UiObject* obj, UiTabViewArgs args) {
	TabView tabview = TabView();
	tabview.IsAddTabButtonVisible(false);
	//tabview.CanDragTabs(false);
	//tabview.CanReorderTabs(false);
	UiMainTabView* uitabview = new UiMainTabView(obj, tabview, args);

	return uitabview;
}

UiNavigationTabView::UiNavigationTabView(UiObject* obj, NavigationView navigationview, UiTabViewArgs args, UiTabViewType type) {
	this->current = obj;
	this->navigationview = navigationview;
	this->type = type;
	this->margin = args.margin;
	this->spacing = args.spacing;
	this->columnspacing = args.columnspacing;
	this->rowspacing = args.rowspacing;

	if (type == UI_TABVIEW_NAVIGATION_TOP) {
		navigationview.PaneDisplayMode(NavigationViewPaneDisplayMode::Top);
	}

	navigationview.SelectionChanged({ this, &UiNavigationTabView::SelectionChanged });
}

UiObject* UiNavigationTabView::AddTab(const char* label) {
	TextBlock text = TextBlock();
	wchar_t* wlabel = str2wstr(label, nullptr);
	winrt::hstring hstr(wlabel);
	text.Text(hstr);
	free(wlabel);

	NavigationViewItem item = NavigationViewItem();
	item.Content(text);

	// sub container
	Grid subcontainer = Grid();
	if (pages.size() == 0) {
		navigationview.Content(subcontainer);
		navigationview.SelectedItem(item);
	}

	navigationview.MenuItems().Append(item);
	auto page = std::tuple<NavigationViewItem, FrameworkElement>{ item, subcontainer };
	pages.push_back(page);

	return create_subcontainer_obj(current, subcontainer, this->subcontainer, margin, spacing, columnspacing, rowspacing);
}

FrameworkElement UiNavigationTabView::GetFrameworkElement() {
	return navigationview;
}

void UiNavigationTabView::SelectionChanged(NavigationView const& sender, NavigationViewSelectionChangedEventArgs const& args) {
	for (auto page : pages) {
		NavigationViewItem item = std::get<0>(page);
		FrameworkElement elm = std::get<1>(page);
		if (item == navigationview.SelectedItem()) {
			navigationview.Content(elm);
			break;
		}
	}
}

static UiTabView* tabview_navigationview_create(UiObject* obj, UiTabViewArgs args, UiTabViewType type) {
	NavigationView navigationview = NavigationView();
	UiNavigationTabView* tabview = new UiNavigationTabView(obj, navigationview, args, type);
	navigationview.IsBackButtonVisible(NavigationViewBackButtonVisible::Collapsed);
	navigationview.IsSettingsVisible(false);

	return tabview;
}

UIWIDGET ui_tabview_create(UiObject* obj, UiTabViewArgs args) {
	UiTabViewType type = args.tabview == UI_TABVIEW_DEFAULT ? UI_TABVIEW_NAVIGATION_TOP2 : args.tabview;
	UiTabView* tabview = nullptr;
	switch (type) {
		default: {
			tabview = tabview_pivot_create(obj, args);
			break;
		}
		case UI_TABVIEW_DOC: { 
			tabview = tabview_main_create(obj, args);
			break;
		}
		case UI_TABVIEW_NAVIGATION_SIDE: { 
			tabview = tabview_navigationview_create(obj, args, type);
			break;
		}
		case UI_TABVIEW_NAVIGATION_TOP: { 
			tabview = tabview_navigationview_create(obj, args, type);
			break;
		}
		case UI_TABVIEW_NAVIGATION_TOP2: { 
			tabview = tabview_pivot_create(obj, args);
			break;
		}
	}
	UiTabViewContainer* ctn = new UiTabViewContainer(tabview);

	// add frame to the parent container
	UiObject* current = uic_current_obj(obj);
	UI_APPLY_LAYOUT1(current, args);
	current->container->Add(tabview->GetFrameworkElement(), true);

	UIElement elm = tabview->GetFrameworkElement();
	UiWidget* widget = new UiWidget(elm);
	ui_context_add_widget_destructor(current->ctx, widget);
	widget->data1 = tabview;

	UiObject* newobj = uic_object_new(obj, widget);
	newobj->container = ctn;
	uic_obj_add(obj, newobj);

	return widget;
}

void ui_tab_create(UiObject* obj, const char* title) {
	UiObject* current = uic_current_obj(obj);
	UiTabView* tabview = (UiTabView*)current->widget->data1;
	UiObject* newobj = tabview->AddTab(title);
	uic_obj_add(current, newobj);
}

/*
 * -------------------- Layout Functions --------------------
 *
 * functions for setting layout attributes for the current container
 *
 */

void ui_layout_fill(UiObject* obj, UiBool fill) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.fill = ui_bool2lb(fill);
}

void ui_layout_hexpand(UiObject* obj, UiBool expand) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.hexpand = expand;
}

void ui_layout_vexpand(UiObject* obj, UiBool expand) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.vexpand = expand;
}

void ui_layout_width(UiObject* obj, int width) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.width = width;
}

void ui_layout_height(UiObject* obj, int height) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.height = height;
}

void ui_layout_colspan(UiObject* obj, int cols) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.colspan = cols;
}

void ui_layout_rowspan(UiObject* obj, int rows) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.rowspan = rows;
}

void ui_newline(UiObject* obj) {
	UiContainer* ct = uic_get_current_container(obj);
	ct->layout.newline = TRUE;
}

mercurial