Use RGBColour exclusively internally. Fix a few bugs related to the internals of...
[SmugglerRL.git] / src / graphix.d
blobd8122899c2bf75a2f6ed479549185733baf06957
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 ubyte fg = get256colour(game.map[i][j].fgcolour), bg = get256colour(game.map[i][j].bgcolour);
121 curses.attron(curses.COLOR_PAIR(getclr(fg, bg))&curses.A_COLOR);
122 /* x and y both start at 1, so we want to get them to 0
123 * in order to align them with the edges of the terminal.
124 * But it's okay to "add" 1 to y, because we want to leave
125 * an extra line up to for messages.
127 curses.mvprint(tmpy, tmpx-1, game.map[i][j].glyph);
128 curses.attroff(curses.COLOR_PAIR(getclr(fg, bg))&curses.A_COLOR);
130 tmpx = 0;
133 // ditto
134 curses.move(t["cursy"]+1, t["cursx"]);
135 curses.refresh();
139 void pline(string msg) {
140 if (msg.length == 0)
141 return;
143 // TODO chop stuff up based on words, not character counts
144 pure string[] chopupmsg(string msgtext, int x) {
145 int tmp = 0;
146 string[] buf;
147 immutable int maxlen = x - cast(int)" --More--".length;
148 while (msgtext.length > x) {
149 buf ~= (msgtext[tmp..tmp+maxlen] ~ " --More--");
150 tmp += maxlen;
151 msgtext = msgtext[tmp..$];
153 buf ~= msgtext;
154 return buf;
156 if (msg.length <= maxx()) {
157 curses.mvprint(0, 0, msg);
158 curses.refresh();
159 } else {
160 string[] buffer = chopupmsg(msg, /*cast(int)*/maxx());
162 loop: foreach (lineindex; 0..buffer.length) {
163 curses.mvprint(0, 0, buffer[lineindex]);
164 curses.refresh();
165 int c;
166 c = curses.getch();
167 if (c == '\033') {
168 // Clear the message bar
169 curses.mvprint(0, 0, fillstr(maxx()));
170 curses.mvprint(0, 0, buffer[$-1]);
171 //curses.move(y, x);
172 break;
174 if (lineindex < buffer.length-1) {
175 while ((c != '\n') && (c != ' ')) {
176 if (c == '\033') {
177 curses.mvprint(0, 0, fillstr(maxx()));
178 curses.mvprint(0, 0, buffer[$-1]);
179 break loop;
181 c = curses.getch();
184 curses.mvprint(0, 0, fillstr(maxx()));
190 pragma(inline, true) {
191 int maxx() {
192 return curses.COLS;
194 int maxy() {
195 return curses.LINES;
198 private ushort getclr(ubyte fg, ubyte bg) {
199 ubyte[2] tmp = [fg, bg];
200 if (tmp in colourlists) {
201 return colourlists[tmp];
202 } else {
203 biggestclr++;
204 curses.init_pair(biggestclr, fg, bg);
205 colourlists[tmp] = biggestclr;
206 return biggestclr;
208 assert(0);