#include "AppHdr.h" #include "viewgeom.h" #include "options.h" #include "player.h" #include "state.h" #include "stuff.h" // ---------------------------------------------------------------------- // Layout helper classes // ---------------------------------------------------------------------- // Moved from directn.h, where they didn't need to be. // define VIEW_MIN_HEIGHT defined elsewhere // define VIEW_MAX_HEIGHT use Options.view_max_height // define VIEW_MIN_WIDTH defined elsewhere // define VIEW_MAX_WIDTH use Options.view_max_width #define HUD_WIDTH 42 #define HUD_HEIGHT 12 #define MSG_MAX_HEIGHT Options.msg_max_height #define MLIST_MIN_HEIGHT Options.mlist_min_height #define MLIST_MIN_WIDTH 25 // non-inline layout only #define MLIST_MAX_WIDTH 42 #define MLIST_GUTTER 1 #define HUD_MIN_GUTTER 2 #define HUD_MAX_GUTTER 4 // Helper for layouts. Tries to increment lvalue without overflowing it. static void _increment(int& lvalue, int delta, int max_value) { lvalue = std::min(lvalue+delta, max_value); } class _layout { public: _layout(coord_def termsz_, coord_def hudsz_) : termp(1,1), termsz(termsz_), viewp(-1,-1), viewsz(VIEW_MIN_WIDTH, VIEW_MIN_HEIGHT), hudp(-1,-1), hudsz(hudsz_), msgp(-1,-1), msgsz(0, Options.msg_min_height), mlistp(-1,-1), mlistsz(MLIST_MIN_WIDTH, 0), hud_gutter(HUD_MIN_GUTTER), valid(false) {} protected: void _assert_validity() const { #ifndef USE_TILE // Check that all the panes fit in the view. ASSERT( (viewp+viewsz - termp).x <= termsz.x ); ASSERT( (viewp+viewsz - termp).y <= termsz.y ); ASSERT( (hudp+hudsz - termp).x <= termsz.x ); ASSERT( (hudp+hudsz - termp).y <= termsz.y ); ASSERT( (msgp+msgsz - termp).x <= termsz.x ); ASSERT( (msgp+msgsz - termp).y <= termsz.y ); ASSERT( (mlistp+mlistsz-termp).x <= termsz.x ); ASSERT( (mlistp+mlistsz-termp).y <= termsz.y ); #endif } public: const coord_def termp, termsz; coord_def viewp, viewsz; coord_def hudp; const coord_def hudsz; coord_def msgp, msgsz; coord_def mlistp, mlistsz; int hud_gutter; bool valid; }; // vvvvvvghhh v=view, g=hud gutter, h=hud, l=list, m=msg // vvvvvvghhh // vvvvvv lll // lll // mmmmmmmmmm class _inline_layout : public _layout { public: _inline_layout(coord_def termsz_, coord_def hudsz_) : _layout(termsz_, hudsz_) { valid = _init(); } bool _init() { // x: View gets leftover; then mlist; then hud gutter if (leftover_x() < 0) return (false); _increment(viewsz.x, leftover_x(), Options.view_max_width); if ((viewsz.x % 2) != 1) --viewsz.x; mlistsz.x = hudsz.x; _increment(mlistsz.x, leftover_x(), MLIST_MAX_WIDTH); _increment(hud_gutter, leftover_x(), HUD_MAX_GUTTER); _increment(mlistsz.x, leftover_x(), INT_MAX); msgsz.x = termsz.x; // y: View gets as much as it wants. // mlist tries to get at least its minimum. // msg expands as much as it wants. // mlist gets any leftovers. if (leftover_y() < 0) return (false); _increment(viewsz.y, leftover_leftcol_y(), Options.view_max_height); if ((viewsz.y % 2) != 1) --viewsz.y; if (Options.classic_hud) { mlistsz.y = 0; _increment(msgsz.y, leftover_y(), MSG_MAX_HEIGHT); } else { if (mlistsz.y < MLIST_MIN_HEIGHT) _increment(mlistsz.y, leftover_rightcol_y(), MLIST_MIN_HEIGHT); _increment(msgsz.y, leftover_y(), MSG_MAX_HEIGHT); _increment(mlistsz.y, leftover_rightcol_y(), INT_MAX); } // Finish off by doing the positions. if (Options.messages_at_top) { msgp = termp; viewp = termp + coord_def(0, msgsz.y); } else { viewp = termp; msgp = termp + coord_def(0, std::max(viewsz.y, hudsz.y+mlistsz.y)); } hudp = viewp + coord_def(viewsz.x+hud_gutter, 0); mlistp = hudp + coord_def(0, hudsz.y); _assert_validity(); return (true); } int leftover_x() const { int width = (viewsz.x + hud_gutter + std::max(hudsz.x, mlistsz.x)); return (termsz.x - width); } int leftover_rightcol_y() const { return termsz.y-hudsz.y-mlistsz.y-msgsz.y; } int leftover_leftcol_y() const { return termsz.y-viewsz.y-msgsz.y; } int leftover_y() const { return std::min(leftover_rightcol_y(), leftover_leftcol_y()); } }; // ll vvvvvvghhh v=view, g=hud gutter, h=hud, l=list, m=msg // ll vvvvvvghhh // ll vvvvvv // mmmmmmmmmmmmm class _mlist_col_layout : public _layout { public: _mlist_col_layout(coord_def termsz_, coord_def hudsz_) : _layout(termsz_, hudsz_) { valid = _init(); } bool _init() { // Don't let the mlist column steal all the width. Up front, // take some for the view. If it makes the layout fail, that's fine. _increment(viewsz.x, MLIST_MIN_WIDTH/2, Options.view_max_width); // x: View and mlist share leftover; then hud gutter. if (leftover_x() < 0) return (false); _increment(mlistsz.x, leftover_x()/2, MLIST_MAX_WIDTH); _increment(viewsz.x, leftover_x(), Options.view_max_width); if ((viewsz.x % 2) != 1) --viewsz.x; _increment(mlistsz.x, leftover_x(), MLIST_MAX_WIDTH); _increment(hud_gutter, leftover_x(), HUD_MAX_GUTTER); msgsz.x = termsz.x-1; // Can't use last character. // y: View gets leftover; then message. if (leftover_y() < 0) return (false); _increment(viewsz.y, leftover_y(), Options.view_max_height); if ((viewsz.y % 2) != 1) --viewsz.y; _increment(msgsz.y, leftover_y(), INT_MAX); mlistsz.y = viewsz.y; // Finish off by doing the positions. mlistp = termp; viewp = mlistp+ coord_def(mlistsz.x+MLIST_GUTTER, 0); msgp = termp + coord_def(0, viewsz.y); hudp = viewp + coord_def(viewsz.x+hud_gutter, 0); _assert_validity(); return (true); } private: int leftover_x() const { int width = (mlistsz.x + MLIST_GUTTER + viewsz.x + hud_gutter + hudsz.x); return (termsz.x - width); } int leftover_y() const { const int top_y = std::max(std::max(viewsz.y, hudsz.y), mlistsz.y); const int height = top_y + msgsz.y; return (termsz.y - height); } }; ////////////////////////////////////////////////////////////////////////////// // crawl_view_buffer crawl_view_buffer::crawl_view_buffer() : buffer(NULL) { } crawl_view_buffer::~crawl_view_buffer() { delete [] buffer; } void crawl_view_buffer::size(const coord_def &sz) { delete [] buffer; buffer = new screen_buffer_t [ sz.x * sz.y * 2 ]; } // ---------------------------------------------------------------------- // crawl_view_geometry // ---------------------------------------------------------------------- crawl_view_geometry::crawl_view_geometry() : termp(1, 1), termsz(80, 24), viewp(1, 1), viewsz(33, 17), hudp(40, 1), hudsz(-1, -1), msgp(1, viewp.y + viewsz.y), msgsz(80, 7), mlistp(hudp.x, hudp.y + hudsz.y), mlistsz(hudsz.x, msgp.y - mlistp.y), vbuf(), vgrdc(), viewhalfsz(), glos1(), glos2(), vlos1(), vlos2(), mousep(), last_player_pos() { } void crawl_view_geometry::init_view() { viewhalfsz = viewsz / 2; vbuf.size(viewsz); set_player_at(you.pos(), true); } void crawl_view_geometry::shift_player_to(const coord_def &c) { // Preserve vgrdc offset after moving. const coord_def offset = crawl_view.vgrdc - last_player_pos; crawl_view.vgrdc = offset + c; last_player_pos = c; set_player_at(c); ASSERT(crawl_view.vgrdc == offset + c); ASSERT(last_player_pos == c); } void crawl_view_geometry::set_player_at(const coord_def &c, bool centre) { if (centre) { vgrdc = c; } else { const coord_def oldc = vgrdc; const int xmarg = Options.scroll_margin_x + LOS_RADIUS <= viewhalfsz.x ? Options.scroll_margin_x : viewhalfsz.x - LOS_RADIUS; const int ymarg = Options.scroll_margin_y + LOS_RADIUS <= viewhalfsz.y ? Options.scroll_margin_y : viewhalfsz.y - LOS_RADIUS; if (Options.view_lock_x) vgrdc.x = c.x; else if (c.x - LOS_RADIUS < vgrdc.x - viewhalfsz.x + xmarg) vgrdc.x = c.x - LOS_RADIUS + viewhalfsz.x - xmarg; else if (c.x + LOS_RADIUS > vgrdc.x + viewhalfsz.x - xmarg) vgrdc.x = c.x + LOS_RADIUS - viewhalfsz.x + xmarg; if (Options.view_lock_y) vgrdc.y = c.y; else if (c.y - LOS_RADIUS < vgrdc.y - viewhalfsz.y + ymarg) vgrdc.y = c.y - LOS_RADIUS + viewhalfsz.y - ymarg; else if (c.y + LOS_RADIUS > vgrdc.y + viewhalfsz.y - ymarg) vgrdc.y = c.y + LOS_RADIUS - viewhalfsz.y + ymarg; if (vgrdc != oldc && Options.center_on_scroll) vgrdc = c; if (!Options.center_on_scroll && Options.symmetric_scroll && !Options.view_lock_x && !Options.view_lock_y && (c - last_player_pos).abs() == 2 && (vgrdc - oldc).abs() == 1) { const coord_def dp = c - last_player_pos; const coord_def dc = vgrdc - oldc; if ((dc.x == dp.x) != (dc.y == dp.y)) vgrdc = oldc + dp; } } glos1 = c - coord_def(LOS_RADIUS, LOS_RADIUS); glos2 = c + coord_def(LOS_RADIUS, LOS_RADIUS); vlos1 = glos1 - vgrdc + view_centre(); vlos2 = glos2 - vgrdc + view_centre(); last_player_pos = c; } void crawl_view_geometry::init_geometry() { termsz = coord_def( get_number_of_cols(), get_number_of_lines() ); hudsz = coord_def(HUD_WIDTH, HUD_HEIGHT + (Options.show_gold_turns ? 1 : 0)); const _inline_layout lay_inline(termsz, hudsz); const _mlist_col_layout lay_mlist(termsz, hudsz); if (! lay_inline.valid) { #ifndef USE_TILE // Terminal too small; exit with an error. if (!crawl_state.need_save) { end(1, false, "Terminal too small (%d,%d); need at least (%d,%d)", termsz.x, termsz.y, termsz.x + std::max(0, -lay_inline.leftover_x()), termsz.y + std::max(0, -lay_inline.leftover_y())); } #endif } const _layout* winner = &lay_inline; if (Options.mlist_allow_alternate_layout && !Options.classic_hud && lay_mlist.valid) { winner = &lay_mlist; } msgp = winner->msgp; msgsz = winner->msgsz; viewp = winner->viewp; viewsz = winner->viewsz; hudp = winner->hudp; hudsz = winner->hudsz; mlistp = winner->mlistp; mlistsz = winner->mlistsz; #ifdef USE_TILE // libgui may redefine these based on its own settings. gui_init_view_params(*this); #endif init_view(); return; }