diff -r 000000000000 -r 2483f517c562 ui/winui/container.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/winui/container.cpp Sun Jan 21 16:30:18 2024 +0100 @@ -0,0 +1,705 @@ +/* + * 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{ 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; +} +