1 #include "AppHdr.h"
2
3 #include "viewgeom.h"
4
5 #include "dungeon.h"
6 #include "end.h"
7 #include "options.h"
8 #include "state.h"
9 #include "tiles-build-specific.h"
10
11
12 // ----------------------------------------------------------------------
13 // Layout helper classes
14 // ----------------------------------------------------------------------
15
16 // Moved from directn.h, where they didn't need to be.
17 // define VIEW_MIN_HEIGHT defined elsewhere
18 // define VIEW_MAX_HEIGHT use Options.view_max_height
19 // define VIEW_MIN_WIDTH defined elsewhere
20 // define VIEW_MAX_WIDTH use Options.view_max_width
21 #define HUD_WIDTH 42
22 #define HUD_HEIGHT 13
23 #define MSG_MAX_HEIGHT Options.msg_max_height
24 #define MLIST_MIN_HEIGHT Options.mlist_min_height
25 #define MLIST_MIN_WIDTH 25 // non-inline layout only
26 #define MLIST_MAX_WIDTH 42
27 #define MLIST_GUTTER 1
28 #define HUD_MIN_GUTTER 2
29 #define HUD_MAX_GUTTER 4
30
31 // Helper for layouts. Tries to increment lvalue without overflowing it.
_increment(int & lvalue,int delta,int max_value)32 static void _increment(int& lvalue, int delta, int max_value)
33 {
34 lvalue = min(lvalue+delta, max_value);
35 }
36
37 class _layout
38 {
39 public:
_layout(coord_def termsz_,coord_def hudsz_)40 _layout(coord_def termsz_, coord_def hudsz_) :
41 termp(1,1), termsz(termsz_),
42 viewp(-1,-1), viewsz(VIEW_MIN_WIDTH, VIEW_MIN_HEIGHT),
43 hudp(-1,-1), hudsz(hudsz_),
44 msgp(-1,-1), msgsz(0, Options.msg_min_height),
45 mlistp(-1,-1), mlistsz(MLIST_MIN_WIDTH, 0),
46 hud_gutter(HUD_MIN_GUTTER),
47 valid(false) {}
48
49 protected:
50 // Smart compilers can recognize some of these assertions as tautological,
51 // but we do want to keep them in case something changes.
52 // A discussion: http://kerneltrap.org/node/7434
53 #pragma GCC diagnostic ignored "-Wstrict-overflow"
_assert_validity() const54 void _assert_validity() const
55 {
56 #ifndef USE_TILE_LOCAL
57 ASSERT(viewp.x >= 1);
58 ASSERT(viewp.y >= 1);
59 // Check that all the panes fit in the view.
60 ASSERT((viewp+viewsz - termp).x <= termsz.x);
61 ASSERT((viewp+viewsz - termp).y <= termsz.y);
62
63 ASSERT((hudp+hudsz - termp).x <= termsz.x);
64 ASSERT((hudp+hudsz - termp).y <= termsz.y);
65
66 ASSERT((msgp+msgsz - termp).x <= termsz.x);
67 ASSERT((msgp+msgsz - termp).y <= termsz.y);
68
69 ASSERT((mlistp+mlistsz-termp).x <= termsz.x);
70 ASSERT((mlistp+mlistsz-termp).y <= termsz.y);
71 #endif
72 }
73 public:
74 const coord_def termp, termsz;
75 coord_def viewp, viewsz;
76 coord_def hudp;
77 const coord_def hudsz;
78 coord_def msgp, msgsz;
79 coord_def mlistp, mlistsz;
80 int hud_gutter;
81 bool valid;
82 };
83
84 // vvvvvvghhh v=view, g=hud gutter, h=hud, l=list, m=msg
85 // vvvvvvghhh
86 // vvvvvv lll
87 // lll
88 // mmmmmmmmmm
89 class _inline_layout : public _layout
90 {
91 public:
_inline_layout(coord_def termsz_,coord_def hudsz_)92 _inline_layout(coord_def termsz_, coord_def hudsz_) :
93 _layout(termsz_, hudsz_)
94 {
95 valid = _init();
96 }
97
_init()98 bool _init()
99 {
100 // x: View gets leftover; then mlist; then hud gutter
101 if (leftover_x() < 0)
102 return false;
103
104 _increment(viewsz.x, leftover_x(), Options.view_max_width);
105
106 if ((viewsz.x % 2) != 1)
107 --viewsz.x;
108
109 mlistsz.x = hudsz.x;
110 _increment(mlistsz.x, leftover_x(), MLIST_MAX_WIDTH);
111 _increment(hud_gutter, leftover_x(), HUD_MAX_GUTTER);
112 _increment(mlistsz.x, leftover_x(), INT_MAX);
113 msgsz.x = termsz.x;
114
115 // y: View gets as much as it wants.
116 // mlist tries to get at least its minimum.
117 // msg expands as much as it wants.
118 // mlist gets any leftovers.
119 if (leftover_y() < 0)
120 return false;
121
122 _increment(viewsz.y, leftover_leftcol_y(), Options.view_max_height);
123 if ((viewsz.y % 2) != 1)
124 --viewsz.y;
125
126 if (mlistsz.y < MLIST_MIN_HEIGHT)
127 _increment(mlistsz.y, leftover_rightcol_y(), MLIST_MIN_HEIGHT);
128 _increment(msgsz.y, leftover_y(), MSG_MAX_HEIGHT);
129 _increment(mlistsz.y, leftover_rightcol_y(), INT_MAX);
130
131 // Finish off by doing the positions.
132 if (Options.messages_at_top)
133 {
134 msgp = termp;
135 viewp = termp + coord_def(0, msgsz.y);
136 }
137 else
138 {
139 viewp = termp;
140 msgp = termp + coord_def(0, max(viewsz.y, hudsz.y+mlistsz.y));
141 }
142 hudp = viewp + coord_def(viewsz.x+hud_gutter, 0);
143 mlistp = hudp + coord_def(0, hudsz.y);
144
145 _assert_validity();
146 return true;
147 }
148
leftover_x() const149 int leftover_x() const
150 {
151 int width = (viewsz.x + hud_gutter + max(hudsz.x, mlistsz.x));
152 return termsz.x - width;
153 }
leftover_rightcol_y() const154 int leftover_rightcol_y() const { return termsz.y-hudsz.y-mlistsz.y-msgsz.y; }
leftover_leftcol_y() const155 int leftover_leftcol_y() const { return termsz.y-viewsz.y-msgsz.y; }
leftover_y() const156 int leftover_y() const
157 {
158 return min(leftover_rightcol_y(), leftover_leftcol_y());
159 }
160 };
161
162 // ll vvvvvvghhh v=view, g=hud gutter, h=hud, l=list, m=msg
163 // ll vvvvvvghhh
164 // ll vvvvvv
165 // mmmmmmmmmmmmm
166 class _mlist_col_layout : public _layout
167 {
168 public:
_mlist_col_layout(coord_def termsz_,coord_def hudsz_)169 _mlist_col_layout(coord_def termsz_, coord_def hudsz_)
170 : _layout(termsz_, hudsz_)
171 { valid = _init(); }
_init()172 bool _init()
173 {
174 // Don't let the mlist column steal all the width. Up front,
175 // take some for the view. If it makes the layout fail, that's fine.
176 _increment(viewsz.x, MLIST_MIN_WIDTH/2, Options.view_max_width);
177
178 // x: View and mlist share leftover; then hud gutter.
179 if (leftover_x() < 0)
180 return false;
181
182 _increment(mlistsz.x, leftover_x()/2, MLIST_MAX_WIDTH);
183 _increment(viewsz.x, leftover_x(), Options.view_max_width);
184
185 if ((viewsz.x % 2) != 1)
186 --viewsz.x;
187
188 _increment(mlistsz.x, leftover_x(), MLIST_MAX_WIDTH);
189 _increment(hud_gutter, leftover_x(), HUD_MAX_GUTTER);
190 msgsz.x = termsz.x-1; // Can't use last character.
191
192 // y: View gets leftover; then message.
193 if (leftover_y() < 0)
194 return false;
195
196 _increment(viewsz.y, leftover_y(), Options.view_max_height);
197
198 if ((viewsz.y % 2) != 1)
199 --viewsz.y;
200
201 _increment(msgsz.y, leftover_y(), INT_MAX);
202 mlistsz.y = viewsz.y;
203
204 // Finish off by doing the positions.
205 mlistp = termp;
206 viewp = mlistp+ coord_def(mlistsz.x+MLIST_GUTTER, 0);
207 msgp = termp + coord_def(0, viewsz.y);
208 hudp = viewp + coord_def(viewsz.x+hud_gutter, 0);
209
210 _assert_validity();
211 return true;
212 }
213 private:
leftover_x() const214 int leftover_x() const
215 {
216 int width = (mlistsz.x + MLIST_GUTTER + viewsz.x + hud_gutter + hudsz.x);
217 return termsz.x - width;
218 }
leftover_y() const219 int leftover_y() const
220 {
221 const int top_y = max(max(viewsz.y, hudsz.y), mlistsz.y);
222 const int height = top_y + msgsz.y;
223 return termsz.y - height;
224 }
225 };
226
227 //////////////////////////////////////////////////////////////////////////////
228 // crawl_view_buffer
229
crawl_view_buffer()230 crawl_view_buffer::crawl_view_buffer()
231 : m_size(0, 0)
232 , m_buffer(nullptr)
233 {
234 }
crawl_view_buffer(const coord_def & sz)235 crawl_view_buffer::crawl_view_buffer(const coord_def &sz)
236 : m_size(0, 0)
237 , m_buffer(nullptr)
238 {
239 resize(sz);
240 }
241
~crawl_view_buffer()242 crawl_view_buffer::~crawl_view_buffer()
243 {
244 delete [] m_buffer;
245 }
246
resize(const coord_def & sz)247 void crawl_view_buffer::resize(const coord_def &sz)
248 {
249 delete [] m_buffer;
250 m_size = sz;
251 m_buffer = new screen_cell_t [ sz.x * sz.y ];
252 }
253
empty() const254 bool crawl_view_buffer::empty() const
255 {
256 return m_size.x * m_size.y <= 0;
257 }
258
crawl_view_buffer(const crawl_view_buffer & rhs)259 crawl_view_buffer::crawl_view_buffer(const crawl_view_buffer &rhs)
260 : crawl_view_buffer(rhs.m_size)
261 {
262 if (rhs.m_buffer)
263 {
264 size_t count = m_size.x * m_size.y;
265 copy(rhs.m_buffer, rhs.m_buffer+count, m_buffer);
266 }
267 }
268
operator =(crawl_view_buffer rhs)269 const crawl_view_buffer &crawl_view_buffer::operator = (crawl_view_buffer rhs)
270 {
271 swap(m_size, rhs.m_size);
272 swap(m_buffer, rhs.m_buffer);
273 return *this;
274 }
275
fill(const screen_cell_t & value)276 void crawl_view_buffer::fill(const screen_cell_t& value)
277 {
278 for (int i = 0; i < m_size.x * m_size.y; ++i)
279 m_buffer[i] = value;
280 }
281
clear()282 void crawl_view_buffer::clear()
283 {
284 delete [] m_buffer;
285 m_buffer = nullptr;
286 m_size = coord_def(0,0);
287 }
288
289 // ----------------------------------------------------------------------
290 // crawl_view_geometry
291 // ----------------------------------------------------------------------
292
crawl_view_geometry()293 crawl_view_geometry::crawl_view_geometry()
294 : termp(1, 1), termsz(80, 24),
295 viewp(1, 1), viewsz(VIEW_BASE_WIDTH, 17),
296 hudp(40, 1), hudsz(-1, -1),
297 msgp(1, viewp.y + viewsz.y), msgsz(80, 7),
298 mlistp(hudp.x, hudp.y + hudsz.y),
299 mlistsz(hudsz.x, msgp.y - mlistp.y),
300 vgrdc(), viewhalfsz(), glos1(), glos2(),
301 vlos1(), vlos2(), mousep(), last_player_pos()
302 {
303 }
304
init_view()305 void crawl_view_geometry::init_view()
306 {
307 viewhalfsz = viewsz / 2;
308 if (!crawl_state.game_is_arena())
309 set_player_at(you.pos(), true);
310 else
311 {
312 coord_def yplace(dgn_find_feature_marker(DNGN_ESCAPE_HATCH_UP));
313 crawl_view.set_player_at(yplace);
314 }
315 }
316
shift_player_to(const coord_def & c)317 void crawl_view_geometry::shift_player_to(const coord_def &c)
318 {
319 // Preserve vgrdc offset after moving.
320 const coord_def offset = crawl_view.vgrdc - last_player_pos;
321 crawl_view.vgrdc = offset + c;
322 last_player_pos = c;
323
324 set_player_at(c);
325
326 ASSERT(crawl_view.vgrdc == offset + c);
327 ASSERT(last_player_pos == c);
328 }
329
set_player_at(const coord_def & c,bool centre)330 void crawl_view_geometry::set_player_at(const coord_def &c, bool centre)
331 {
332 if (centre)
333 vgrdc = c;
334 else
335 {
336 const coord_def oldc = vgrdc;
337 const int xmarg = Options.scroll_margin_x + LOS_RADIUS <= viewhalfsz.x
338 ? Options.scroll_margin_x
339 : viewhalfsz.x - LOS_RADIUS;
340 const int ymarg = Options.scroll_margin_y + LOS_RADIUS <= viewhalfsz.y
341 ? Options.scroll_margin_y
342 : viewhalfsz.y - LOS_RADIUS;
343
344 if (Options.view_lock_x)
345 vgrdc.x = c.x;
346 else if (c.x - LOS_RADIUS < vgrdc.x - viewhalfsz.x + xmarg)
347 vgrdc.x = c.x - LOS_RADIUS + viewhalfsz.x - xmarg;
348 else if (c.x + LOS_RADIUS > vgrdc.x + viewhalfsz.x - xmarg)
349 vgrdc.x = c.x + LOS_RADIUS - viewhalfsz.x + xmarg;
350
351 if (Options.view_lock_y)
352 vgrdc.y = c.y;
353 else if (c.y - LOS_RADIUS < vgrdc.y - viewhalfsz.y + ymarg)
354 vgrdc.y = c.y - LOS_RADIUS + viewhalfsz.y - ymarg;
355 else if (c.y + LOS_RADIUS > vgrdc.y + viewhalfsz.y - ymarg)
356 vgrdc.y = c.y + LOS_RADIUS - viewhalfsz.y + ymarg;
357
358 if (vgrdc != oldc && Options.centre_on_scroll)
359 vgrdc = c;
360
361 if (!Options.centre_on_scroll && Options.symmetric_scroll
362 && !Options.view_lock_x
363 && !Options.view_lock_y
364 && (c - last_player_pos).abs() == 2
365 && (vgrdc - oldc).abs() == 1)
366 {
367 const coord_def dp = c - last_player_pos;
368 const coord_def dc = vgrdc - oldc;
369 if ((dc.x == dp.x) != (dc.y == dp.y))
370 vgrdc = oldc + dp;
371 }
372 }
373
374 glos1 = c - coord_def(LOS_RADIUS, LOS_RADIUS);
375 glos2 = c + coord_def(LOS_RADIUS, LOS_RADIUS);
376
377 calc_vlos();
378
379 last_player_pos = c;
380 }
381
init_geometry()382 void crawl_view_geometry::init_geometry()
383 {
384 termsz = coord_def(get_number_of_cols(), get_number_of_lines());
385
386 // currently, webtiles has weird interactions with this logic (I think
387 // because of extra resize calls). But this is basically safe because
388 // dgamelaunch wraps terminal size and prevents smallterm.
389 #ifndef USE_TILE_LOCAL
390 const bool smallterm = termsz.x < MIN_COLS || termsz.y < MIN_LINES;
391 crawl_state.smallterm = smallterm;
392 if (crawl_state.need_save)
393 {
394 // if the game has already started, just fake the terminal size.
395 // this can cause weird glitches, would be more elegant to actually
396 // crop this. (But is it worth it?) crawl_state.smallterm should mostly
397 // prevent drawing if this comes into play.
398 termsz.x = max(termsz.x, MIN_COLS);
399 termsz.y = max(termsz.y, MIN_LINES);
400 }
401 #endif
402 hudsz = coord_def(HUD_WIDTH, HUD_HEIGHT);
403
404 const _inline_layout lay_inline(termsz, hudsz);
405 const _mlist_col_layout lay_mlist(termsz, hudsz);
406
407 #ifndef USE_TILE_LOCAL
408 if (!crawl_state.need_save)
409 {
410 if (smallterm)
411 {
412 end(1, false, "Terminal too small (%d,%d); need at least (%d,%d)",
413 termsz.x, termsz.y, MIN_COLS, MIN_LINES);
414 }
415 else if (!lay_inline.valid)
416 {
417 const int x_left = lay_inline.leftover_x();
418 const int y_left = lay_inline.leftover_y();
419
420 end(1, false, "Terminal too small (%d,%d); layout needs (%d,%d)",
421 termsz.x, termsz.y,
422 x_left < 0 ? termsz.x - x_left : MIN_COLS,
423 y_left < 0 ? termsz.y - y_left : MIN_LINES);
424 }
425 }
426 #endif
427
428 const _layout* winner = &lay_inline;
429 if (Options.mlist_allow_alternate_layout
430 && lay_mlist.valid)
431 {
432 winner = &lay_mlist;
433 }
434 #ifndef USE_TILE_LOCAL
435 // I don't know why this crashes on local tiles
436 ASSERT(winner->valid);
437 #endif
438
439 msgp = winner->msgp;
440 msgsz = winner->msgsz;
441 viewp = winner->viewp;
442 viewsz = winner->viewsz;
443 hudp = winner->hudp;
444 hudsz = winner->hudsz;
445 mlistp = winner->mlistp;
446 mlistsz = winner->mlistsz;
447
448 #ifdef USE_TILE_LOCAL
449 // libgui may redefine these based on its own settings.
450 gui_init_view_params(*this);
451 #endif
452 #ifdef USE_TILE_WEB
453 tiles.layout_reset();
454 #endif
455
456 #ifdef USE_TILE
457 tiles.resize();
458 #endif
459
460 init_view();
461 return;
462 }
463
calc_vlos()464 void crawl_view_geometry::calc_vlos()
465 {
466 vlos1 = grid2view(glos1);
467 vlos2 = grid2view(glos2);
468 }
469