Add in a util to convert from hex/rgb colour to indexed (xterm) colour. Shoutout...
[SmugglerRL.git] / src / graphix.d
blob98cb123ed4f61de71b93ffa36ad8721713680956
1 static import curses;
2 import constants;
3 import util;
4 import game;
6 interface Graphicshandler {
7 void refresh();
8 pragma(inline, true) pure final void pline(T...)(string s, T args) {
9 if (s.length == 0) {
10 return;
11 } else if (args.length == 0) {
12 pline(args);
13 } else {
14 pline(format(s, args));
17 void pline(string s);
18 int maxx();
19 int maxy();
22 class Ncurseshandler: Graphicshandler {
23 const Game game;
24 ushort[ubyte[2]] colourlists;
25 ushort biggestclr;
27 this(Game g) {
28 import std.stdio: writefln;
29 // objects are references. I don't pretend to like it,
30 // but in this case it means we don't have to use a pointer.
31 this.game = g;
33 // need this for unicode
34 import core.stdc.locale: setlocale, LC_CTYPE;
35 setlocale(LC_CTYPE, "");
38 curses.initscr();
39 curses.start_color();
40 curses.cbreak(); // Receive data 1 character at a time
41 // By default, the terminal will buffer inputted
42 // characters until the user presses enter,
43 // and then present them to the program. This makes
44 // the terminal present us with one character at a time
45 curses.noecho(); // Don't echo output
46 if ((maxx() < min_x) || (maxy() < min_y)) {
47 curses.endwin();
48 writefln("Error: the terminal needs to be at least %dx%d, but it's really %dx%d", min_x, min_y, curses.COLS, curses.LINES);
49 exit(1);
54 void refresh() {
55 int tmpx = 0, tmpy = 0;
57 // FUN FACT while coding this, I had no idea what it did or how
58 // it worked. I still have no idea how it works. But it does!
59 int[string] getstartendxy() {
60 import std.math: round;
61 int[string] tmp;
62 int startx, starty, endx, endy;
63 int offsetx, offsety, x = game.x, y = game.y; // offsets are distance from top edge, left edge to centre
64 int cursx, cursy;
65 int width = maxx();
66 int height = maxy() - 1;
68 offsetx = cast(int)round(width / 2.0);
69 offsety = cast(int)round(height / 2.0);
71 // Don't keep the map centred when we're near an edge,
72 // actually move the @ closer to the edge
73 if (x <= offsetx) {
74 startx = 1;
75 cursx = x - 1;
76 } else {
77 startx = x - offsetx;
78 cursx = offsetx;
80 if (y <= offsety) {
81 starty = 1;
82 cursy = y - 1;
83 } else {
84 starty = y - offsety;
85 cursy = offsety;
88 endx = startx + width;
89 endy = starty + height;
91 // Ditto
92 if (endx >= map_x) {
93 endx = map_x+1;
94 startx = endx-width;
95 cursx = width - (map_x - x) - 1;
97 if (endy >= map_y) {
98 endy = map_y+1;
99 starty = endy-height;
100 cursy = height - (map_y - y) - 1;
103 tmp["startx"] = startx;
104 tmp["starty"] = starty;
105 tmp["endx"] = endx;
106 tmp["endy"] = endy;
107 tmp["cursx"] = cursx;
108 tmp["cursy"] = cursy;
110 return tmp;
113 int[string] t = getstartendxy();
115 foreach (uint i; t["starty"]..t["endy"]) {
116 tmpy++;
117 foreach (uint j; t["startx"]..t["endx"]) {
118 tmpx++;
119 curses.attron(curses.COLOR_PAIR(getclr(game.map[i][j].fgcolour, game.map[i][j].bgcolour))&curses.A_COLOR);
120 /* x and y both start at 1, so we want to get them to 0
121 * in order to align them with the edges of the terminal.
122 * But it's okay to "add" 1 to y, because we want to leave
123 * an extra line up to for messages.
125 curses.mvprint(tmpy, tmpx-1, game.map[i][j].glyph);
126 curses.attroff(curses.COLOR_PAIR(getclr(game.map[i][j].fgcolour, game.map[i][j].bgcolour))&curses.A_COLOR);
128 tmpx = 0;
131 // ditto
132 curses.move(t["cursy"]+1, t["cursx"]);
133 curses.refresh();
137 void pline(string msg) {
138 if (msg.length == 0)
139 return;
141 // TODO chop stuff up based on words, not character counts
142 pure string[] chopupmsg(string msgtext, int x) {
143 int tmp = 0;
144 string[] buf;
145 immutable int maxlen = x - cast(int)" --More--".length;
146 while (msgtext.length > x) {
147 buf ~= (msgtext[tmp..tmp+maxlen] ~ " --More--");
148 tmp += maxlen;
149 msgtext = msgtext[tmp..$];
151 buf ~= msgtext;
152 return buf;
154 if (msg.length <= maxx()) {
155 curses.mvprint(0, 0, msg);
156 curses.refresh();
157 } else {
158 string[] buffer = chopupmsg(msg, /*cast(int)*/maxx());
160 loop: foreach (lineindex; 0..buffer.length) {
161 curses.mvprint(0, 0, buffer[lineindex]);
162 curses.refresh();
163 int c;
164 c = curses.getch();
165 if (c == '\033') {
166 // Clear the message bar
167 curses.mvprint(0, 0, fillstr(maxx()));
168 curses.mvprint(0, 0, buffer[$-1]);
169 //curses.move(y, x);
170 break;
172 if (lineindex < buffer.length-1) {
173 while ((c != '\n') && (c != ' ')) {
174 if (c == '\033') {
175 curses.mvprint(0, 0, fillstr(maxx()));
176 curses.mvprint(0, 0, buffer[$-1]);
177 break loop;
179 c = curses.getch();
182 curses.mvprint(0, 0, fillstr(maxx()));
188 pragma(inline, true) {
189 int maxx() {
190 return curses.COLS;
192 int maxy() {
193 return curses.LINES;
196 private ushort getclr(ubyte fg, ubyte bg) {
197 ubyte[2] tmp = [fg, bg];
198 if (tmp in colourlists) {
199 return colourlists[tmp];
200 } else {
201 biggestclr++;
202 curses.init_pair(biggestclr, fg, bg);
203 colourlists[tmp] = biggestclr;
204 return biggestclr;
206 assert(0);