ref: 5da27e00a50fa64e340f62ad813858b8a9de2260
parent: ac31afbe7610df77b6658b84afaaef4e01c7028d
author: Simon Howard <fraggle@soulsphere.org>
date: Wed Jul 8 21:51:05 EDT 2015
textscreen: Add table cell overflowing. It's a common pattern in the setup tool that widgets are laid out in a grid, except there inevitably end up being widgets that do not conform to this grid. This become ugly to code around, as it ends up requiring packing tables inside tables to get the desired result, when all that is really wanted is a widget that can take up more than one cell in the table. Overflowing allows cells in the table to be designated as "overflows" of neighboring cells, to facilitate this.
--- a/textscreen/examples/guitest.c
+++ b/textscreen/examples/guitest.c
@@ -103,7 +103,7 @@
toplabel = TXT_NewLabel("This is a multiline label.\n"
"A single label object contains \n"
- "all three of these lines.\n");
+ "all three of these lines.");
TXT_AddWidget(window, toplabel);
TXT_SetWidgetAlign(toplabel, TXT_HORIZ_CENTER);
@@ -110,7 +110,7 @@
//TXT_AddWidget(window, TXT_NewScrollPane(15, 4, table));
TXT_AddWidget(window, table);
- for (i=0; i<5; ++i)
+ for (i=0; i<3; ++i)
{
TXT_snprintf(buf, sizeof(buf), "Option %i in a table:", i + 1);
TXT_AddWidget(table, TXT_NewLabel(buf));
@@ -119,6 +119,17 @@
TXT_snprintf(buf, sizeof(buf), " Button %i-2 ", i + 1);
TXT_AddWidget(table, TXT_NewButton(buf));
}
+
+ TXT_AddWidgets(table,
+ TXT_NewLabel("Still the same table, but:\n"
+ "This label magically overflows\n"
+ "across multiple cells! Cool, huh? "),
+ TXT_TABLE_OVERFLOW_RIGHT,
+ TXT_NewButton("Do nothing"),
+ TXT_TABLE_OVERFLOW_DOWN,
+ TXT_TABLE_OVERFLOW_DOWN,
+ TXT_NewButton("Also nothing"),
+ NULL);
TXT_AddWidget(window, TXT_NewStrut(0, 1));
value_label = TXT_NewLabel("");
--- a/textscreen/txt_checkbox.c
+++ b/textscreen/txt_checkbox.c
@@ -65,7 +65,7 @@
TXT_SetWidgetBG(checkbox);
TXT_DrawString(checkbox->label);
- for (i=strlen(checkbox->label); i < w-5; ++i)
+ for (i=strlen(checkbox->label); i < w-4; ++i)
{
TXT_DrawString(" ");
}
--- a/textscreen/txt_table.c
+++ b/textscreen/txt_table.c
@@ -26,6 +26,18 @@
#include "txt_strut.h"
#include "txt_table.h"
+const txt_widget_t txt_table_overflow_right = {};
+const txt_widget_t txt_table_overflow_down = {};
+
+// Returns true if the given widget in the table's widgets[] array refers
+// to an actual widget - not NULL, or one of the special overflow pointers.
+static int IsActualWidget(txt_widget_t *widget)
+{
+ return widget != NULL
+ && widget != &txt_table_overflow_right
+ && widget != &txt_table_overflow_down;
+}
+
// Remove all entries from a table
void TXT_ClearTable(TXT_UNCAST_ARG(table))
@@ -39,12 +51,12 @@
for (i=table->columns; i<table->num_widgets; ++i)
{
- if (table->widgets[i] != NULL)
+ if (IsActualWidget(table->widgets[i]))
{
TXT_DestroyWidget(table->widgets[i]);
}
}
-
+
// Shrink the table to just the column strut widgets
table->num_widgets = table->columns;
@@ -62,8 +74,97 @@
return (table->num_widgets + table->columns - 1) / table->columns;
}
-static void CalcRowColSizes(txt_table_t *table,
- unsigned int *row_heights,
+// Most widgets occupy just one cell of a table, but if the special
+// overflow constants are used, they can occupy multiple cells.
+// This function figures out for a widget in a given cell, which
+// cells it should actually occupy (always a rectangle).
+static void CellOverflowedSize(txt_table_t *table, int x, int y,
+ int *w, int *h)
+{
+ txt_widget_t *widget;
+ int x1, y1;
+
+ if (!IsActualWidget(table->widgets[y * table->columns + x]))
+ {
+ *w = 0; *h = 0;
+ return;
+ }
+
+ *w = table->columns - x;
+ *h = 0;
+ for (y1 = y; y1 < TableRows(table); ++y1)
+ {
+ // Every overflow cell must point to either (x, y) or another
+ // overflow cell. This means the first in every row must be
+ // txt_table_overflow_down.
+ widget = table->widgets[y1 * table->columns + x];
+ if (y1 != y && widget != &txt_table_overflow_down)
+ {
+ break;
+ }
+
+ for (x1 = x + 1; x1 < x + *w; ++x1)
+ {
+ if (y1 * table->columns + x1 >= table->num_widgets)
+ {
+ break;
+ }
+
+ // Can be either type of overflow, except on the first row.
+ // Otherwise we impose a limit on the width.
+ widget = table->widgets[y1 * table->columns + x1];
+ if (widget != &txt_table_overflow_right
+ && (widget != &txt_table_overflow_down || y1 == y))
+ {
+ *w = x1 - x;
+ break;
+ }
+ }
+
+ ++*h;
+ }
+}
+
+static int IsOverflowingCell(txt_table_t *table, int x, int y)
+{
+ int w, h;
+ CellOverflowedSize(table, x, y, &w, &h);
+ return w > 1 || h > 1;
+}
+
+// Using the given column/row size tables, calculate the size of the given
+// widget, storing the result in (w, h).
+static void CalculateWidgetDimensions(txt_table_t *table,
+ int x, int y,
+ unsigned int *column_widths,
+ unsigned int *row_heights,
+ unsigned int *w, unsigned int *h)
+{
+ txt_widget_t *widget;
+ int cell_w, cell_h;
+ int x1, y1;
+
+ widget = table->widgets[y * table->columns + x];
+
+ // Find which cells this widget occupies.
+ CellOverflowedSize(table, x, y, &cell_w, &cell_h);
+
+ // Add up column / row widths / heights to get the actual dimensions.
+ *w = 0;
+ for (x1 = x; x1 < x + cell_w; ++x1)
+ {
+ *w += column_widths[x1];
+ }
+
+ *h = 0;
+ for (y1 = y; y1 < y + cell_h; ++y1)
+ {
+ *h += row_heights[y1];
+ }
+}
+
+static void CalcRowColSizes(txt_table_t *table,
+ unsigned int *row_heights,
unsigned int *col_widths)
{
int x, y;
@@ -74,11 +175,11 @@
memset(col_widths, 0, sizeof(int) * table->columns);
- for (y=0; y<rows; ++y)
+ for (y = 0; y < rows; ++y)
{
row_heights[y] = 0;
- for (x=0; x<table->columns; ++x)
+ for (x = 0; x < table->columns; ++x)
{
if (y * table->columns + x >= table->num_widgets)
break;
@@ -85,11 +186,20 @@
widget = table->widgets[y * table->columns + x];
- // NULL represents an empty spacer
-
- if (widget != NULL)
+ if (IsActualWidget(widget))
{
TXT_CalcWidgetSize(widget);
+ }
+
+ // In the first pass we ignore overflowing cells.
+ if (IsOverflowingCell(table, x, y))
+ {
+ continue;
+ }
+
+ // NULL represents an empty spacer
+ if (IsActualWidget(widget))
+ {
if (widget->h > row_heights[y])
row_heights[y] = widget->h;
if (widget->w > col_widths[x])
@@ -97,6 +207,37 @@
}
}
}
+
+ // In the second pass, we go through again and process overflowing
+ // widgets, to ensure that they will fit.
+ for (y = 0; y < rows; ++y)
+ {
+ for (x = 0; x < table->columns; ++x)
+ {
+ unsigned int w, h;
+
+ if (y * table->columns + x >= table->num_widgets)
+ break;
+
+ widget = table->widgets[y * table->columns + x];
+ if (!IsActualWidget(widget))
+ {
+ continue;
+ }
+
+ // Expand column width and row heights as needed.
+ CalculateWidgetDimensions(table, x, y, col_widths, row_heights,
+ &w, &h);
+ if (w < widget->w)
+ {
+ col_widths[x] += widget->w - w;
+ }
+ if (h < widget->h)
+ {
+ row_heights[y] += widget->h - h;
+ }
+ }
+ }
}
static void TXT_CalcTableSize(TXT_UNCAST_ARG(table))
@@ -143,17 +284,15 @@
last_widget = table->widgets[table->num_widgets - 1];
- if (widget != NULL && last_widget != NULL
+ if (IsActualWidget(widget)
+ && IsActualWidget(last_widget)
&& widget->widget_class == &txt_separator_class
&& last_widget->widget_class == &txt_separator_class)
{
- // The previous widget added was a separator; replace
- // it with this one.
- //
- // This way, if the first widget added to a window is
- // a separator, it replaces the "default" separator that
- // the window itself adds on creation.
-
+ // The previous widget added was a separator; replace it with
+ // this one. This way, if the first widget added to a window is
+ // a separator, it replaces the "default" separator that the
+ // window itself adds on creation.
table->widgets[table->num_widgets - 1] = widget;
TXT_DestroyWidget(last_widget);
@@ -169,7 +308,7 @@
// Maintain parent pointer.
- if (widget != NULL)
+ if (IsActualWidget(widget))
{
widget->parent = &table->widget;
}
@@ -217,7 +356,7 @@
if (i >= 0 && i < table->num_widgets)
{
widget = table->widgets[i];
- return widget != NULL
+ return IsActualWidget(widget)
&& TXT_SelectableWidget(widget)
&& widget->visible;
}
@@ -280,7 +419,7 @@
{
cur_widget = table->widgets[i];
- if (table->widget.focused && cur_widget != NULL)
+ if (table->widget.focused && IsActualWidget(cur_widget))
{
TXT_SetWidgetFocus(cur_widget, 0);
}
@@ -313,7 +452,7 @@
if (selected >= 0 && selected < table->num_widgets)
{
- if (table->widgets[selected] != NULL
+ if (IsActualWidget(table->widgets[selected])
&& TXT_SelectableWidget(table->widgets[selected])
&& TXT_WidgetKeyPress(table->widgets[selected], key))
{
@@ -330,7 +469,7 @@
for (new_y = table->selected_y + 1; new_y < rows; ++new_y)
{
new_x = FindSelectableColumn(table, new_y, table->selected_x);
-
+
if (new_x >= 0)
{
// Found a selectable widget in this column!
@@ -428,13 +567,16 @@
}
}
-static void LayoutCell(txt_table_t *table, int x, int y, int col_width,
+static void LayoutCell(txt_table_t *table, int x, int y,
int draw_x, int draw_y)
{
txt_widget_t *widget;
+ int col_width;
widget = table->widgets[y * table->columns + x];
+ col_width = widget->w;
+
// Adjust x position based on alignment property
switch (widget->align)
@@ -445,7 +587,7 @@
case TXT_HORIZ_CENTER:
TXT_CalcWidgetSize(widget);
-
+
// Separators are always drawn left-aligned.
if (widget->widget_class != &txt_separator_class)
@@ -452,17 +594,16 @@
{
draw_x += (col_width - widget->w) / 2;
}
-
+
break;
case TXT_HORIZ_RIGHT:
TXT_CalcWidgetSize(widget);
-
+
if (widget->widget_class != &txt_separator_class)
{
draw_x += col_width - widget->w;
}
-
break;
}
@@ -481,6 +622,7 @@
TXT_CAST_ARG(txt_table_t, table);
unsigned int *column_widths;
unsigned int *row_heights;
+ txt_widget_t *widget;
int draw_x, draw_y;
int x, y;
int i;
@@ -505,9 +647,9 @@
}
// Draw all cells
-
+
draw_y = table->widget.y;
-
+
for (y=0; y<rows; ++y)
{
draw_x = table->widget.x;
@@ -519,10 +661,14 @@
if (i >= table->num_widgets)
break;
- if (table->widgets[i] != NULL)
+ widget = table->widgets[i];
+
+ if (IsActualWidget(widget))
{
- LayoutCell(table, x, y, column_widths[x],
- draw_x, draw_y);
+ CalculateWidgetDimensions(table, x, y,
+ column_widths, row_heights,
+ &widget->w, &widget->h);
+ LayoutCell(table, x, y, draw_x, draw_y);
}
draw_x += column_widths[x];
@@ -552,7 +698,7 @@
{
widget = table->widgets[i];
- if (widget != NULL)
+ if (IsActualWidget(widget))
{
TXT_GotoXY(widget->x, widget->y);
TXT_DrawWidget(widget);
@@ -574,7 +720,7 @@
// NULL widgets are spacers
- if (widget != NULL)
+ if (IsActualWidget(widget))
{
if (x >= widget->x && x < (signed) (widget->x + widget->w)
&& y >= widget->y && y < (signed) (widget->y + widget->h))
@@ -617,7 +763,7 @@
for (i = 0; i < table->num_widgets; ++i)
{
- if (table->widgets[i] != NULL
+ if (IsActualWidget(table->widgets[i])
&& TXT_SelectableWidget(table->widgets[i]))
{
ChangeSelection(table, i % table->columns, i / table->columns);
@@ -641,7 +787,7 @@
if (i < table->num_widgets)
{
- if (table->widgets[i] != NULL)
+ if (IsActualWidget(table->widgets[i]))
{
TXT_SetWidgetFocus(table->widgets[i], focused);
}
@@ -777,6 +923,11 @@
if (index >= 0 && index < table->num_widgets)
{
result = table->widgets[index];
+
+ if (!IsActualWidget(result))
+ {
+ result = NULL;
+ }
}
if (result != NULL && result->widget_class == &txt_table_class)
@@ -798,7 +949,7 @@
for (i=0; i<table->num_widgets; ++i)
{
- if (table->widgets[i] == NULL)
+ if (!IsActualWidget(table->widgets[i]))
{
continue;
}
--- a/textscreen/txt_table.h
+++ b/textscreen/txt_table.h
@@ -22,6 +22,20 @@
*/
/**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell to the left can overflow into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_RIGHT (&txt_table_overflow_right)
+
+/**
+ * Magic value that if used in a table, will indicate that the cell is
+ * empty and the widget in the cell above it can overflow down into it.
+ */
+
+#define TXT_TABLE_OVERFLOW_DOWN (&txt_table_overflow_down)
+
+/**
* Table widget.
*
* A table is a widget that contains other widgets. It may have
@@ -45,21 +59,20 @@
// Widgets in this table
// The widget at (x,y) in the table is widgets[columns * y + x]
-
txt_widget_t **widgets;
int num_widgets;
// Number of columns
-
int columns;
- // Currently selected
-
+ // Currently selected:
int selected_x;
int selected_y;
};
extern txt_widget_class_t txt_table_class;
+extern const txt_widget_t txt_table_overflow_right;
+extern const txt_widget_t txt_table_overflow_down;
void TXT_InitTable(txt_table_t *table, int columns);