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