--- /dev/null
+#include <ncurses.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "windows.h"
+
+struct Map {
+ int width;
+ int height;
+ int offset_x;
+ int offset_y;
+ int player_x;
+ int player_y;
+ char * cells; };
+
+void draw_with_linebreaks (struct Win * win, char * text, int start_y) {
+// Write text into window content space. Start on row start_y. Fill unused rows with whitespace.
+ int x, y;
+ char toggle;
+ int height_av = win->height - 1;
+ int width_av = win->width - 1;
+ char fin = 0;
+ int z = -1;
+ for (y = start_y; y < height_av; y++) {
+ if (0 == fin)
+ toggle = 0;
+ for (x = 0; x < width_av; x++) {
+ if (0 == toggle) {
+ z++;
+ if ('\n' == text[z]) {
+ mvwaddch(win->curses_win, y+1, x+win->border_left, ' ');
+ toggle = 1;
+ continue; }
+ else
+ mvwaddch(win->curses_win, y+1, x+win->border_left, text[z]);
+ if ('\n' == text[z+1]) {
+ z++;
+ toggle = 1; }
+ else if (0 == text[z+1]) {
+ toggle = 1;
+ fin = 1; } }
+ else
+ mvwaddch(win->curses_win, y+1, x+win->border_left, ' '); } } }
+
+void draw_text_from_bottom (struct Win * win) {
+// Draw text in win->data from end/bottom to the top.
+ char * text = (char *) win->data;
+ int width_av = win->width - 1;
+ int height_av = win->height - 1;
+ char toggle = 0;
+ int x, y, offset;
+ int z = -1;
+ for (y = 0; 0 == toggle; y++) // Determine number of lines text would have in a window of available
+ for (x = 0; x < width_av; x++) { // width, but infinite height.
+ z++;
+ if ('\n' == text[z]) // Treat \n and \0 as control characters for incrementing y and
+ break; // stopping the loop. Make sure they don't count as cell space
+ if ('\n' == text[z+1]) { // themselves.
+ z++;
+ break; }
+ else if (0 == text[z+1]) {
+ toggle = 1;
+ break; } }
+ z = -1;
+ int start_y = 0;
+ if (y < height_av) { // Depending on what is bigger, determine start point in window or in text.
+ start_y = height_av - y;
+ for (y = 0; y < start_y; y++)
+ for (x = 0; x < width_av; x++)
+ mvwaddch(win->curses_win, y+1, x+win->border_left, ' '); }
+ else if (y > height_av) {
+ offset = y - height_av;
+ for (y = 0; y < offset; y++)
+ for (x = 0; x < width_av; x++) {
+ z++;
+ if ('\n' == text[z])
+ break;
+ if ('\n' == text[z+1]) {
+ z++;
+ break; } }
+ text = text + (sizeof(char) * (z + 1)); }
+ draw_with_linebreaks(win, text, start_y); }
+
+void draw_map (struct Win * win) {
+// Draw map determined by win->data Map struct into window. Respect offset.
+ int height_av = win->height - 1;
+ int width_av = win->width - 1;
+ struct Map map = * (struct Map *) win->data;
+ char * cells = map.cells;
+ int width_map_av = map.width - map.offset_x;
+ int height_map_av = map.height - map.offset_y;
+ int x, y, z;
+ for (y = 0; y < height_av; y++) {
+ z = map.offset_x + (map.offset_y + y) * (map.width);
+ for (x = 0; x < width_av; x++) {
+ if (y < height_map_av && x < width_map_av) {
+ if (z == (map.width * map.player_y) + map.player_x)
+ mvwaddch(win->curses_win, y+1, x+win->border_left, '@');
+ else
+ mvwaddch(win->curses_win, y+1, x+win->border_left, cells[z]);
+ z++; }
+ else
+ mvwaddch(win->curses_win, y+1, x+win->border_left, ' '); } } }
+
+void draw_info (struct Win * win) {
+// Draw info window by appending win->data integer value to "Turn: " display.
+ int count = * (int *) win->data;
+ char text[100];
+ snprintf(text, 100, "Turn: %d", count);
+ draw_with_linebreaks(win, text, 0); }
+
+void toggle_window (struct WinMeta * win_meta, struct Win * win) {
+// Toggle display of window win.
+ if (0 != win->curses_win)
+ suspend_window(win_meta, win);
+ else
+ append_window(win_meta, win); }
+
+struct Map init_map () {
+// Initialize map with some experimental start values.
+ struct Map map;
+ map.width = 128;
+ map.height = 128;
+ map.offset_x = 0;
+ map.offset_y = 0;
+ map.player_x = 2;
+ map.player_y = 2;
+ map.cells = malloc(map.width * map.height);
+ int x, y;
+ for (y = 0; y < map.height; y++)
+ for (x = 0; x < map.width; x++)
+ map.cells[(y * map.width) + x] = '.';
+ map.cells[(5 * map.width) + 5] = 'X';
+ map.cells[(3 * map.width) + 8] = 'X';
+ map.cells[(8 * map.width) + 3] = 'X';
+ return map; }
+
+void update_info (struct Win * win) {
+// Update info data by incrementing turn value.
+ * (int *) win->data = * (int *) win->data + 1; }
+
+void update_log (struct Win * win, char * text) {
+// Update log with new text to be appended.
+ char * new_text;
+ int len_old = strlen(win->data);
+ int len_new = strlen(text);
+ int len_whole = len_old + len_new + 1;
+ new_text = calloc(len_whole, sizeof(char));
+ memcpy(new_text, win->data, len_old);
+ memcpy(new_text + len_old, text, len_new);
+ free(win->data);
+ win->data = new_text; }
+
+int main () {
+ WINDOW * screen = initscr();
+ noecho();
+ curs_set(0);
+ struct WinMeta win_meta = init_win_meta(screen);
+
+ struct Win win_map = init_window(&win_meta, "Map");
+ win_map.draw = draw_map;
+ struct Map map = init_map();
+ win_map.data = ↦
+
+ struct Win win_info = init_window(&win_meta, "Info");
+ win_info.draw = draw_info;
+ win_info.data = malloc(sizeof(int));
+ * (int *) win_info.data = 0;
+
+ struct Win win_log = init_window(&win_meta, "Log");
+ win_log.draw = draw_text_from_bottom;
+ win_log.data = calloc(1, sizeof(char));
+ update_log (&win_log, "Start!");
+
+ char key;
+ while (1) {
+ key = getch();
+ if (key == 'Q') // quit
+ break;
+ else if (key == '1') // toggle map window
+ toggle_window(&win_meta, &win_map);
+ else if (key == '2') // toggle info window
+ toggle_window(&win_meta, &win_info);
+ else if (key == '3') // toggle log window
+ toggle_window(&win_meta, &win_log);
+ else if (key == 'x') { // scroll map down
+ map.offset_y++;
+ draw_all_windows (&win_meta); }
+ else if (key == 'w' && map.offset_y > 0) { // scroll map up
+ map.offset_y--;
+ draw_all_windows (&win_meta); }
+ else if (key == 'd') { // scroll map right
+ map.offset_x++;
+ draw_all_windows (&win_meta); }
+ else if (key == 'a' && map.offset_x > 0) { // scroll map left
+ map.offset_x--;
+ draw_all_windows (&win_meta); }
+ else if (key == 'b' && map.player_y < map.height - 1) { // move player south
+ update_info (&win_info);
+ update_log (&win_log, "\nYou move south.");
+ map.player_y++;
+ draw_all_windows (&win_meta); }
+ else if (key == 't' && map.player_y > 0) { // move player north
+ update_info (&win_info);
+ update_log (&win_log, "\nYou move north.");
+ map.player_y--;
+ draw_all_windows (&win_meta); }
+ else if (key == 'h' && map.player_x < map.width - 1) { // move player east
+ update_info (&win_info);
+ update_log (&win_log, "\nYou move east.");
+ map.player_x++;
+ draw_all_windows (&win_meta); }
+ else if (key == 'f' && map.player_x > 0) { // move player west
+ update_info (&win_info);
+ update_log (&win_log, "\nYou move west.");
+ map.player_x--;
+ draw_all_windows (&win_meta); }
+ else if (key == '.') { // wait
+ update_info (&win_info);
+ update_log (&win_log, "\nYou wait.");
+ draw_all_windows(&win_meta); }
+ else if (key == '>' && win_meta.active != 0) // cycle forwards
+ cycle_active_window(&win_meta, 'n');
+ else if (key == '<' && win_meta.active != 0) // cycle backwards
+ cycle_active_window(&win_meta, 'p');
+ else if (key == 'y' && win_meta.active != 0) // shift window forwards
+ shift_window(&win_meta, 'f');
+ else if (key == 'Y' && win_meta.active != 0) // shift window backwards
+ shift_window(&win_meta, 'b');
+ else if (key == '*' && win_meta.active != 0) // grow window horizontally
+ resize_window(&win_meta, '*');
+ else if (key == '_' && win_meta.active != 0) // shrink window horizontally
+ resize_window(&win_meta, '_');
+ else if (key == '+' && win_meta.active != 0) // grow window vertically
+ resize_window(&win_meta, '+');
+ else if (key == '-' && win_meta.active != 0) // shrink window vertically
+ resize_window(&win_meta, '-'); }
+
+ endwin();
+ return 0; }
--- /dev/null
+#include <stdlib.h>
+#include <ncurses.h>
+#include <string.h>
+#include "windows.h"
+
+struct WinMeta init_win_meta (WINDOW * screen) {
+// Create and populate WinMeta struct with sane default values.
+ struct WinMeta win_meta;
+ win_meta.height = screen->_maxy + 1;
+ win_meta.chain_start = 0;
+ win_meta.chain_end = 0;
+ return win_meta; }
+
+struct Win init_window (struct WinMeta * win_meta, char * title) {
+// Create and populate Win struct with sane default values.
+ struct Win win;
+ win.prev = 0;
+ win.next = 0;
+ win.curses_win = 0;
+ win.title = title;
+ win.width = 20;
+ win.height = win_meta->height;
+ return win; }
+
+void append_window (struct WinMeta * win_meta, struct Win * win) {
+// Append win to window chain. Set active, if first window. Update geometry of windows from new window on.
+ if (0 != win_meta->chain_start) {
+ win->prev = win_meta->chain_end;
+ win_meta->chain_end->next = win; }
+ else {
+ win_meta->active = win;
+ win_meta->chain_start = win; }
+ win_meta->chain_end = win;
+ update_windows(win_meta, win);
+ draw_all_windows(win_meta); }
+
+void suspend_window (struct WinMeta * win_meta, struct Win * win) {
+// Destroy win, suspend from window chain. Update geometry of following rows, as well as activity selection.
+ destroy_window(win);
+ if (win_meta->chain_start != win) // Give win's position in the chain to element next to it in the chain.
+ win->prev->next = win->next;
+ else
+ win_meta->chain_start = win->next;
+ if (win_meta->chain_end != win) { // Let chain element next to win know its new predecessor.
+ win->next->prev = win->prev;
+ if (win_meta->active == win) // If win was active, shift active window pointer to ...
+ win_meta->active = win->next; // ... the next chain element, if that is a window ...
+ update_windows(win_meta, win->next); }
+ else {
+ win_meta->chain_end = win->prev;
+ if (win_meta->active == win) // ... or else to the previous element.
+ win_meta->active = win->prev; }
+ win->prev = 0;
+ win->next = 0;
+ if (0 != win_meta->chain_start)
+ draw_all_windows(win_meta); }
+
+void place_window (struct WinMeta * win_meta, struct Win * win) {
+// Based on position and sizes of previous window, find fitting place for current window.
+ win->start_x = 0; // if window is first in chain, place it on top-left corner
+ win->start_y = 0;
+ if (0 != win->prev) {
+ win->start_x = win->prev->start_x + win->prev->width; // next best default: open new window column with it
+ if (win->prev->height < win_meta->height) { // ... unless the previous window does not fill a whole column
+ struct Win * last_ceiling;
+ last_ceiling = win->prev;
+ while (last_ceiling->start_y != 0 // determine last window serving as a
+ && (last_ceiling->prev->start_y == last_ceiling->start_y // ceiling to other windows or filling
+ || last_ceiling->prev->width > last_ceiling->width)) // the whole last column's width
+ last_ceiling = last_ceiling->prev;
+ if (win->prev == last_ceiling) {
+ if (win->width <= win->prev->width
+ && win->prev->start_y + win->prev->height + win->height <= win_meta->height) {
+ win->start_x = win->prev->start_x; // if prev window is last ceiling, try to
+ win->start_y = win->prev->start_y + win->prev->height; } } // fit window below it; else: use default
+ else {
+ int remaining_width = last_ceiling->width; // calculate free width remaining in last row of last
+ struct Win * win_p = last_ceiling->next; // window column
+ while (win != win_p) {
+ remaining_width = remaining_width - win_p->width;
+ win_p = win_p->next; }
+ if (win->width <= remaining_width && win->height <= win->prev->height) { // if enough space left in
+ win->start_y = win->prev->start_y; // last column, place window
+ win->start_x = win->prev->start_x + win->prev->width; } // here
+ else if (win->width <= last_ceiling->width
+ && win->height + win->prev->height + win->prev->start_y <= win_meta->height ) {
+ win->start_y = last_ceiling->next->start_y + last_ceiling->next->height; // else, try to put it
+ win->start_x = last_ceiling->start_x; } // below
+ else // else, put it next to max
+ win->start_x = last_ceiling->width + last_ceiling->start_x; } } } } // width of the last last column
+
+void update_windows (struct WinMeta * win_meta, struct Win * win) {
+// Update geometry of win and its next of kin. Before, destroy window, if visible. After, (re-)build it.
+ if (0 != win->curses_win)
+ destroy_window (win);
+ place_window(win_meta, win);
+ if (win->start_y + win->height < win_meta->height) // dependent on window position,
+ win->border_down = 1; // append space for borders to be drawn
+ else
+ win->border_down = 0;
+ if (win->start_x > 0)
+ win->border_left = 1;
+ else
+ win->border_left = 0;
+ win->curses_win = newwin(win->height + win->border_down, win->width + win->border_left, win->start_y, win->start_x - win->border_left);
+ if (0 != win->next)
+ update_windows (win_meta, win->next); }
+
+void destroy_window (struct Win * win) {
+// Undraw and delete window.
+ undraw_window (win->curses_win);
+ delwin(win->curses_win);
+ win->curses_win = 0; }
+
+void draw_windows (struct WinMeta * win_meta, struct Win * win) {
+// Draw all windows from the current one on.
+ draw_window(win_meta, win);
+ if (0 != win->next)
+ draw_windows (win_meta, win->next); }
+
+void draw_all_windows (struct WinMeta * win_meta) {
+// Draw all windows from the chain start on.
+ draw_windows (win_meta, win_meta->chain_start); }
+
+void draw_window(struct WinMeta * win_meta, struct Win * win) {
+// Draw win's content, including border and title (the latter dependent on space available for it).
+ char ls = '|';
+ char rs = '|';
+ char ts = '-';
+ char bs = '-';
+ char tl = '-';
+ char tr = '+';
+ char bl = '|';
+ char br = '|';
+ if (1 == win->border_down) {
+ bl = '+';
+ br = '+'; }
+ if (1 == win->border_left)
+ tl = '+';
+ wborder(win->curses_win, ls, rs, ts, bs, tl, tr, bl, br);
+ char min_title_length_visible = 3; // 1 char minimal, plus 2 chars for decoration left/right of title
+ if (win->width > min_title_length_visible) {
+ int title_length = strlen(win->title);
+ int title_offset = (((win->width) - (title_length + 2)) / 2) + win->border_left; // + 2 is for decoration
+ if (title_offset < win->border_left)
+ title_offset = win->border_left;
+ int length_visible = strnlen(win->title, win->width - min_title_length_visible);
+ char title[length_visible + 3];
+ char decoration = ' ';
+ if (win_meta->active == win)
+ decoration = '$';
+ memcpy(title + 1, win->title, length_visible);
+ title[0] = title[length_visible + 1] = decoration;
+ title[length_visible + 2] = '\0';
+ mvwaddstr(win->curses_win, 0, title_offset, title); }
+ if (win->height > 1 && win->width > 1) ;
+ win->draw(win);
+ wrefresh(win->curses_win); }
+
+void undraw_window (WINDOW * win) {
+// Fill entire window with whitespace.
+ int y, x;
+ for (y = 0; y <= win->_maxy; y++)
+ for (x = 0; x <= win->_maxx; x++)
+ mvwaddch(win, y, x, ' ');
+ wrefresh(win); }
+
+void resize_window (struct WinMeta * win_meta, char change) {
+// Grow or shrink currently active window. Correct its geometry and that of its followers.
+ if (change == '-' && win_meta->active->height > 2)
+ win_meta->active->height--;
+ else if (change == '+' && win_meta->active->height < win_meta->height)
+ win_meta->active->height++;
+ else if (change == '_' && win_meta->active->width > 2)
+ win_meta->active->width--;
+ else if (change == '*')
+ win_meta->active->width++;
+ update_windows(win_meta, win_meta->chain_start);
+ draw_all_windows(win_meta); }
+
+void cycle_active_window (struct WinMeta * win_meta, char dir) {
+// Cycle active window selection forwards (dir = 'n') or backwards.
+ if ('n' == dir) {
+ if (win_meta->active->next != 0)
+ win_meta->active = win_meta->active->next;
+ else
+ win_meta->active = win_meta->chain_start; }
+ else {
+ if (win_meta->active->prev != 0)
+ win_meta->active = win_meta->active->prev;
+ else
+ win_meta->active = win_meta->chain_end; }
+ draw_all_windows(win_meta); }
+
+void shift_window (struct WinMeta * win_meta, char dir) {
+// Move active window forward/backward in window chain. If jumping beyond start/end, move to other chain end.
+ if (win_meta->active != win_meta->chain_start || win_meta->active != win_meta->chain_end) {
+ if ('f' == dir) {
+ if (win_meta->active == win_meta->chain_end) { // move forward beyond chain end
+ win_meta->active->prev->next = 0;
+ win_meta->chain_end = win_meta->active->prev;
+ win_meta->active->prev = 0;
+ win_meta->active->next = win_meta->chain_start;
+ win_meta->chain_start->prev = win_meta->active;
+ win_meta->chain_start = win_meta->active; }
+ else { // move forward before chain end
+ if (win_meta->chain_start != win_meta->active)
+ win_meta->active->prev->next = win_meta->active->next;
+ else
+ win_meta->chain_start = win_meta->active->next;
+ win_meta->active->next->prev = win_meta->active->prev;
+ win_meta->active->prev = win_meta->active->next;
+ win_meta->active->next = win_meta->active->next->next;
+ win_meta->active->prev->next = win_meta->active;
+ if (0 != win_meta->active->next)
+ win_meta->active->next->prev = win_meta->active;
+ else
+ win_meta->chain_end = win_meta->active; } }
+ else { // mirror of above, backwards
+ if (win_meta->active == win_meta->chain_start) {
+ win_meta->active->next->prev = 0;
+ win_meta->chain_start = win_meta->active->next;
+ win_meta->active->next = 0;
+ win_meta->active->prev = win_meta->chain_end;
+ win_meta->chain_end->next = win_meta->active;
+ win_meta->chain_end = win_meta->active; }
+ else {
+ if (win_meta->chain_end != win_meta->active)
+ win_meta->active->next->prev = win_meta->active->prev;
+ else
+ win_meta->chain_end = win_meta->active->prev;
+ win_meta->active->prev->next = win_meta->active->next;
+ win_meta->active->next = win_meta->active->prev;
+ win_meta->active->prev = win_meta->active->prev->prev;
+ win_meta->active->next->prev = win_meta->active;
+ if (0 != win_meta->active->prev)
+ win_meta->active->prev->next = win_meta->active;
+ else
+ win_meta->chain_start = win_meta->active; } }
+ update_windows(win_meta, win_meta->chain_start);
+ draw_all_windows(win_meta); } }