From a638e8cc33404bbc757ab08886f436201e78a7d3 Mon Sep 17 00:00:00 2001 From: Charles Dang Date: Tue, 3 Apr 2018 13:53:30 +1100 Subject: [PATCH] Fixed crash when starting the editor (fixes #2816) If no display_context was passed to the display ctor (such as is the case in the editor), the terrain_builder's gamemap pointer would be null. Really not sure why this didn't cause a crash n the 1.14 branch, but oh well. I just removed the ctor call to rebuild_all() since it's not really needed. The map is rebuilt when change_display_context is called anyway, which both the editor_controller and play_controller do. --- src/display.cpp | 2929 +++++++++++++++-------------------------------- 1 file changed, 923 insertions(+), 2006 deletions(-) diff --git a/src/display.cpp b/src/display.cpp index d121d8a1c6bb..ed5b7cfa30f7 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1,6 +1,6 @@ /* Copyright (C) 2003 - 2018 by David White - Part of the Battle for Wesnoth Project https://www.wesnoth.org/ + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,53 +17,44 @@ * Routines to set up the display, scroll and zoom the map. */ +#include "display.hpp" + #include "arrow.hpp" +#include "color.hpp" #include "cursor.hpp" -#include "display.hpp" #include "fake_unit_manager.hpp" -#include "font/sdl_ttf.hpp" +#include "floating_label.hpp" +#include "font/marked-up_text.hpp" #include "font/text.hpp" -#include "preferences/game.hpp" #include "gettext.hpp" +#include "gui/dialogs/loading_screen.hpp" #include "halo.hpp" #include "hotkey/command_executor.hpp" #include "language.hpp" #include "log.hpp" -#include "font/marked-up_text.hpp" -#include "map/map.hpp" #include "map/label.hpp" +#include "map/map.hpp" #include "minimap.hpp" #include "overlay.hpp" -#include "play_controller.hpp" //note: this can probably be refactored out -#include "reports.hpp" +#include "preferences/game.hpp" #include "resources.hpp" -#include "color.hpp" +#include "sdl/render_utils.hpp" #include "synced_context.hpp" #include "team.hpp" #include "terrain/builder.hpp" #include "time_of_day.hpp" -#include "tooltips.hpp" #include "tod_manager.hpp" -#include "units/unit.hpp" +#include "tooltips.hpp" #include "units/animation_component.hpp" #include "units/drawer.hpp" +#include "units/unit.hpp" #include "whiteboard/manager.hpp" -#include "gui/dialogs/loading_screen.hpp" - -#include -#include #include #include #include #include -#ifdef _WIN32 -#include -#endif - -// Includes for bug #17573 - static lg::log_domain log_display("display"); #define ERR_DP LOG_STREAM(err, log_display) #define LOG_DP LOG_STREAM(info, log_display) @@ -77,141 +68,82 @@ static lg::log_domain log_display("display"); #define MinZoom (zoom_levels.front()) #define MaxZoom (zoom_levels.back()) -namespace { - bool benchmark = false; +namespace +{ +bool benchmark = false; +bool debug_foreground = false; - bool debug_foreground = false; +int prevLabel = 0; - int prevLabel = 0; +// frametime is in milliseconds +static unsigned calculate_fps(unsigned frametime) +{ + return frametime != 0u ? 1000u / frametime : 999u; } +} // end anon namespace + unsigned int display::zoom_ = DefaultZoom; unsigned int display::last_zoom_ = SmallZoom; -void display::parse_team_overlays() -{ - const team& curr_team = dc_->teams()[playing_team()]; - const team& prev_team = playing_team() == 0 - ? dc_->teams().back() - : dc_->get_team(playing_team()); - for (const auto& i : get_overlays()) { - for(const overlay& ov : i.second) { - if(!ov.team_name.empty() && - ((ov.team_name.find(curr_team.team_name()) + 1) != 0) != - ((ov.team_name.find(prev_team.team_name()) + 1) != 0)) - { - invalidate(i.first); - } - } - } -} - - -void display::add_overlay(const map_location& loc, const std::string& img, const std::string& halo, const std::string& team_name, const std::string& item_id, bool visible_under_fog, float z_order) -{ - if (halo_man_) { - halo::handle halo_handle; - if(halo != "") { - halo_handle = halo_man_->add(get_location_x(loc) + hex_size() / 2, - get_location_y(loc) + hex_size() / 2, halo, loc); - } - - std::vector& overlays = get_overlays()[loc]; - auto it = std::find_if(overlays.begin(), overlays.end(), [z_order](const overlay& ov) { return ov.z_order > z_order; }); - overlays.emplace(it, img, halo, halo_handle, team_name, item_id, visible_under_fog, z_order); - } -} - -void display::remove_overlay(const map_location& loc) +display::display(const display_context* dc, + std::weak_ptr wb, + const config& theme_cfg, + const config& level, + bool auto_join) + : events::sdl_handler(auto_join) + , dc_(dc) + , halo_man_(new halo::manager()) + , wb_(wb) + , exclusive_unit_draw_requests_() + , video_(CVideo::get_singleton()) + , currentTeam_(0) + , dont_show_all_(false) + , xpos_(0) + , ypos_(0) + , view_locked_(false) + , theme_(theme_cfg, video().screen_area()) + , zoom_index_(0) + , fake_unit_man_(new fake_unit_manager(*this)) + , builder_(new terrain_builder(level, (dc_ ? &dc_->map() : nullptr), theme_.border().tile_image, theme_.border().show_border)) + , minimap_(nullptr) + , minimap_location_(sdl::empty_rect) + , redrawMinimap_(false) + , grid_(false) + , diagnostic_label_(0) + , turbo_speed_(2) + , turbo_(false) + , map_labels_(new map_labels(nullptr)) + , scroll_event_("scrolled") + , complete_redraw_event_("completely_redrawn") + , fps_counter_() + , fps_start_() + , fps_actual_() + , mouseover_hex_overlay_(nullptr) + , tod_hex_mask1(nullptr) + , tod_hex_mask2(nullptr) + , fog_images_() + , shroud_images_() + , selectedHex_() + , mouseoverHex_() + , keys_() + , animate_map_(true) + , animate_water_(true) + , flags_() + , activeTeam_(0) + , map_screenshot_(false) + , reach_map_() + , overlays_(nullptr) + , fps_handle_(0) + , drawn_hexes_(0) + , idle_anim_(preferences::idle_anim()) + , idle_anim_rate_(1.0) + , draw_coordinates_(false) + , draw_terrain_codes_(false) + , draw_num_of_bitmaps_(false) + , arrows_map_() + , color_adjust_() { - get_overlays().erase(loc); -} - -void display::remove_single_overlay(const map_location& loc, const std::string& toDelete) -{ - std::vector& overlays = get_overlays()[loc]; - overlays.erase( - std::remove_if( - overlays.begin(), overlays.end(), - [&toDelete](const overlay& ov) { return ov.image == toDelete || ov.halo == toDelete || ov.id == toDelete; } - ), - overlays.end() - ); -} - -display::display(const display_context * dc, std::weak_ptr wb, reports & reports_object, const config& theme_cfg, const config& level, bool auto_join) : - video2::draw_layering(auto_join), - dc_(dc), - halo_man_(new halo::manager(*this)), - wb_(wb), - exclusive_unit_draw_requests_(), - screen_(CVideo::get_singleton()), - currentTeam_(0), - dont_show_all_(false), - xpos_(0), - ypos_(0), - view_locked_(false), - theme_(theme_cfg, screen_.screen_area()), - zoom_index_(0), - fake_unit_man_(new fake_unit_manager(*this)), - builder_(new terrain_builder(level, (dc_ ? &dc_->map() : nullptr), theme_.border().tile_image, theme_.border().show_border)), - minimap_(nullptr), - minimap_location_(sdl::empty_rect), - redrawMinimap_(false), - redraw_background_(true), - invalidateAll_(true), - grid_(false), - diagnostic_label_(0), - panelsDrawn_(false), - turbo_speed_(2), - turbo_(false), - invalidateGameStatus_(true), - map_labels_(new map_labels(nullptr)), - reports_object_(&reports_object), - scroll_event_("scrolled"), - complete_redraw_event_("completely_redrawn"), - frametimes_(50), - fps_counter_(), - fps_start_(), - fps_actual_(), - reportRects_(), - reportSurfaces_(), - reports_(), - menu_buttons_(), - action_buttons_(), - invalidated_(), - mouseover_hex_overlay_(nullptr), - tod_hex_mask1(nullptr), - tod_hex_mask2(nullptr), - fog_images_(), - shroud_images_(), - selectedHex_(), - mouseoverHex_(), - keys_(), - animate_map_(true), - animate_water_(true), - flags_(), - activeTeam_(0), - drawing_buffer_(), - map_screenshot_(false), - reach_map_(), - reach_map_old_(), - reach_map_changed_(true), - fps_handle_(0), - invalidated_hexes_(0), - drawn_hexes_(0), - idle_anim_(preferences::idle_anim()), - idle_anim_rate_(1.0), - map_screenshot_surf_(nullptr), - redraw_observers_(), - draw_coordinates_(false), - draw_terrain_codes_(false), - draw_num_of_bitmaps_(false), - arrows_map_(), - color_adjust_(), - dirty_() -{ - //The following assertion fails when starting a campaign assert(singleton_ == nullptr); singleton_ = this; @@ -221,10 +153,8 @@ display::display(const display_context * dc, std::weak_ptr wb, repo read(level.child_or_empty("display")); - if(screen_.non_interactive() - && (screen_.getSurface() != nullptr - && screen_.faked())) { - screen_.lock_updates(true); + if(video_.non_interactive() && video_.faked()) { + video_.lock_updates(true); } fill_images_list(game_config::fog_prefix, fog_images_); @@ -237,40 +167,27 @@ display::display(const display_context * dc, std::weak_ptr wb, repo image::set_zoom(zoom_); init_flags(); - - if(!menu_buttons_.empty() || !action_buttons_.empty()) { - create_buttons(); - } - -#ifdef _WIN32 - // Increase timer resolution to prevent delays getting much longer than they should. - timeBeginPeriod(1u); -#endif } display::~display() { -#ifdef _WIN32 - timeEndPeriod(1u); -#endif - singleton_ = nullptr; resources::fake_units = nullptr; } -void display::set_theme(config theme_cfg) { - theme_ = theme(theme_cfg, screen_.screen_area()); - builder_->set_draw_border(theme_.border().show_border); - menu_buttons_.clear(); - action_buttons_.clear(); - create_buttons(); - invalidate_theme(); +void display::set_theme(config theme_cfg) +{ + theme_ = theme(theme_cfg, video_.screen_area()); } -void display::init_flags() { - +void display::init_flags() +{ flags_.clear(); - if (!dc_) return; + + if(!dc_) { + return; + } + flags_.resize(dc_->teams().size()); std::vector side_colors; @@ -279,15 +196,17 @@ void display::init_flags() { for(const team& t : dc_->teams()) { std::string side_color = t.color(); side_colors.push_back(side_color); + init_flags_for_side_internal(t.side() - 1, side_color); } + image::set_team_colors(&side_colors); } void display::reinit_flags_for_side(std::size_t side) { - if (!dc_ || side >= dc_->teams().size()) { - ERR_DP << "Cannot rebuild flags for inexistent or unconfigured side " << side << '\n'; + if(!dc_ || side >= dc_->teams().size()) { + ERR_DP << "Cannot rebuild flags for nonexistent or unconfigured side " << side << '\n'; return; } @@ -324,84 +243,99 @@ void display::init_flags_for_side_internal(std::size_t n, const std::string& sid str = sub_items.front(); try { time = std::max(1, std::stoi(sub_items.back())); - } catch(const std::invalid_argument&) { + } catch(std::invalid_argument&) { ERR_DP << "Invalid time value found when constructing flag for side " << n << ": " << sub_items.back() << "\n"; } } std::stringstream temp; - temp << str << "~RC(" << old_rgb << ">"<< new_rgb << ")"; + temp << str << "~RC(" << old_rgb << ">" << new_rgb << ")"; image::locator flag_image(temp.str()); + temp_anim.add_frame(time, flag_image); } animated& f = flags_[n]; f = temp_anim; auto time = f.get_end_time(); - if (time > 0) { - f.start_animation(randomness::rng::default_instance().get_random_int(0, time-1), true); - } - else { + if(time > 0) { + f.start_animation(randomness::rng::default_instance().get_random_int(0, time - 1), true); + } else { // this can happen if both flag and game_config::images::flag are empty. ERR_DP << "missing flag for team" << n << "\n"; } } -surface display::get_flag(const map_location& loc) -{ - if(!get_map().is_village(loc)) { - return surface(nullptr); - } - - for (const team& t : dc_->teams()) { - if (t.owns_village(loc) && (!fogged(loc) || !dc_->get_team(viewing_side()).is_enemy(t.side()))) - { - auto& flag = flags_[t.side() - 1]; - flag.update_last_draw_time(); - const image::locator &image_flag = animate_map_ ? - flag.get_current_frame() : flag.get_first_frame(); - return image::get_image(image_flag, image::TOD_COLORED); - } - } - - return surface(nullptr); -} - void display::set_team(std::size_t teamindex, bool show_everything) { assert(teamindex < dc_->teams().size()); currentTeam_ = teamindex; - if (!show_everything) - { + + if(!show_everything) { labels().set_team(&dc_->teams()[teamindex]); dont_show_all_ = true; - } - else - { + } else { labels().set_team(nullptr); dont_show_all_ = false; } + labels().recalculate_labels(); - if(std::shared_ptr w = wb_.lock()) + + if(std::shared_ptr w = wb_.lock()) { w->on_viewer_change(teamindex); + } } void display::set_playing_team(std::size_t teamindex) { assert(teamindex < dc_->teams().size()); activeTeam_ = teamindex; - invalidate_game_status(); +} + +void display::add_overlay(const map_location& loc, + const std::string& img, + const std::string& halo, + const std::string& team_name, + const std::string& item_id, + bool visible_under_fog) +{ + if(halo_man_) { + const halo::handle halo_handle = halo_man_->add( + get_location_x(loc) + hex_size() / 2, + get_location_y(loc) + hex_size() / 2, halo, loc + ); + + overlays_->emplace(loc, overlay(img, halo, halo_handle, team_name, item_id, visible_under_fog)); + } +} + +void display::remove_overlay(const map_location& loc) +{ + overlays_->erase(loc); +} + +void display::remove_single_overlay(const map_location& loc, const std::string& toDelete) +{ + // Iterate through the values with key of loc + auto itors = overlays_->equal_range(loc); + + while(itors.first != itors.second) { + const overlay& o = itors.first->second; + + if(o.image == toDelete || o.halo == toDelete || o.id == toDelete) { + overlays_->erase(itors.first++); + } else { + ++itors.first; + } + } } bool display::add_exclusive_draw(const map_location& loc, unit& unit) { - if (loc.valid() && exclusive_unit_draw_requests_.find(loc) == exclusive_unit_draw_requests_.end()) - { + if(loc.valid() && exclusive_unit_draw_requests_.find(loc) == exclusive_unit_draw_requests_.end()) { exclusive_unit_draw_requests_[loc] = unit.id(); return true; - } - else - { + } else { return false; } } @@ -409,16 +343,16 @@ bool display::add_exclusive_draw(const map_location& loc, unit& unit) std::string display::remove_exclusive_draw(const map_location& loc) { std::string id = ""; - if(loc.valid()) - { + if(loc.valid()) { id = exclusive_unit_draw_requests_[loc]; - //id will be set to the default "" string by the [] operator if the map doesn't have anything for that loc. + // id will be set to the default "" string by the [] operator if the map doesn't have anything for that loc. exclusive_unit_draw_requests_.erase(loc); } + return id; } -const time_of_day & display::get_time_of_day(const map_location& /*loc*/) const +const time_of_day& display::get_time_of_day(const map_location& /*loc*/) const { static time_of_day tod; return tod; @@ -443,29 +377,36 @@ void display::adjust_color_overlay(int r, int g, int b) void display::fill_images_list(const std::string& prefix, std::vector& images) { - if(prefix == ""){ + if(prefix == "") { return; } // search prefix.png, prefix1.png, prefix2.png ... - for(int i=0; ; ++i){ + for(int i = 0;; ++i) { std::ostringstream s; s << prefix; - if(i != 0) + + if(i != 0) { s << i; + } + s << ".png"; - if(image::exists(s.str())) + + if(image::exists(s.str())) { images.push_back(s.str()); - else if(i>0) + } else if(i > 0) { break; + } } - if (images.empty()) + + if(images.empty()) { images.emplace_back(); + } } -const std::string& display::get_variant(const std::vector& variants, const map_location &loc) +const std::string& display::get_variant(const std::vector& variants, const map_location& loc) const { - //TODO use better noise function + // TODO use better noise function return variants[std::abs(loc.x + loc.y) % variants.size()]; } @@ -476,32 +417,32 @@ void display::rebuild_all() void display::reload_map() { - redraw_background_ = true; builder_->reload_map(); } -void display::change_display_context(const display_context * dc) +void display::change_display_context(const display_context* dc) { dc_ = dc; - builder_->change_map(&dc_->map()); //TODO: Should display_context own and initialize the builder object? + builder_->change_map(&dc_->map()); // TODO: Should display_context own and initialize the builder object? } void display::reset_halo_manager() { - halo_man_.reset(new halo::manager(*this)); + halo_man_.reset(new halo::manager()); } -void display::reset_halo_manager(halo::manager & halo_man) +void display::reset_halo_manager(halo::manager& halo_man) { halo_man_.reset(&halo_man); } void display::blindfold(bool value) { - if(value == true) + if(value == true) { ++blindfold_ctr_; - else + } else { --blindfold_ctr_; + } } bool display::is_blindfolded() const @@ -509,20 +450,16 @@ bool display::is_blindfolded() const return blindfold_ctr_ > 0; } - const SDL_Rect& display::max_map_area() const { static SDL_Rect max_area {0, 0, 0, 0}; - // hex_size() is always a multiple of 4 - // and hex_width() a multiple of 3, - // so there shouldn't be off-by-one-errors - // due to rounding. - // To display a hex fully on screen, - // a little bit extra space is needed. + // hex_size() is always a multiple of 4 and hex_width() a multiple of 3, + // so there shouldn't be off-by-one-errors due to rounding. + // To display a hex fully on screen, a little bit extra space is needed. // Also added the border two times. - max_area.w = static_cast((get_map().w() + 2 * theme_.border().size + 1.0/3.0) * hex_width()); - max_area.h = static_cast((get_map().h() + 2 * theme_.border().size + 0.5) * hex_size()); + max_area.w = static_cast((get_map().w() + 2 * theme_.border().size + 1.0 / 3.0) * hex_width()); + max_area.h = static_cast((get_map().h() + 2 * theme_.border().size + 0.5) * hex_size()); return max_area; } @@ -533,41 +470,45 @@ const SDL_Rect& display::map_area() const max_area = max_map_area(); // if it's for map_screenshot, maximize and don't recenter - if (map_screenshot_) { + if(map_screenshot_) { return max_area; } static SDL_Rect res; res = map_outside_area(); + // map is smaller, center if(max_area.w < res.w) { - // map is smaller, center - res.x += (res.w - max_area.w)/2; + res.x += (res.w - max_area.w) / 2; res.w = max_area.w; } + // map is smaller, center if(max_area.h < res.h) { - // map is smaller, center - res.y += (res.h - max_area.h)/2; + res.y += (res.h - max_area.h) / 2; res.h = max_area.h; } return res; } +const SDL_Rect display::map_outside_area() const +{ + return map_screenshot_ ? max_map_area() : video_.screen_area(); +} + bool display::outside_area(const SDL_Rect& area, const int x, const int y) { const int x_thresh = hex_size(); const int y_thresh = hex_size(); - return (x < area.x || x > area.x + area.w - x_thresh || - y < area.y || y > area.y + area.h - y_thresh); + return (x < area.x || x > area.x + area.w - x_thresh || y < area.y || y > area.y + area.h - y_thresh); } // This function uses the screen as reference const map_location display::hex_clicked_on(int xclick, int yclick) const { const SDL_Rect& rect = map_area(); - if(sdl::point_in_rect(xclick,yclick,rect) == false) { + if(sdl::point_in_rect(xclick, yclick, rect) == false) { return map_location(); } @@ -577,13 +518,13 @@ const map_location display::hex_clicked_on(int xclick, int yclick) const return pixel_position_to_hex(xpos_ + xclick, ypos_ + yclick); } - // This function uses the rect of map_area as reference const map_location display::pixel_position_to_hex(int x, int y) const { // adjust for the border x -= static_cast(theme_.border().size * hex_width()); y -= static_cast(theme_.border().size * hex_size()); + // The editor can modify the border and this will result in a negative y // value. Instead of adding extra cases we just shift the hex. Since the // editor doesn't use the direction this is no problem. @@ -592,6 +533,7 @@ const map_location display::pixel_position_to_hex(int x, int y) const x += hex_width(); y += hex_size(); } + const int s = hex_size(); const int tesselation_x_size = hex_width() * 2; const int tesselation_y_size = s; @@ -603,23 +545,22 @@ const map_location display::pixel_position_to_hex(int x, int y) const int x_modifier = 0; int y_modifier = 0; - if (y_mod < tesselation_y_size / 2) { - if ((x_mod * 2 + y_mod) < (s / 2)) { + if(y_mod < tesselation_y_size / 2) { + if((x_mod * 2 + y_mod) < (s / 2)) { x_modifier = -1; y_modifier = -1; - } else if ((x_mod * 2 - y_mod) < (s * 3 / 2)) { + } else if((x_mod * 2 - y_mod) < (s * 3 / 2)) { x_modifier = 0; y_modifier = 0; } else { x_modifier = 1; y_modifier = -1; } - } else { - if ((x_mod * 2 - (y_mod - s / 2)) < 0) { + if((x_mod * 2 - (y_mod - s / 2)) < 0) { x_modifier = -1; y_modifier = 0; - } else if ((x_mod * 2 + (y_mod - s / 2)) < s * 2) { + } else if((x_mod * 2 + (y_mod - s / 2)) < s * 2) { x_modifier = 0; y_modifier = 0; } else { @@ -631,67 +572,42 @@ const map_location display::pixel_position_to_hex(int x, int y) const return map_location(x_base + x_modifier - offset, y_base + y_modifier - offset); } -display::rect_of_hexes::iterator& display::rect_of_hexes::iterator::operator++() -{ - if (loc_.y < rect_.bottom[loc_.x & 1]) - ++loc_.y; - else { - ++loc_.x; - loc_.y = rect_.top[loc_.x & 1]; - } - - return *this; -} - -// begin is top left, and end is after bottom right -display::rect_of_hexes::iterator display::rect_of_hexes::begin() const -{ - return iterator(map_location(left, top[left & 1]), *this); -} -display::rect_of_hexes::iterator display::rect_of_hexes::end() const -{ - return iterator(map_location(right+1, top[(right+1) & 1]), *this); -} - -const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const +const rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const { rect_of_hexes res; - if (r.w<=0 || r.h<=0) { - // empty rect, return dummy values giving begin=end - res.left = 0; - res.right = -1; // end is right+1 - res.top[0] = 0; - res.top[1] = 0; - res.bottom[0] = 0; - res.bottom[1] = 0; + if(r.w <= 0 || r.h <= 0) { return res; } SDL_Rect map_rect = map_area(); + // translate rect coordinates from screen-based to map_area-based int x = xpos_ - map_rect.x + r.x; int y = ypos_ - map_rect.y + r.y; + // we use the "double" type to avoid important rounding error (size of an hex!) // we will also need to use std::floor to avoid bad rounding at border (negative values) double tile_width = hex_width(); double tile_size = hex_size(); double border = theme_.border().size; + // we minus "0.(3)", for horizontal imbrication. // reason is: two adjacent hexes each overlap 1/4 of their width, so for // grid calculation 3/4 of tile width is used, which by default gives // 18/54=0.(3). Note that, while tile_width is zoom dependent, 0.(3) is not. res.left = static_cast(std::floor(-border + x / tile_width - 0.3333333)); + // we remove 1 pixel of the rectangle dimensions // (the rounded division take one pixel more than needed) - res.right = static_cast(std::floor(-border + (x + r.w-1) / tile_width)); + res.right = static_cast(std::floor(-border + (x + r.w - 1) / tile_width)); // for odd x, we must shift up one half-hex. Since x will vary along the edge, // we store here the y values for even and odd x, respectively res.top[0] = static_cast(std::floor(-border + y / tile_size)); res.top[1] = static_cast(std::floor(-border + y / tile_size - 0.5)); - res.bottom[0] = static_cast(std::floor(-border + (y + r.h-1) / tile_size)); - res.bottom[1] = static_cast(std::floor(-border + (y + r.h-1) / tile_size - 0.5)); + res.bottom[0] = static_cast(std::floor(-border + (y + r.h - 1) / tile_size)); + res.bottom[1] = static_cast(std::floor(-border + (y + r.h - 1) / tile_size - 0.5)); // TODO: in some rare cases (1/16), a corner of the big rect is on a tile // (the 72x72 rectangle containing the hex) but not on the hex itself @@ -700,6 +616,11 @@ const display::rect_of_hexes display::hexes_under_rect(const SDL_Rect& r) const return res; } +const rect_of_hexes display::get_visible_hexes() const +{ + return hexes_under_rect(map_area()); +} + bool display::team_valid() const { return currentTeam_ < dc_->teams().size(); @@ -725,12 +646,20 @@ int display::get_location_y(const map_location& loc) const return static_cast(map_area().y + (loc.y + theme_.border().size) * zoom_ - ypos_ + (is_odd(loc.x) ? zoom_/2 : 0)); } +SDL_Point display::get_loc_drawing_origin(const map_location& loc) const +{ + return { + get_location_x(loc), + get_location_y(loc) + }; +} + map_location display::minimap_location_on(int x, int y) { - //TODO: don't return location for this, + // TODO: don't return location for this, // instead directly scroll to the clicked pixel position - if (!sdl::point_in_rect(x, y, minimap_area())) { + if(!sdl::point_in_rect(x, y, minimap_area())) { return map_location(); } @@ -738,214 +667,82 @@ map_location display::minimap_location_on(int x, int y) // probably more adjustments to do (border, minimap shift...) // but the mouse and human capacity to evaluate the rectangle center // is not pixel precise. - int px = (x - minimap_location_.x) * get_map().w()*hex_width() / std::max (minimap_location_.w, 1); - int py = (y - minimap_location_.y) * get_map().h()*hex_size() / std::max(minimap_location_.h, 1); + int px = (x - minimap_location_.x) * get_map().w() * hex_width() / std::max(minimap_location_.w, 1); + int py = (y - minimap_location_.y) * get_map().h() * hex_size() / std::max(minimap_location_.h, 1); map_location loc = pixel_position_to_hex(px, py); - if (loc.x < 0) - loc.x = 0; - else if (loc.x >= get_map().w()) - loc.x = get_map().w() - 1; - - if (loc.y < 0) - loc.y = 0; - else if (loc.y >= get_map().h()) - loc.y = get_map().h() - 1; + loc.x = utils::clamp(loc.x, 0, get_map().w() - 1); + loc.y = utils::clamp(loc.x, 0, get_map().h() - 1); return loc; } surface display::screenshot(bool map_screenshot) { - if (!map_screenshot) { + if(!map_screenshot) { // Use make_neutral_surface() to copy surface content - return make_neutral_surface(screen_.getSurface()); - } else { - if (get_map().empty()) { - ERR_DP << "No map loaded, cannot create a map screenshot.\n"; - return nullptr; - } - - SDL_Rect area = max_map_area(); - map_screenshot_surf_ = create_compatible_surface(screen_.getSurface(), area.w, area.h); - - if (map_screenshot_surf_ == nullptr) { - // Memory problem ? - ERR_DP << "Could not create screenshot surface, try zooming out.\n"; - return nullptr; - } - - // back up the current map view position and move to top-left - int old_xpos = xpos_; - int old_ypos = ypos_; - xpos_ = 0; - ypos_ = 0; - - // we reroute render output to the screenshot surface and invalidate all - map_screenshot_= true; - invalidateAll_ = true; - DBG_DP << "draw() with map_screenshot\n"; - draw(true,true); - - // restore normal rendering - map_screenshot_= false; - xpos_ = old_xpos; - ypos_ = old_ypos; - - // Clear map_screenshot_surf_ and return a new surface that contains the same data - surface surf(std::move(map_screenshot_surf_)); - return surf; - } -} - -std::shared_ptr display::find_action_button(const std::string& id) -{ - for (std::size_t i = 0; i < action_buttons_.size(); ++i) { - if(action_buttons_[i]->id() == id) { - return action_buttons_[i]; - } - } - return nullptr; -} - -std::shared_ptr display::find_menu_button(const std::string& id) -{ - for (std::size_t i = 0; i < menu_buttons_.size(); ++i) { - if(menu_buttons_[i]->id() == id) { - return menu_buttons_[i]; - } - } - return nullptr; -} - -void display::layout_buttons() -{ - DBG_DP << "positioning menu buttons...\n"; - for(const auto& menu : theme_.menus()) { - std::shared_ptr b = find_menu_button(menu.get_id()); - if(b) { - const SDL_Rect& loc = menu.location(screen_.screen_area()); - b->set_location(loc); - b->set_measurements(0,0); - b->set_label(menu.title()); - b->set_image(menu.image()); - } - } - - DBG_DP << "positioning action buttons...\n"; - for(const auto& action : theme_.actions()) { - std::shared_ptr b = find_action_button(action.get_id()); - if(b) { - const SDL_Rect& loc = action.location(screen_.screen_area()); - b->set_location(loc); - b->set_measurements(0,0); - b->set_label(action.title()); - b->set_image(action.image()); - } + // TODO: convert to texture handling + //return make_neutral_surface(video_.getSurface()); + return surface(nullptr); } -} - -void display::create_buttons() -{ - std::vector> menu_work; - std::vector> action_work; - - DBG_DP << "creating menu buttons...\n"; - for(const auto& menu : theme_.menus()) { - if (!menu.is_button()) continue; - std::shared_ptr b(new gui::button(screen_, menu.title(), gui::button::TYPE_PRESS, menu.image(), - gui::button::DEFAULT_SPACE, false, menu.overlay())); - DBG_DP << "drawing button " << menu.get_id() << "\n"; - b->join_same(this); - b->set_id(menu.get_id()); - if (!menu.tooltip().empty()){ - b->set_tooltip_string(menu.tooltip()); - } - - std::shared_ptr b_prev = find_menu_button(b->id()); - if(b_prev) { - b->enable(b_prev->enabled()); - } - - menu_work.push_back(b); + if(get_map().empty()) { + ERR_DP << "No map loaded, cannot create a map screenshot.\n"; + return nullptr; } - DBG_DP << "creating action buttons...\n"; - for(const auto& action : theme_.actions()) { - std::shared_ptr b(new gui::button(screen_, action.title(), string_to_button_type(action.type()), action.image(), - gui::button::DEFAULT_SPACE, false, action.overlay())); - - DBG_DP << "drawing button " << action.get_id() << "\n"; - b->set_id(action.get_id()); - b->join_same(this); - if (!action.tooltip(0).empty()){ - b->set_tooltip_string(action.tooltip(0)); - } - - std::shared_ptr b_prev = find_action_button(b->id()); - if(b_prev) { - b->enable(b_prev->enabled()); - if (b_prev->get_type() == gui::button::TYPE_CHECK) { - b->set_check(b_prev->checked()); - } - } + const SDL_Rect area = max_map_area(); + surface res = create_neutral_surface(area.w, area.h); - action_work.push_back(b); + // Memory problem? + if(res == nullptr) { + ERR_DP << "Could not create screenshot surface, try zooming out.\n"; + return nullptr; } + // Back up the current map viewport position and move to top-left. + const int old_xpos = xpos_; + const int old_ypos = ypos_; + xpos_ = 0; + ypos_ = 0; - menu_buttons_.clear(); - menu_buttons_.assign(menu_work.begin(), menu_work.end()); - action_buttons_.clear(); - action_buttons_.assign(action_work.begin(), action_work.end()); + // Reroute render output to a separate texture . + texture output_texture(area.w, area.h, SDL_TEXTUREACCESS_TARGET); + const render_target_setter target_setter(output_texture); - layout_buttons(); - DBG_DP << "buttons created\n"; -} + map_screenshot_ = true; -void display::render_buttons() -{ - for (std::shared_ptr btn : menu_buttons_) { - btn->set_dirty(true); - btn->draw(); - } + DBG_DP << "draw() call for map screenshot\n"; + draw(); - for (std::shared_ptr btn : action_buttons_) { - btn->set_dirty(true); - btn->draw(); - } -} + map_screenshot_ = false; + // Restore map viewport position + xpos_ = old_xpos; + ypos_ = old_ypos; -gui::button::TYPE display::string_to_button_type(std::string type) -{ - gui::button::TYPE res = gui::button::TYPE_PRESS; - if (type == "checkbox") { res = gui::button::TYPE_CHECK; } - else if (type == "image") { res = gui::button::TYPE_IMAGE; } - else if (type == "radiobox") { res = gui::button::TYPE_RADIO; } - else if (type == "turbo") { res = gui::button::TYPE_TURBO; } + // Copy the texture data to the output surface. + SDL_RenderReadPixels(video_.get_renderer(), &area, SDL_PIXELFORMAT_ARGB8888, res->pixels, res->pitch); return res; } static const std::string& get_direction(std::size_t n) { - static const std::array dirs {{ "-n", "-ne", "-se", "-s", "-sw", "-nw" }}; + static const std::array dirs {{"-n", "-ne", "-se", "-s", "-sw", "-nw"}}; return dirs[n >= dirs.size() ? 0 : n]; } -std::vector display::get_fog_shroud_images(const map_location& loc, image::TYPE image_type) +void display::draw_fog_shroud_transition_images(const map_location& loc, image::TYPE /*image_type*/) { std::vector names; adjacent_loc_array_t adjacent; get_adjacent_tiles(loc, adjacent.data()); - enum visibility {FOG=0, SHROUD=1, CLEAR=2}; + enum visibility { FOG = 0, SHROUD = 1, CLEAR = 2 }; visibility tiles[6]; - const std::string* image_prefix[] = - { &game_config::fog_prefix, &game_config::shroud_prefix}; + const std::string* image_prefix[]{&game_config::fog_prefix, &game_config::shroud_prefix}; for(int i = 0; i < 6; ++i) { if(shrouded(adjacent[i])) { @@ -967,34 +764,34 @@ std::vector display::get_fog_shroud_images(const map_location& loc, ima } if(start == 6) { - // Completely surrounded by fog or shroud. This might have - // a special graphic. + // Completely surrounded by fog or shroud. This might have a special graphic. const std::string name = *image_prefix[v] + "-all.png"; - if ( image::exists(name) ) { + if(image::exists(name)) { names.push_back(name); // Proceed to the next visibility (fog -> shroud -> clear). continue; } + // No special graphic found. We'll just combine some other images // and hope it works out. start = 0; } // Find all the directions overlap occurs from - for (int i = (start+1)%6, cap1 = 0; i != start && cap1 != 6; ++cap1) { + for(int i = (start + 1) % 6, cap1 = 0; i != start && cap1 != 6; ++cap1) { if(tiles[i] == v) { std::ostringstream stream; - std::string name; stream << *image_prefix[v]; - for (int cap2 = 0; v == tiles[i] && cap2 != 6; i = (i+1)%6, ++cap2) { + std::string name; + for(int cap2 = 0; v == tiles[i] && cap2 != 6; i = (i + 1) % 6, ++cap2) { stream << get_direction(i); if(!image::exists(stream.str() + ".png")) { // If we don't have any surface at all, // then move onto the next overlapped area if(name.empty()) { - i = (i+1)%6; + i = (i + 1) % 6; } break; } else { @@ -1006,597 +803,137 @@ std::vector display::get_fog_shroud_images(const map_location& loc, ima names.push_back(name + ".png"); } } else { - i = (i+1)%6; + i = (i + 1) % 6; } } } - // now get the surfaces - std::vector res; - - for (std::string& name : names) { - surface surf(image::get_image(name, image_type)); - if (surf) - res.push_back(std::move(surf)); + // Now render the images + for(std::string& name : names) { + render_scaled_to_zoom(image::get_texture(name), loc); // TODO: image_type } - - return res; } -void display::get_terrain_images(const map_location &loc, - const std::string& timeid, - TERRAIN_TYPE terrain_type) +void display::toggle_benchmark() { - terrain_image_vector_.clear(); - - terrain_builder::TERRAIN_TYPE builder_terrain_type = - (terrain_type == FOREGROUND ? - terrain_builder::FOREGROUND : terrain_builder::BACKGROUND); - - const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc, - timeid, builder_terrain_type); - - image::light_string lt; + benchmark = !benchmark; +} - const time_of_day& tod = get_time_of_day(loc); +void display::toggle_debug_foreground() +{ + debug_foreground = !debug_foreground; +} - //get all the light transitions - adjacent_loc_array_t adjs; - std::array atods; - get_adjacent_tiles(loc, adjs.data()); - for(size_t d = 0; d < adjs.size(); ++d){ - atods[d] = &get_time_of_day(adjs[d]); +void display::draw_debugging_aids() +{ + if(video_.update_locked()) { + return; } - for(int d=0; d<6; ++d){ - /* concave - _____ - / \ - / atod1 \_____ - \ !tod / \ - \_____/ atod2 \ - / \__\ !tod / - / \_____/ - \ tod / - \_____/*/ - - const time_of_day& atod1 = *atods[d]; - const time_of_day& atod2 = *atods[(d + 1) % 6]; + if(preferences::show_fps() || benchmark) { + static int frames = 0; + ++frames; - if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color) - continue; + const int sample_freq = 10; + if(frames == sample_freq) { + const auto minmax_it = std::minmax_element(frametimes_.begin(), frametimes_.end()); + const unsigned render_avg = std::accumulate(frametimes_.begin(), frametimes_.end(), 0) / frametimes_.size(); + const int avg_fps = calculate_fps(render_avg); + const int max_fps = calculate_fps(*minmax_it.first); + const int min_fps = calculate_fps(*minmax_it.second); + frames = 0; - if(lt.empty()) { - //color the full hex before adding transitions - tod_color col = tod.color + color_adjust_; - lt = image::get_light_string(0, col.r, col.g, col.b); - } + if(fps_handle_ != 0) { + font::remove_floating_label(fps_handle_); + fps_handle_ = 0; + } - // add the directional transitions - tod_color acol = atod1.color + color_adjust_; - lt += image::get_light_string(d + 1, acol.r, acol.g, acol.b); - } - for(int d=0; d<6; ++d){ - /* convex 1 - _____ - / \ - / atod1 \_____ - \ !tod / \ - \_____/ atod2 \ - / \__\ tod / - / \_____/ - \ tod / - \_____/*/ - - const time_of_day& atod1 = *atods[d]; - const time_of_day& atod2 = *atods[(d + 1) % 6]; - - if(atod1.color == tod.color || atod1.color == atod2.color) - continue; + std::ostringstream stream; + stream << " min/avg/max/act\n"; + stream << "FPS: " << std::setfill(' ') << std::setw(3) << min_fps << '/'<< std::setw(3) << avg_fps << '/' << std::setw(3) << max_fps << '/' << std::setw(3) << fps_actual_ << "\n"; + stream << "Time: " << std::setfill(' ') << std::setw(3) << *minmax_it.first << '/' << std::setw(3) << render_avg << '/' << std::setw(3) << *minmax_it.second << " ms\n"; + if(game_config::debug) { + stream << "\nhex: " << drawn_hexes_ * 1.0 / sample_freq; + } - if(lt.empty()) { - //color the full hex before adding transitions - tod_color col = tod.color + color_adjust_; - lt = image::get_light_string(0, col.r, col.g, col.b); - } + drawn_hexes_ = 0; - // add the directional transitions - tod_color acol = atod1.color + color_adjust_; - lt += image::get_light_string(d + 7, acol.r, acol.g, acol.b); - } - for(int d=0; d<6; ++d){ - /* convex 2 - _____ - / \ - / atod1 \_____ - \ tod / \ - \_____/ atod2 \ - / \__\ !tod / - / \_____/ - \ tod / - \_____/*/ - - const time_of_day& atod1 = *atods[d]; - const time_of_day& atod2 = *atods[(d + 1) % 6]; - - if(atod2.color == tod.color || atod1.color == atod2.color) - continue; + font::floating_label flabel(stream.str()); + flabel.set_font_size(12); + flabel.set_color(benchmark ? font::BAD_COLOR : font::NORMAL_COLOR); + flabel.set_position(10, 100); + flabel.set_alignment(font::LEFT_ALIGN); - if(lt.empty()) { - //color the full hex before adding transitions - tod_color col = tod.color + color_adjust_; - lt = image::get_light_string(0, col.r, col.g, col.b); + fps_handle_ = font::add_floating_label(flabel); } - - // add the directional transitions - tod_color acol = atod2.color + color_adjust_; - lt += image::get_light_string(d + 13, acol.r, acol.g, acol.b); + } else if(fps_handle_ != 0) { + font::remove_floating_label(fps_handle_); + fps_handle_ = 0; + drawn_hexes_ = 0; } +} - if(lt.empty()){ - tod_color col = tod.color + color_adjust_; - if(!col.is_zero()){ - // no real lightmap needed but still color the hex - lt = image::get_light_string(-1, col.r, col.g, col.b); - } - } +void display::draw_background() +{ + const std::string& image = theme_.border().background_image; + SDL_Rect area = map_outside_area(); - if(terrains != nullptr) { - // Cache the offmap name. - // Since it is themeable it can change, - // so don't make it static. - const std::string off_map_name = "terrain/" + theme_.border().tile_image; - for(const auto& terrain : *terrains) { - const image::locator &image = animate_map_ ? - terrain.get_current_frame() : terrain.get_first_frame(); - - // We prevent ToD coloring and brightening of off-map tiles, - // We need to test for the tile to be rendered and - // not the location, since the transitions are rendered - // over the offmap-terrain and these need a ToD coloring. - surface surf; - const bool off_map = (image.get_filename() == off_map_name || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos); - - if(off_map) { - surf = image::get_image(image, image::SCALED_TO_HEX); - } else if(lt.empty()) { - surf = image::get_image(image, image::SCALED_TO_HEX); - } else { - surf = image::get_lighted_image(image, lt, image::SCALED_TO_HEX); - } - - if (!surf.null()) { - terrain_image_vector_.push_back(std::move(surf)); - } - } - } -} - -void display::drawing_buffer_add(const drawing_layer layer, - const map_location& loc, int x, int y, const surface& surf, - const SDL_Rect &clip) -{ - drawing_buffer_.emplace_back(layer, loc, x, y, surf, clip); -} - -void display::drawing_buffer_add(const drawing_layer layer, - const map_location& loc, int x, int y, - const std::vector &surf, - const SDL_Rect &clip) -{ - drawing_buffer_.emplace_back(layer, loc, x, y, surf, clip); -} - -// FIXME: temporary method. Group splitting should be made -// public into the definition of drawing_layer -// -// The drawing is done per layer_group, the range per group is [low, high). -const std::array display::drawing_buffer_key::layer_groups {{ - LAYER_TERRAIN_BG, - LAYER_UNIT_FIRST, - LAYER_UNIT_MOVE_DEFAULT, - // Make sure the movement doesn't show above fog and reachmap. - LAYER_REACHMAP -}}; - -enum { - // you may adjust the following when needed: - - // maximum border. 3 should be safe even if a larger border is in use somewhere - MAX_BORDER = 3, - - // store x, y, and layer in one 32 bit integer - // 4 most significant bits == layer group => 16 - BITS_FOR_LAYER_GROUP = 4, - - // 10 second most significant bits == y => 1024 - BITS_FOR_Y = 10, - - // 1 third most significant bit == x parity => 2 - BITS_FOR_X_PARITY = 1, - - // 8 fourth most significant bits == layer => 256 - BITS_FOR_LAYER = 8, - - // 9 least significant bits == x / 2 => 512 (really 1024 for x) - BITS_FOR_X_OVER_2 = 9 -}; - -inline display::drawing_buffer_key::drawing_buffer_key(const map_location &loc, drawing_layer layer) - : key_(0) -{ - // Start with the index of last group entry... - unsigned int group_i = layer_groups.size() - 1; - - // ...and works backwards until the group containing the specified layer is found. - while(layer < layer_groups[group_i]) { - --group_i; - } - - enum { - SHIFT_LAYER = BITS_FOR_X_OVER_2, - SHIFT_X_PARITY = BITS_FOR_LAYER + SHIFT_LAYER, - SHIFT_Y = BITS_FOR_X_PARITY + SHIFT_X_PARITY, - SHIFT_LAYER_GROUP = BITS_FOR_Y + SHIFT_Y - }; - static_assert(SHIFT_LAYER_GROUP + BITS_FOR_LAYER_GROUP == sizeof(key_) * 8, "Bit field too small"); - - // the parity of x must be more significant than the layer but less significant than y. - // Thus basically every row is split in two: First the row containing all the odd x - // then the row containing all the even x. Since thus the least significant bit of x is - // not required for x ordering anymore it can be shifted out to the right. - const unsigned int x_parity = static_cast(loc.x) & 1; - key_ = (group_i << SHIFT_LAYER_GROUP) | (static_cast(loc.y + MAX_BORDER) << SHIFT_Y); - key_ |= (x_parity << SHIFT_X_PARITY); - key_ |= (static_cast(layer) << SHIFT_LAYER) | static_cast(loc.x + MAX_BORDER) / 2; -} - -void display::drawing_buffer_commit() -{ - // std::list::sort() is a stable sort - drawing_buffer_.sort(); - - SDL_Rect clip_rect = map_area(); - surface& screen = get_screen_surface(); - clip_rect_setter set_clip_rect(screen, &clip_rect); - - /* - * Info regarding the rendering algorithm. - * - * In order to render a hex properly it needs to be rendered per row. On - * this row several layers need to be drawn at the same time. Mainly the - * unit and the background terrain. This is needed since both can spill - * in the next hex. The foreground terrain needs to be drawn before to - * avoid decapitation a unit. - * - * This ended in the following priority order: - * layergroup > location > layer > 'blit_helper' > surface - */ - - for (const blit_helper &blit : drawing_buffer_) { - for (const surface& surf : blit.surf()) { - // Note that dstrect can be changed by sdl_blit - // and so a new instance should be initialized - // to pass to each call to sdl_blit. - SDL_Rect dstrect {blit.x(), blit.y(), 0, 0}; - SDL_Rect srcrect = blit.clip(); - SDL_Rect *srcrectArg = (srcrect.x | srcrect.y | srcrect.w | srcrect.h) - ? &srcrect : nullptr; - sdl_blit(surf, srcrectArg, screen, &dstrect); - //NOTE: the screen part should already be marked as 'to update' - } - } - drawing_buffer_clear(); -} - -void display::drawing_buffer_clear() -{ - drawing_buffer_.clear(); -} - -void display::toggle_benchmark() -{ - benchmark = !benchmark; -} - -void display::toggle_debug_foreground() -{ - debug_foreground = !debug_foreground; -} - -void display::flip() -{ - if(video().faked()) { - return; - } - - surface& frameBuffer = video().getSurface(); - - font::draw_floating_labels(frameBuffer); - events::raise_volatile_draw_event(); - - video().flip(); - - events::raise_volatile_undraw_event(); - font::undraw_floating_labels(frameBuffer); -} - -// frametime is in milliseconds -static unsigned calculate_fps(unsigned frametime) -{ - return frametime != 0u ? 1000u / frametime : 999u; -} - -void display::update_display() -{ - if (screen_.update_locked()) { - return; - } - - if(preferences::show_fps() || benchmark) { - static int frames = 0; - ++frames; - const int sample_freq = 10; - if(frames == sample_freq) { - const auto minmax_it = std::minmax_element(frametimes_.begin(), frametimes_.end()); - const unsigned render_avg = std::accumulate(frametimes_.begin(), frametimes_.end(), 0) / frametimes_.size(); - const int avg_fps = calculate_fps(render_avg); - const int max_fps = calculate_fps(*minmax_it.first); - const int min_fps = calculate_fps(*minmax_it.second); - frames = 0; - - if(fps_handle_ != 0) { - font::remove_floating_label(fps_handle_); - fps_handle_ = 0; - } - std::ostringstream stream; - stream << " min/avg/max/act\n"; - stream << "FPS: " << std::setfill(' ') << std::setw(3) << min_fps << '/'<< std::setw(3) << avg_fps << '/' << std::setw(3) << max_fps << '/' << std::setw(3) << fps_actual_ << "\n"; - stream << "Time: " << std::setfill(' ') << std::setw(3) << *minmax_it.first << '/' << std::setw(3) << render_avg << '/' << std::setw(3) << *minmax_it.second << " ms\n"; - if (game_config::debug) { - stream << "\nhex: " << drawn_hexes_*1.0/sample_freq; - if (drawn_hexes_ != invalidated_hexes_) - stream << " (" << (invalidated_hexes_-drawn_hexes_)*1.0/sample_freq << ")"; - } - drawn_hexes_ = 0; - invalidated_hexes_ = 0; - - font::floating_label flabel(stream.str()); - flabel.set_font_size(12); - flabel.set_color(benchmark ? font::BAD_COLOR : font::NORMAL_COLOR); - flabel.set_position(10, 100); - flabel.set_alignment(font::LEFT_ALIGN); - - fps_handle_ = font::add_floating_label(flabel); - } - } else if(fps_handle_ != 0) { - font::remove_floating_label(fps_handle_); - fps_handle_ = 0; - drawn_hexes_ = 0; - invalidated_hexes_ = 0; - } - - flip(); -} -static void draw_panel(CVideo &video, const theme::panel& panel, std::vector>& /*buttons*/) -{ - //log_scope("draw panel"); - DBG_DP << "drawing panel " << panel.get_id() << "\n"; - - surface surf(image::get_image(panel.image())); - - const SDL_Rect screen = video.screen_area(); - SDL_Rect& loc = panel.location(screen); - - DBG_DP << "panel location: x=" << loc.x << ", y=" << loc.y - << ", w=" << loc.w << ", h=" << loc.h << "\n"; - - if(!surf.null()) { - if(surf->w != loc.w || surf->h != loc.h) { - surf.assign(tile_surface(surf,loc.w,loc.h)); - } - video.blit_surface(loc.x, loc.y, surf); - } -} - -static void draw_label(CVideo& video, surface target, const theme::label& label) -{ - //log_scope("draw label"); - - const color_t& RGB = label.font_rgb(); - - std::string c_start="<"; - std::string c_sep=","; - std::string c_end=">"; - std::stringstream color; - color<< c_start << RGB.r << c_sep << RGB.g << c_sep << RGB.b << c_end; - std::string text = label.text(); - - if(label.font_rgb_set()) { - color<w > loc.w || surf->h > loc.h) { - surf.assign(scale_surface(surf,loc.w,loc.h)); - } - - sdl_blit(surf,nullptr,target,&loc); - } - - if(text.empty() == false) { - tooltips::add_tooltip(loc,text); - } - } else if(text.empty() == false) { - font::draw_text(&video,loc,label.font_size(),font::NORMAL_COLOR,text,loc.x,loc.y); - } - -} - -void display::draw_all_panels() -{ - surface& screen(screen_.getSurface()); - - /* - * The minimap is also a panel, force it to update its contents. - * This is required when the size of the minimap has been modified. - */ - recalculate_minimap(); - - for(const auto& panel : theme_.panels()) { - draw_panel(video(), panel, menu_buttons_); - } - - for(const auto& label : theme_.labels()) { - draw_label(video(), screen, label); - } - - render_buttons(); -} - -static void draw_background(surface screen, const SDL_Rect& area, const std::string& image) -{ // No background image, just fill in black. if(image.empty()) { sdl::fill_rectangle(area, color_t(0, 0, 0)); return; } - const surface background(image::get_image(image)); + const texture background(image::get_texture(image)); if(background.null()) { return; } - const unsigned int width = background->w; - const unsigned int height = background->h; - - const unsigned int w_count = static_cast(std::ceil(static_cast(area.w) / static_cast(width))); - const unsigned int h_count = static_cast(std::ceil(static_cast(area.h) / static_cast(height))); - - for(unsigned int w = 0, w_off = area.x; w < w_count; ++w, w_off += width) { - for(unsigned int h = 0, h_off = area.y; h < h_count; ++h, h_off += height) { - SDL_Rect clip = sdl::create_rect(w_off, h_off, 0, 0); - sdl_blit(background, nullptr, screen, &clip); - } - } + // TODO: should probably tile this as before. + video_.render_copy(background, nullptr, &area); } -void display::draw_text_in_hex(const map_location& loc, - const drawing_layer layer, +int display::draw_text_in_hex(const map_location& loc, const std::string& text, std::size_t font_size, color_t color, + int fl_label_id, double x_in_hex, double y_in_hex) { - if (text.empty()) return; - - const std::size_t font_sz = static_cast(font_size * get_zoom_factor()); - - surface text_surf = font::get_rendered_text(text, font_sz, color); - surface back_surf = font::get_rendered_text(text, font_sz, font::BLACK_COLOR); - const int x = get_location_x(loc) - text_surf->w/2 - + static_cast(x_in_hex* hex_size()); - const int y = get_location_y(loc) - text_surf->h/2 - + static_cast(y_in_hex* hex_size()); - for (int dy=-1; dy <= 1; ++dy) { - for (int dx=-1; dx <= 1; ++dx) { - if (dx!=0 || dy!=0) { - drawing_buffer_add(layer, loc, x + dx, y + dy, back_surf); - } - } - } - drawing_buffer_add(layer, loc, x, y, text_surf); -} - -//TODO: convert this to use sdl::ttexture -void display::render_image(int x, int y, const display::drawing_layer drawing_layer, - const map_location& loc, surface image, - bool hreverse, bool greyscale, fixed_t alpha, - color_t blendto, double blend_ratio, double submerged, bool vreverse) -{ - if (image==nullptr) - return; - - SDL_Rect image_rect {x, y, image->w, image->h}; - SDL_Rect clip_rect = map_area(); - if (!sdl::rects_overlap(image_rect, clip_rect)) - return; - - surface surf(image); - - if(hreverse) { - surf = image::reverse_image(surf); - } - if(vreverse) { - surf = flop_surface(surf); + if(text.empty()) { + return fl_label_id; } - if(greyscale) { - surf = greyscale_image(surf); - } + const std::size_t font_sz = static_cast(font_size * get_zoom_factor()); - if(blend_ratio != 0) { - surf = blend_surface(surf, blend_ratio, blendto); - } - if(alpha > ftofxp(1.0)) { - surf = brighten_image(surf, alpha); - //} else if(alpha != 1.0 && blendto != 0) { - // surf.assign(blend_surface(surf,1.0-alpha,blendto)); - } else if(alpha != ftofxp(1.0)) { - surface temp = make_neutral_surface(surf); - adjust_surface_alpha(temp, alpha); - surf = temp; - } + const int x = get_location_x(loc) /*- text_surf->w / 2*/ + static_cast(x_in_hex * hex_size()); + const int y = get_location_y(loc) /*- text_surf->h / 2*/ + static_cast(y_in_hex * hex_size()); - if(surf == nullptr) { - ERR_DP << "surface lost..." << std::endl; - return; + // We were given a label id, remove it. + if(fl_label_id != 0) { + font::remove_floating_label(fl_label_id); } - if(submerged > 0.0) { - // divide the surface into 2 parts - const int submerge_height = std::max(0, surf->h*(1.0-submerged)); - const int depth = surf->h - submerge_height; - SDL_Rect srcrect {0, 0, surf->w, submerge_height}; - drawing_buffer_add(drawing_layer, loc, x, y, surf, srcrect); - - if(submerge_height != surf->h) { - //the lower part will be transparent - float alpha_base = 0.3f; // 30% alpha at surface of water - float alpha_delta = 0.015f; // lose 1.5% per pixel depth - alpha_delta *= zoom_ / DefaultZoom; // adjust with zoom - surf = submerge_alpha(surf, depth, alpha_base, alpha_delta); - - srcrect.y = submerge_height; - srcrect.h = surf->h-submerge_height; - y += submerge_height; + font::floating_label flabel(text); + flabel.set_font_size(font_sz); + flabel.set_color(color); + flabel.set_position(x, y); + flabel.set_alignment(font::CENTER_ALIGN); + flabel.set_scroll_mode(font::ANCHOR_LABEL_MAP); - drawing_buffer_add(drawing_layer, loc, x, y, surf, srcrect); - } - } else { - // simple blit - drawing_buffer_add(drawing_layer, loc, x, y, surf); - } + return font::add_floating_label(flabel); } + void display::select_hex(map_location hex) { - invalidate(selectedHex_); selectedHex_ = hex; - invalidate(selectedHex_); recalculate_minimap(); } void display::highlight_hex(map_location hex) { - invalidate(mouseoverHex_); mouseoverHex_ = hex; - invalidate(mouseoverHex_); } void display::set_diagnostic(const std::string& msg) @@ -1617,142 +954,16 @@ void display::set_diagnostic(const std::string& msg) } } -void display::draw_init() -{ - if (get_map().empty()) { - return; - } - - if(benchmark) { - invalidateAll_ = true; - } - - if(!panelsDrawn_) { - draw_all_panels(); - panelsDrawn_ = true; - } - - if(redraw_background_) { - // Full redraw of the background - const SDL_Rect clip_rect = map_outside_area(); - const surface& screen = get_screen_surface(); - clip_rect_setter set_clip_rect(screen, &clip_rect); - SDL_FillRect(screen, &clip_rect, 0x00000000); - draw_background(screen, clip_rect, theme_.border().background_image); - redraw_background_ = false; - - // Force a full map redraw - invalidateAll_ = true; - } - - if(invalidateAll_) { - DBG_DP << "draw() with invalidateAll\n"; - - // toggle invalidateAll_ first to allow regular invalidations - invalidateAll_ = false; - invalidate_locations_in_rect(map_area()); - - redrawMinimap_ = true; - } -} - -void display::draw_wrap(bool update, bool force) -{ - static int time_between_draws = preferences::draw_delay(); - if(time_between_draws < 0) { - time_between_draws = 1000 / screen_.current_refresh_rate(); - } - - if(redrawMinimap_) { - redrawMinimap_ = false; - draw_minimap(); - } - - if(update) { - update_display(); - - frametimes_.push_back(SDL_GetTicks() - last_frame_finished_); - fps_counter_++; - using std::chrono::duration_cast; - using std::chrono::seconds; - using std::chrono::steady_clock; - const seconds current_second = duration_cast(steady_clock::now().time_since_epoch()); - if(current_second != fps_start_) { - fps_start_ = current_second; - fps_actual_ = fps_counter_; - fps_counter_ = 0; - } - int longest_frame = *std::max_element(frametimes_.begin(), frametimes_.end()); - int wait_time = time_between_draws - longest_frame; - - if(!force && !benchmark && wait_time > 0) { - // If it's not time yet to draw, delay until it is - SDL_Delay(wait_time); - } - - last_frame_finished_ = SDL_GetTicks(); - } -} - -const theme::action* display::action_pressed() -{ - for(auto i = action_buttons_.begin(); i != action_buttons_.end(); ++i) { - if((*i)->pressed()) { - const std::size_t index = std::distance(action_buttons_.begin(), i); - if(index >= theme_.actions().size()) { - assert(false); - return nullptr; - } - return &theme_.actions()[index]; - } - } - - return nullptr; -} - -const theme::menu* display::menu_pressed() -{ - for(auto i = menu_buttons_.begin(); i != menu_buttons_.end(); ++i) { - if((*i)->pressed()) { - const std::size_t index = std::distance(menu_buttons_.begin(), i); - if(index >= theme_.menus().size()) { - assert(false); - return nullptr; - } - return theme_.get_menu_item((*i)->id()); - } - } - - return nullptr; -} - -void display::enable_menu(const std::string& item, bool enable) -{ - for(auto menu = theme_.menus().begin(); menu != theme_.menus().end(); ++menu) { - - const auto hasitem = std::find_if(menu->items().begin(), menu->items().end(), - [&item](const config& c) { return c["id"].str() == item; } - ); - - if(hasitem != menu->items().end()) { - const std::size_t index = std::distance(theme_.menus().begin(), menu); - if(index >= menu_buttons_.size()) { - continue; - } - menu_buttons_[index]->enable(enable); - } - } -} - void display::announce(const std::string& message, const color_t& color, const announce_options& options) { if(options.discard_previous) { font::remove_floating_label(prevLabel); } + font::floating_label flabel(message); flabel.set_font_size(font::SIZE_XLARGE); flabel.set_color(color); - flabel.set_position(map_outside_area().w/2, map_outside_area().h/3); + flabel.set_position(map_outside_area().w / 2, map_outside_area().h / 3); flabel.set_lifetime(options.lifetime); flabel.set_clip_rect(map_outside_area()); @@ -1776,11 +987,8 @@ void display::draw_minimap() } } - const surface& screen(screen_.getSurface()); - clip_rect_setter clip_setter(screen, &area); - color_t back_color {31,31,23,SDL_ALPHA_OPAQUE}; - draw_centered_on_background(minimap_, area, back_color, screen); + draw_centered_on_background(minimap_, area, back_color); //update the minimap location for mouse and units functions minimap_location_.x = area.x + (area.w - minimap_->w) / 2; @@ -1792,17 +1000,19 @@ void display::draw_minimap() // calculate the visible portion of the map: // scaling between minimap and full map images - double xscaling = 1.0*minimap_->w / (get_map().w()*hex_width()); - double yscaling = 1.0*minimap_->h / (get_map().h()*hex_size()); + double xscaling = 1.0 * minimap_->w / (get_map().w() * hex_width()); + double yscaling = 1.0 *minimap_->h / (get_map().h() * hex_size()); // we need to shift with the border size // and the 0.25 from the minimap balanced drawing // and the possible difference between real map and outside off-map SDL_Rect map_rect = map_area(); SDL_Rect map_out_rect = map_outside_area(); + double border = theme_.border().size; - double shift_x = - border*hex_width() - (map_out_rect.w - map_rect.w) / 2; - double shift_y = - (border+0.25)*hex_size() - (map_out_rect.h - map_rect.h) / 2; + + double shift_x = - border * hex_width() - (map_out_rect.w - map_rect.w) / 2; + double shift_y = - (border + 0.25) * hex_size() - (map_out_rect.h - map_rect.h) / 2; int view_x = static_cast((xpos_ + shift_x) * xscaling); int view_y = static_cast((ypos_ + shift_y) * yscaling); @@ -1829,7 +1039,7 @@ void display::draw_minimap_units() for(const auto& u : dc_->units()) { if (fogged(u.get_location()) || (dc_->teams()[currentTeam_].is_enemy(u.side()) && - u.invisible(u.get_location())) || + u.invisible(u.get_location(), *dc_)) || u.get_hidden()) { continue; } @@ -1864,10 +1074,10 @@ void display::draw_minimap_units() double u_h = yscaling; SDL_Rect r { - minimap_location_.x + int(std::round(u_x)) - , minimap_location_.y + int(std::round(u_y)) - , int(std::round(u_w)) - , int(std::round(u_h)) + minimap_location_.x + round_double(u_x) + , minimap_location_.y + round_double(u_y) + , round_double(u_w) + , round_double(u_h) }; sdl::fill_rectangle(r, col); @@ -1915,58 +1125,12 @@ bool display::scroll(int xmove, int ymove, bool force) labels().recalculate_shroud(); - // - // NOTE: the next three blocks can be removed once we switch to accelerated rendering. - // - - if(!screen_.update_locked()) { - surface& screen(screen_.getSurface()); - - SDL_Rect dstrect = map_area(); - dstrect.x += diff_x; - dstrect.y += diff_y; - dstrect = sdl::intersect_rects(dstrect, map_area()); + scroll_event_.notify_observers(); - SDL_Rect srcrect = dstrect; - srcrect.x -= diff_x; - srcrect.y -= diff_y; + redrawMinimap_ = true; - // This is a workaround for a SDL2 bug when blitting on overlapping surfaces. The bug - // only strikes during scrolling, but will then duplicate textures across the entire map. - surface screen_copy = make_neutral_surface(screen); - - SDL_SetSurfaceBlendMode(screen_copy, SDL_BLENDMODE_NONE); - SDL_BlitSurface(screen_copy, &srcrect, screen, &dstrect); - } - - if(diff_y != 0) { - SDL_Rect r = map_area(); - - if(diff_y < 0) { - r.y = r.y + r.h + diff_y; - } - - r.h = std::abs(diff_y); - invalidate_locations_in_rect(r); - } - - if(diff_x != 0) { - SDL_Rect r = map_area(); - - if(diff_x < 0) { - r.x = r.x + r.w + diff_x; - } - - r.w = std::abs(diff_x); - invalidate_locations_in_rect(r); - } - - scroll_event_.notify_observers(); - - redrawMinimap_ = true; - - return true; -} + return true; +} bool display::zoom_at_max() { @@ -2022,10 +1186,10 @@ bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_in const SDL_Rect& area = map_area(); // Turn the zoom factor to a double in order to avoid rounding errors. - double zoom_factor = static_cast(new_zoom) / static_cast(zoom_); + double zoom_factor = double(new_zoom) / double(zoom_); - xpos_ = std::round(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2)); - ypos_ = std::round(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2)); + xpos_ = round_double(((xpos_ + area.w / 2) * zoom_factor) - (area.w / 2)); + ypos_ = round_double(((ypos_ + area.h / 2) * zoom_factor) - (area.h / 2)); zoom_ = new_zoom; bounds_check_position(xpos_, ypos_); @@ -2036,19 +1200,17 @@ bool display::set_zoom(unsigned int amount, const bool validate_value_and_set_in image::set_zoom(zoom_); labels().recalculate_labels(); - redraw_background_ = true; - invalidate_all(); // Forces a redraw after zooming. // This prevents some graphic glitches from occurring. - draw(); + // draw(); return true; } void display::set_default_zoom() { - if (zoom_ != DefaultZoom) { + if(zoom_ != DefaultZoom) { last_zoom_ = zoom_; set_zoom(DefaultZoom); } else { @@ -2069,21 +1231,24 @@ bool display::tile_nearly_on_screen(const map_location& loc) const { int x = get_location_x(loc); int y = get_location_y(loc); - const SDL_Rect &area = map_area(); + const SDL_Rect& area = map_area(); int hw = hex_width(), hs = hex_size(); - return x + hs >= area.x - hw && x < area.x + area.w + hw && - y + hs >= area.y - hs && y < area.y + area.h + hs; + return x + hs >= area.x - hw && x < area.x + area.w + hw && y + hs >= area.y - hs && y < area.y + area.h + hs; } void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_type, bool force) { - if(!force && (view_locked_ || !preferences::scroll_to_action())) return; - if(screen_.update_locked()) { + if(!force && (view_locked_ || !preferences::scroll_to_action())) { + return; + } + + if(video_.update_locked()) { return; } + const SDL_Rect area = map_area(); - const int xmove_expected = screenxpos - (area.x + area.w/2); - const int ymove_expected = screenypos - (area.y + area.h/2); + const int xmove_expected = screenxpos - (area.x + area.w / 2); + const int ymove_expected = screenypos - (area.y + area.h / 2); int xpos = xpos_ + xmove_expected; int ypos = ypos_ + ymove_expected; @@ -2092,8 +1257,8 @@ void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_ty int ymove = ypos - ypos_; if(scroll_type == WARP || scroll_type == ONSCREEN_WARP || turbo_speed() > 2.0 || preferences::scroll_speed() > 99) { - scroll(xmove,ymove,true); - draw(); + scroll(xmove, ymove, true); + // draw(); return; } @@ -2108,12 +1273,12 @@ void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_ty int t_prev = SDL_GetTicks(); double velocity = 0.0; - while (dist_moved < dist_total) { - events::pump(); + while(dist_moved < dist_total) { + events::run_event_loop(); int t = SDL_GetTicks(); double dt = (t - t_prev) / 1000.0; - if (dt > 0.200) { + if(dt > 0.200) { // Do not skip too many frames on slow PCs dt = 0.200; } @@ -2129,28 +1294,35 @@ void display::scroll_to_xy(int screenxpos, int screenypos, SCROLL_TYPE scroll_ty // If we started to decelerate now, where would we stop? double stop_time = velocity / decel; - double dist_stop = dist_moved + velocity*stop_time - 0.5*decel*stop_time*stop_time; - if (dist_stop > dist_total || velocity > velocity_max) { + double dist_stop = dist_moved + velocity * stop_time - 0.5 * decel * stop_time * stop_time; + + if(dist_stop > dist_total || velocity > velocity_max) { velocity -= decel * dt; - if (velocity < 1.0) velocity = 1.0; + if(velocity < 1.0) { + velocity = 1.0; + } } else { velocity += accel * dt; - if (velocity > velocity_max) velocity = velocity_max; + if(velocity > velocity_max) { + velocity = velocity_max; + } } dist_moved += velocity * dt; - if (dist_moved > dist_total) dist_moved = dist_total; + if(dist_moved > dist_total) { + dist_moved = dist_total; + } - int x_new = std::round(xmove * dist_moved / dist_total); - int y_new = std::round(ymove * dist_moved / dist_total); + int x_new = round_double(xmove * dist_moved / dist_total); + int y_new = round_double(ymove * dist_moved / dist_total); int dx = x_new - x_old; int dy = y_new - y_old; - scroll(dx,dy,true); + scroll(dx, dy, true); x_old += dx; y_old += dy; - draw(); + // draw(); } } @@ -2163,23 +1335,30 @@ void display::scroll_to_tile(const map_location& loc, SCROLL_TYPE scroll_type, b std::vector locs; locs.push_back(loc); - scroll_to_tiles(locs, scroll_type, check_fogged,false,0.0,force); + + scroll_to_tiles(locs, scroll_type, check_fogged, false, 0.0, force); } -void display::scroll_to_tiles(map_location loc1, map_location loc2, - SCROLL_TYPE scroll_type, bool check_fogged, - double add_spacing, bool force) +void display::scroll_to_tiles(map_location loc1, + map_location loc2, + SCROLL_TYPE scroll_type, + bool check_fogged, + double add_spacing, + bool force) { std::vector locs; locs.push_back(loc1); locs.push_back(loc2); - scroll_to_tiles(locs, scroll_type, check_fogged, false, add_spacing,force); + scroll_to_tiles(locs, scroll_type, check_fogged, false, add_spacing, force); } -void display::scroll_to_tiles(const std::vector::const_iterator & begin, - const std::vector::const_iterator & end, - SCROLL_TYPE scroll_type, bool check_fogged, - bool only_if_possible, double add_spacing, bool force) +void display::scroll_to_tiles(const std::vector::const_iterator& begin, + const std::vector::const_iterator& end, + SCROLL_TYPE scroll_type, + bool check_fogged, + bool only_if_possible, + double add_spacing, + bool force) { // basically we calculate the min/max coordinates we want to have on-screen int minx = 0; @@ -2188,69 +1367,83 @@ void display::scroll_to_tiles(const std::vector::const_iterator & int maxy = 0; bool valid = false; - for(std::vector::const_iterator itor = begin; itor != end ; ++itor) { - if(get_map().on_board(*itor) == false) continue; - if(check_fogged && fogged(*itor)) continue; + for(std::vector::const_iterator itor = begin; itor != end; ++itor) { + if(get_map().on_board(*itor) == false) + continue; + if(check_fogged && fogged(*itor)) + continue; int x = get_location_x(*itor); int y = get_location_y(*itor); - if (!valid) { + if(!valid) { minx = x; maxx = x; miny = y; maxy = y; + valid = true; } else { - int minx_new = std::min(minx,x); - int miny_new = std::min(miny,y); - int maxx_new = std::max(maxx,x); - int maxy_new = std::max(maxy,y); + int minx_new = std::min(minx, x); + int miny_new = std::min(miny, y); + int maxx_new = std::max(maxx, x); + int maxy_new = std::max(maxy, y); + SDL_Rect r = map_area(); r.x = minx_new; r.y = miny_new; + if(outside_area(r, maxx_new, maxy_new)) { // we cannot fit all locations to the screen - if (only_if_possible) return; + if(only_if_possible) { + return; + } + break; } + minx = minx_new; miny = miny_new; maxx = maxx_new; maxy = maxy_new; } } - //if everything is fogged or the location list is empty - if(!valid) return; - if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) { + // if everything is fogged or the location list is empty + if(!valid) { + return; + } + + if(scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) { SDL_Rect r = map_area(); - int spacing = std::round(add_spacing * hex_size()); + int spacing = round_double(add_spacing * hex_size()); r.x += spacing; r.y += spacing; - r.w -= 2*spacing; - r.h -= 2*spacing; - if (!outside_area(r, minx,miny) && !outside_area(r, maxx,maxy)) { + r.w -= 2 * spacing; + r.h -= 2 * spacing; + + if(!outside_area(r, minx, miny) && !outside_area(r, maxx, maxy)) { return; } } // let's do "normal" rectangle math from now on - SDL_Rect locs_bbox; - locs_bbox.x = minx; - locs_bbox.y = miny; - locs_bbox.w = maxx - minx + hex_size(); - locs_bbox.h = maxy - miny + hex_size(); + SDL_Rect locs_bbox { + minx, + miny, + maxx - minx + hex_size(), + maxy - miny + hex_size() + }; // target the center - int target_x = locs_bbox.x + locs_bbox.w/2; - int target_y = locs_bbox.y + locs_bbox.h/2; + int target_x = locs_bbox.x + locs_bbox.w / 2; + int target_y = locs_bbox.y + locs_bbox.h / 2; - if (scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) { + if(scroll_type == ONSCREEN || scroll_type == ONSCREEN_WARP) { // when doing an ONSCREEN scroll we do not center the target unless needed SDL_Rect r = map_area(); - int map_center_x = r.x + r.w/2; - int map_center_y = r.y + r.h/2; + int map_center_x = r.x + r.w / 2; + int map_center_y = r.y + r.h / 2; int h = r.h; int w = r.w; @@ -2268,8 +1461,8 @@ void display::scroll_to_tiles(const std::vector::const_iterator & if (w < 1) w = 1; if (h < 1) h = 1; - r.x = target_x - w/2; - r.y = target_y - h/2; + r.x = target_x - w / 2; + r.y = target_y - h / 2; r.w = w; r.h = h; @@ -2277,47 +1470,32 @@ void display::scroll_to_tiles(const std::vector::const_iterator & // we take the one with the minimum distance to map_center // which will always be at the border of r - if (map_center_x < r.x) { + if(map_center_x < r.x) { target_x = r.x; - target_y = map_center_y; - if (target_y < r.y) target_y = r.y; - if (target_y > r.y+r.h-1) target_y = r.y+r.h-1; - } else if (map_center_x > r.x+r.w-1) { - target_x = r.x+r.w-1; - target_y = map_center_y; - if (target_y < r.y) target_y = r.y; - if (target_y >= r.y+r.h) target_y = r.y+r.h-1; - } else if (map_center_y < r.y) { + target_y = utils::clamp(map_center_y, r.y, r.y + r.h - 1); + } else if(map_center_x > r.x + r.w - 1) { + target_x = r.x + r.w - 1; + target_y = utils::clamp(map_center_y, r.y, r.y + r.h - 1); + } else if(map_center_y < r.y) { target_y = r.y; - target_x = map_center_x; - if (target_x < r.x) target_x = r.x; - if (target_x > r.x+r.w-1) target_x = r.x+r.w-1; - } else if (map_center_y > r.y+r.h-1) { - target_y = r.y+r.h-1; - target_x = map_center_x; - if (target_x < r.x) target_x = r.x; - if (target_x > r.x+r.w-1) target_x = r.x+r.w-1; + target_x = utils::clamp(map_center_x, r.x, r.x + r.w - 1); + } else if(map_center_y > r.y + r.h - 1) { + target_y = r.y + r.h - 1; + target_x = utils::clamp(map_center_x, r.x, r.x + r.w - 1); } else { ERR_DP << "Bug in the scrolling code? Looks like we would not need to scroll after all..." << std::endl; // keep the target at the center } } - scroll_to_xy(target_x, target_y,scroll_type,force); + scroll_to_xy(target_x, target_y, scroll_type, force); } - void display::bounds_check_position() { const unsigned int orig_zoom = zoom_; - if(zoom_ < MinZoom) { - zoom_ = MinZoom; - } - - if(zoom_ > MaxZoom) { - zoom_ = MaxZoom; - } + zoom_ = utils::clamp(zoom_, MinZoom, MaxZoom); bounds_check_position(xpos_, ypos_); @@ -2331,24 +1509,11 @@ void display::bounds_check_position(int& xpos, int& ypos) const const int tile_width = hex_width(); // Adjust for the border 2 times - const int xend = static_cast(tile_width * (get_map().w() + 2 * theme_.border().size) + tile_width/3); - const int yend = static_cast(zoom_ * (get_map().h() + 2 * theme_.border().size) + zoom_/2); - - if(xpos > xend - map_area().w) { - xpos = xend - map_area().w; - } + const int xend = static_cast(tile_width * (get_map().w() + 2 * theme_.border().size) + tile_width / 3); + const int yend = static_cast(zoom_ * (get_map().h() + 2 * theme_.border().size) + zoom_ / 2); - if(ypos > yend - map_area().h) { - ypos = yend - map_area().h; - } - - if(xpos < 0) { - xpos = 0; - } - - if(ypos < 0) { - ypos = 0; - } + xpos = utils::clamp(xpos, 0, xend - map_area().w); + ypos = utils::clamp(ypos, 0, yend - map_area().h); } double display::turbo_speed() const @@ -2358,296 +1523,500 @@ double display::turbo_speed() const res = !res; } - res |= screen_.faked(); - if (res) + res |= video_.faked(); + if(res) { return turbo_speed_; - else - return 1.0; + } + + return 1.0; } void display::set_idle_anim_rate(int rate) { - idle_anim_rate_ = std::pow(2.0, -rate/10.0); + idle_anim_rate_ = std::pow(2.0, -rate / 10.0); } -void display::redraw_everything() +map_labels& display::labels() { - if(screen_.update_locked()) - return; + return *map_labels_; +} - invalidateGameStatus_ = true; +const map_labels& display::labels() const +{ + return *map_labels_; +} - reportRects_.clear(); - reportSurfaces_.clear(); - reports_.clear(); +const SDL_Rect display::get_clip_rect() +{ + return map_area(); +} - bounds_check_position(); +image::TYPE display::get_image_type(const map_location& /*loc*/) +{ + return image::TOD_COLORED; +} + +void display::invalidate_animations() +{ + new_animation_frame(); - tooltips::clear_tooltips(); + animate_map_ = preferences::animate_map(); + if(animate_map_) { + for(const map_location& loc : get_visible_hexes()) { + if(shrouded(loc)) { + continue; + } - theme_.set_resolution(screen_.screen_area()); + builder_->update_animation(loc); + } + } - if(!menu_buttons_.empty() || !action_buttons_.empty()) { - create_buttons(); +#ifndef _OPENMP + for(const unit& u : dc_->units()) { + u.anim_comp().refresh(); } - if(resources::controller) { - hotkey::command_executor* command_executor = resources::controller->get_hotkey_command_executor(); - if(command_executor != nullptr) { - // This function adds button overlays, - // it needs to be run after recreating the buttons. - command_executor->set_button_state(); - } + for(const unit* u : *fake_unit_man_) { + u->anim_comp().refresh(); + } +#else + std::vector open_mp_list; + for(const unit& u : dc_->units()) { + open_mp_list.push_back(&u); } - panelsDrawn_ = false; - if (!gui::in_dialog()) { - labels().recalculate_labels(); + // Note that it is an important assumption of the system that the fake units are added to the list + // after the real units, so that e.g. whiteboard planned moves are drawn over the real units. + for(const unit* u : *fake_unit_man_) { + open_mp_list.push_back(u); } - redraw_background_ = true; + // openMP can't iterate over std::size_t + const int omp_iterations = open_mp_list.size(); + // #pragma omp parallel for shared(open_mp_list) - for(std::function f : redraw_observers_) { - f(*this); + // This loop must not be parallelized. Refresh is not thread-safe; for one, unit filters are not thread safe. + // This is because, adding new "scoped" wml variables is not thread safe. Lua itself is not thread safe. + // When this loop was parallelized, assertion failures were reported in windows openmp builds. + for(int i = 0; i < omp_iterations; i++) { + open_mp_list[i]->anim_comp().refresh(); } +#endif +} - int ticks1 = SDL_GetTicks(); - invalidate_all(); - int ticks2 = SDL_GetTicks(); - draw(true,true); - int ticks3 = SDL_GetTicks(); - LOG_DP << "invalidate and draw: " << (ticks3 - ticks2) << " and " << (ticks2 - ticks1) << "\n"; +void display::reset_standing_animations() +{ + for(const unit& u : dc_->units()) { + u.anim_comp().set_standing(); + } +} - complete_redraw_event_.notify_observers(); +void display::add_arrow(arrow& arrow) +{ + for(const map_location& loc : arrow.get_path()) { + arrows_map_[loc].push_back(&arrow); + } } -void display::add_redraw_observer(std::function f) +void display::remove_arrow(arrow& arrow) { - redraw_observers_.push_back(f); + for(const map_location& loc : arrow.get_path()) { + arrows_map_[loc].remove(&arrow); + } } -void display::clear_redraw_observers() +void display::update_arrow(arrow& arrow) { - redraw_observers_.clear(); + for(const map_location& loc : arrow.get_previous_path()) { + arrows_map_[loc].remove(&arrow); + } + + for(const map_location& loc : arrow.get_path()) { + arrows_map_[loc].push_back(&arrow); + } +} + +map_location display::get_middle_location() const +{ + const SDL_Rect& rect = map_area(); + return pixel_position_to_hex(xpos_ + rect.x + rect.w / 2, ypos_ + rect.y + rect.h / 2); +} + +void display::write(config& cfg) const +{ + cfg["view_locked"] = view_locked_; + cfg["color_adjust_red"] = color_adjust_.r; + cfg["color_adjust_green"] = color_adjust_.g; + cfg["color_adjust_blue"] = color_adjust_.b; + + get_middle_location().write(cfg.add_child("location")); } -void display::draw() { - draw(true, false); +void display::read(const config& cfg) +{ + view_locked_ = cfg["view_locked"].to_bool(false); + color_adjust_.r = cfg["color_adjust_red"].to_int(0); + color_adjust_.g = cfg["color_adjust_green"].to_int(0); + color_adjust_.b = cfg["color_adjust_blue"].to_int(0); } -void display::draw(bool update) { - draw(update, false); +// +// NEW RENDERING CODE ========================================================================= +// + +void display::draw_hex_overlays() +{ + // DO NOTHING } +void display::draw_visible_hexes(const rect_of_hexes& visible_hexes, TERRAIN_TYPE layer) +{ + assert(builder_); + + drawn_hexes_ = 0; -void display::draw(bool update,bool force) { -// log_scope("display::draw"); + terrain_builder::TERRAIN_TYPE builder_terrain_type = layer == FOREGROUND + ? terrain_builder::FOREGROUND + : terrain_builder::BACKGROUND; - if (screen_.update_locked()) { - return; - } + for(const map_location& loc : visible_hexes) { + if(shrouded(loc)) { + continue; + } - if (dirty_) { - flip_locker flip_lock(screen_); - dirty_ = false; - redraw_everything(); - return; - } + //image::TYPE image_type = get_image_type(loc); + const time_of_day& tod = get_time_of_day(loc); - // Trigger cache rebuild when preference gets changed - if (animate_water_ != preferences::animate_water()) { - animate_water_ = preferences::animate_water(); - builder_->rebuild_cache_all(); - } + // Get the image list for this location. + const terrain_builder::imagelist* const terrains = builder_->get_terrain_at(loc, tod.id, builder_terrain_type); + if(!terrains) { + continue; + } - set_scontext_unsynced leave_synced_context; +#if 0 + image::light_string lt; - draw_init(); - pre_draw(); - // invalidate all that needs to be invalidated - invalidate_animations(); + const time_of_day& tod = get_time_of_day(loc); + + //get all the light transitions + map_location adjs[6]; + get_adjacent_tiles(loc,adjs); + + for(int d=0; d<6; ++d){ + /* concave + _____ + / \ + / atod1 \_____ + \ !tod / \ + \_____/ atod2 \ + / \__\ !tod / + / \_____/ + \ tod / + \_____/*/ + + const time_of_day& atod1 = get_time_of_day(adjs[d]); + const time_of_day& atod2 = get_time_of_day(adjs[(d + 1) % 6]); - if(!get_map().empty()) { - //int simulate_delay = 0; + if(atod1.color == tod.color || atod2.color == tod.color || atod1.color != atod2.color) + continue; + + if(lt.empty()) { + //color the full hex before adding transitions + tod_color col = tod.color + color_adjust_; + lt = image::get_light_string(0, col.r, col.g, col.b); + } + + // add the directional transitions + tod_color acol = atod1.color + color_adjust_; + lt += image::get_light_string(d + 1, acol.r, acol.g, acol.b); + } + for(int d=0; d<6; ++d){ + /* convex 1 + _____ + / \ + / atod1 \_____ + \ !tod / \ + \_____/ atod2 \ + / \__\ tod / + / \_____/ + \ tod / + \_____/*/ + + const time_of_day& atod1 = get_time_of_day(adjs[d]); + const time_of_day& atod2 = get_time_of_day(adjs[(d + 1) % 6]); + + if(atod1.color == tod.color || atod1.color == atod2.color) + continue; + + if(lt.empty()) { + //color the full hex before adding transitions + tod_color col = tod.color + color_adjust_; + lt = image::get_light_string(0, col.r, col.g, col.b); + } - /* - * draw_invalidated() also invalidates the halos, so also needs to be - * ran if invalidated_.empty() == true. - */ - if(!invalidated_.empty() || preferences::show_haloes()) { - draw_invalidated(); - invalidated_.clear(); + // add the directional transitions + tod_color acol = atod1.color + color_adjust_; + lt += image::get_light_string(d + 7, acol.r, acol.g, acol.b); + } + for(int d=0; d<6; ++d){ + /* convex 2 + _____ + / \ + / atod1 \_____ + \ tod / \ + \_____/ atod2 \ + / \__\ !tod / + / \_____/ + \ tod / + \_____/*/ + + const time_of_day& atod1 = get_time_of_day(adjs[d]); + const time_of_day& atod2 = get_time_of_day(adjs[(d + 1) % 6]); + + if(atod2.color == tod.color || atod1.color == atod2.color) + continue; + + if(lt.empty()) { + //color the full hex before adding transitions + tod_color col = tod.color + color_adjust_; + lt = image::get_light_string(0, col.r, col.g, col.b); + } + + // add the directional transitions + tod_color acol = atod2.color + color_adjust_; + lt += image::get_light_string(d + 13, acol.r, acol.g, acol.b); } - drawing_buffer_commit(); - post_commit(); - draw_sidebar(); - // Simulate slow PC: - //SDL_Delay(2*simulate_delay + rand() % 20); - } - draw_wrap(update, force); - post_draw(); -} + if(lt.empty()){ + tod_color col = tod.color + color_adjust_; + if(!col.is_zero()){ + // no real lightmap needed but still color the hex + lt = image::get_light_string(-1, col.r, col.g, col.b); + } + } +#endif -map_labels& display::labels() -{ - return *map_labels_; -} + // Cache the offmap name. + // Since it is themeable it can change, so don't make it static. + //const std::string off_map_name = "terrain/" + theme_.border().tile_image; -const map_labels& display::labels() const -{ - return *map_labels_; + for(const auto& terrain : *terrains) { + const image::locator& image = animate_map_ + ? terrain.get_current_frame() + : terrain.get_first_frame(); + + // We prevent ToD coloring and brightening of off-map tiles, + // We need to test for the tile to be rendered and + // not the location, since the transitions are rendered + // over the offmap-terrain and these need a ToD coloring. + texture tex = image::get_texture(image, image::HEXED); // TODO: scaled to hex? + + //const bool off_map = (image.get_filename() == off_map_name || image.get_modifications().find("NO_TOD_SHIFT()") != std::string::npos); +#if 0 + if(off_map) { + surf = image::get_image(image, off_map ? image::SCALED_TO_HEX : image_type); + } else if(lt.empty()) { + surf = image::get_image(image, image::SCALED_TO_HEX); + } else { + surf = image::get_lighted_image(image, lt, image::SCALED_TO_HEX); + } +#endif + if(!tex.null()) { + render_scaled_to_zoom(tex, loc); + } + } + + ++drawn_hexes_; + } } -const SDL_Rect& display::get_clip_rect() +void display::draw_gamemap() { - return map_area(); -} + // Currenty visible hexes. + const rect_of_hexes& visible_hexes = get_visible_hexes(); + + // + // Background terrains + // + draw_visible_hexes(visible_hexes, BACKGROUND); -void display::draw_invalidated() { -// log_scope("display::draw_invalidated"); - SDL_Rect clip_rect = get_clip_rect(); - surface& screen = get_screen_surface(); - clip_rect_setter set_clip_rect(screen, &clip_rect); - for (const map_location& loc : invalidated_) { - int xpos = get_location_x(loc); - int ypos = get_location_y(loc); + // + // On-map overlays, such as [item]s. + // + for(const auto& overlay_record : *overlays_) { + const map_location& o_loc = overlay_record.first; - //const bool on_map = get_map().on_board(loc); - SDL_Rect hex_rect = sdl::create_rect(xpos, ypos, zoom_, zoom_); - if(!sdl::rects_overlap(hex_rect,clip_rect)) { + if(shrouded(o_loc)) { continue; } - draw_hex(loc); - drawn_hexes_+=1; - } - invalidated_hexes_ += invalidated_.size(); - if (dc_->teams().empty()) - { - // The unit drawer can't function without teams - return; - } + const overlay& item = overlay_record.second; + const std::string& current_team_name = dc_->teams()[viewing_team()].team_name(); - unit_drawer drawer = unit_drawer(*this); + if((item.team_name.empty() || item.team_name.find(current_team_name) != std::string::npos) && + (!fogged(o_loc) || item.visible_in_fog)) + { + const texture tex = item.image.find("~NO_TOD_SHIFT()") == std::string::npos + ? image::get_texture(item.image) // TODO + : image::get_texture(item.image); - for (const map_location& loc : invalidated_) { - unit_map::const_iterator u_it = dc_->units().find(loc); - exclusive_unit_draw_requests_t::iterator request = exclusive_unit_draw_requests_.find(loc); - if (u_it != dc_->units().end() - && (request == exclusive_unit_draw_requests_.end() || request->second == u_it->id())) - drawer.redraw_unit(*u_it); + // was: SCALED_TO_HEX + render_scaled_to_zoom(tex, o_loc); + } } -} + // + // Village flags + // + for(const team& t : dc_->teams()) { + auto& flag = flags_[t.side() - 1]; + flag.update_last_draw_time(); -void display::draw_hex(const map_location& loc) { - int xpos = get_location_x(loc); - int ypos = get_location_y(loc); - image::TYPE image_type = get_image_type(loc); - const bool on_map = get_map().on_board(loc); - const time_of_day& tod = get_time_of_day(loc); + for(const map_location& v_loc : t.villages()) { + if(!fogged(v_loc) || !dc_->get_team(viewing_side()).is_enemy(t.side())) { - int num_images_fg = 0; - int num_images_bg = 0; + // TODO: move this if-animated check to a helper function. + const image::locator& flag_image = animate_map_ + ? flag.get_current_frame() + : flag.get_first_frame(); - if(!shrouded(loc)) { - // unshrouded terrain (the normal case) - get_terrain_images(loc, tod.id, BACKGROUND); // updates terrain_image_vector_ - drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, terrain_image_vector_); - num_images_bg = terrain_image_vector_.size(); + render_scaled_to_zoom(image::get_texture(flag_image), v_loc); + } + } + } - get_terrain_images(loc, tod.id, FOREGROUND); // updates terrain_image_vector_ - drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, terrain_image_vector_); - num_images_fg = terrain_image_vector_.size(); + // + // The grid overlay, if that's been enabled + // + if(grid_) { + for(const map_location& loc : visible_hexes) { + if(shrouded(loc)) { + continue; + } - // Draw the grid, if that's been enabled - if(grid_) { - static const image::locator grid_top(game_config::images::grid_top); - drawing_buffer_add(LAYER_GRID_TOP, loc, xpos, ypos, - image::get_image(grid_top, image::TOD_COLORED)); - static const image::locator grid_bottom(game_config::images::grid_bottom); - drawing_buffer_add(LAYER_GRID_BOTTOM, loc, xpos, ypos, - image::get_image(grid_bottom, image::TOD_COLORED)); + // TODO: split into drawing layers? If not, combine into one image or texture. + static const texture grid_top = image::get_texture(game_config::images::grid_top); + render_scaled_to_zoom(grid_top, loc); + + static const texture grid_bottom = image::get_texture(game_config::images::grid_bottom); + render_scaled_to_zoom(grid_bottom, loc); } } - if(!shrouded(loc)) { - auto it = get_overlays().find(loc); - if(it != get_overlays().end()) { - std::vector& overlays = it->second; - if(overlays.size() != 0) { - tod_color tod_col = tod.color + color_adjust_; - image::light_string lt = image::get_light_string(-1, tod_col.r, tod_col.g, tod_col.b); - - for(const overlay& ov : overlays) { - const std::string& current_team_name = get_teams()[viewing_team()].team_name(); - const std::vector& current_team_names = utils::split(current_team_name); - const std::vector& team_names = utils::split(ov.team_name); - if((ov.team_name.empty() || - std::find_first_of(team_names.begin(), team_names.end(), - current_team_names.begin(), current_team_names.end()) != team_names.end()) - && !(fogged(loc) && !ov.visible_in_fog)) - { - const surface surf = ov.image.find("~NO_TOD_SHIFT()") == std::string::npos ? - image::get_lighted_image(ov.image, lt, image::SCALED_TO_HEX) : image::get_image(ov.image, image::SCALED_TO_HEX); - drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, surf); - } - } - } + // + // Real (standing) units + // + if(!dc_->teams().empty()) { + unit_drawer drawer = unit_drawer(*this); + + for(const unit& real_unit : dc_->units()) { + drawer.redraw_unit(real_unit); } + + // TODO: re-add exclusionary checks for exclusive_unit_draw_requests_ later on, if necessary. +#if 0 + auto request = exclusive_unit_draw_requests_.find(loc); + if(request == exclusive_unit_draw_requests_.end() || request->second == u_it->id()) {}; +#endif } - if(!shrouded(loc)) { - // village-control flags. - drawing_buffer_add(LAYER_TERRAIN_BG, loc, xpos, ypos, get_flag(loc)); + // + // Foreground terrains. FIXME! sometimes cut off units... + // + draw_visible_hexes(visible_hexes, FOREGROUND); + + // + // Fake (moving) units + // + if(!dc_->teams().empty()) { + unit_drawer drawer = unit_drawer(*this); // TODO: don't create this twice per cycle. + + for(const unit* temp_unit : *fake_unit_man_) { + drawer.redraw_unit(*temp_unit); + } } - // Draw the time-of-day mask on top of the terrain in the hex. - // tod may differ from tod if hex is illuminated. - const std::string& tod_hex_mask = tod.image_mask; - if(tod_hex_mask1 != nullptr || tod_hex_mask2 != nullptr) { - drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask1); - drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask2); - } else if(!tod_hex_mask.empty()) { - drawing_buffer_add(LAYER_TERRAIN_FG, loc, xpos, ypos, - image::get_image(tod_hex_mask,image::SCALED_TO_HEX)); + // + // Draws various overlays, such as reach maps, etc. + // + draw_hex_overlays(); + + // + // Hex cursor (TODO: split into layers?) + // + draw_hex_cursor(mouseoverHex_); + + // + // Shroud and fog + // + for(const map_location& loc : visible_hexes) { + image::TYPE image_type = get_image_type(loc); + + const bool is_shrouded = shrouded(loc); + const bool is_fogged = fogged(loc); + + // Main images. + if(is_shrouded || is_fogged) { + + // If is_shrouded is false, is_fogged is true + const std::string& weather_image = is_shrouded + ? get_variant(shroud_images_, loc) + : get_variant(fog_images_, loc); + + // TODO: image type + render_scaled_to_zoom(image::get_texture(weather_image), loc); + } + + // Transitions to main hexes. + if(!is_shrouded) { + draw_fog_shroud_transition_images(loc, image_type); + } } - // Paint mouseover overlays + // ================================================================================ + // TODO: RE-ADD ALL THIS! + // ================================================================================ + +#if 0 + + // + // Mouseover overlays (TODO: delegate to editor) + // if(loc == mouseoverHex_ && (on_map || (in_editor() && get_map().on_board_with_border(loc))) - && mouseover_hex_overlay_ != nullptr) { - drawing_buffer_add(LAYER_MOUSEOVER_OVERLAY, loc, xpos, ypos, mouseover_hex_overlay_); + && mouseover_hex_overlay_ != nullptr) + { + render_scaled_to_zoom(mouseover_hex_overlay_, xpos, ypos); } - // Paint arrows - arrows_map_t::const_iterator arrows_in_hex = arrows_map_.find(loc); + // + // Arrows (whiteboard?) TODO: + // + auto arrows_in_hex = arrows_map_.find(loc); if(arrows_in_hex != arrows_map_.end()) { - for (arrow* const a : arrows_in_hex->second) { + for(arrow* const a : arrows_in_hex->second) { a->draw_hex(loc); } } - // Apply shroud, fog and linger overlay - - if(shrouded(loc)) { - // We apply void also on off-map tiles - // to shroud the half-hexes too - const std::string& shroud_image = get_variant(shroud_images_, loc); - drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos, - image::get_image(shroud_image, image_type)); - } else if(fogged(loc)) { - const std::string& fog_image = get_variant(fog_images_, loc); - drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos, - image::get_image(fog_image, image_type)); - } - - if(!shrouded(loc)) { - drawing_buffer_add(LAYER_FOG_SHROUD, loc, xpos, ypos, get_fog_shroud_images(loc, image_type)); + // + // ToD Mask + // + // Draw the time-of-day mask on top of the terrain in the hex. + // tod may differ from tod if hex is illuminated. + const std::string& tod_hex_mask = tod.image_mask; + if(tod_hex_mask1 != nullptr || tod_hex_mask2 != nullptr) { + drawing_queue_add(drawing_queue::LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask1); + drawing_queue_add(drawing_queue::LAYER_TERRAIN_FG, loc, xpos, ypos, tod_hex_mask2); + } else if(!tod_hex_mask.empty()) { + drawing_queue_add(drawing_queue::LAYER_TERRAIN_FG, loc, xpos, ypos, + image::get_image(tod_hex_mask,image::SCALED_TO_HEX)); } - if (on_map) { - if (draw_coordinates_) { + // + // Debugging output - coordinates, etc. + // + if(on_map) { + if(draw_coordinates_) { int off_x = xpos + hex_size()/2; int off_y = ypos + hex_size()/2; surface text = font::get_rendered_text(lexical_cast(loc), font::SIZE_SMALL, font::NORMAL_COLOR); @@ -2662,10 +2031,11 @@ void display::draw_hex(const map_location& loc) { if (draw_num_of_bitmaps_) { off_y -= text->h / 2; } - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg); - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, bg); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, text); } - if (draw_terrain_codes_ && (game_config::debug || !shrouded(loc))) { + + if(draw_terrain_codes_ && (game_config::debug || !shrouded(loc))) { int off_x = xpos + hex_size()/2; int off_y = ypos + hex_size()/2; surface text = font::get_rendered_text(lexical_cast(get_map().get_terrain(loc)), font::SIZE_SMALL, font::NORMAL_COLOR); @@ -2679,10 +2049,11 @@ void display::draw_hex(const map_location& loc) { } else if (draw_num_of_bitmaps_ && !draw_coordinates_) { off_y -= text->h / 2; } - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg); - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, bg); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, text); } - if (draw_num_of_bitmaps_) { + + if(draw_num_of_bitmaps_) { int off_x = xpos + hex_size()/2; int off_y = ypos + hex_size()/2; surface text = font::get_rendered_text(lexical_cast(num_images_bg + num_images_fg), font::SIZE_SMALL, font::NORMAL_COLOR); @@ -2697,525 +2068,71 @@ void display::draw_hex(const map_location& loc) { if (draw_terrain_codes_) { off_y += text->h / 2; } - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, bg); - drawing_buffer_add(LAYER_FOG_SHROUD, loc, off_x, off_y, text); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, bg); + drawing_queue_add(drawing_queue::LAYER_FOG_SHROUD, loc, off_x, off_y, text); } } if(debug_foreground) { - drawing_buffer_add(LAYER_UNIT_DEFAULT, loc, xpos, ypos, + drawing_queue_add(drawing_queue::LAYER_UNIT_DEFAULT, loc, xpos, ypos, image::get_image("terrain/foreground.png", image_type)); } - -} - -image::TYPE display::get_image_type(const map_location& /*loc*/) { - return image::TOD_COLORED; -} - -/*void display::draw_sidebar() { - -}*/ - -void display::draw_image_for_report(surface& img, SDL_Rect& rect) -{ - SDL_Rect visible_area = get_non_transparent_portion(img); - SDL_Rect target = rect; - if(visible_area.x != 0 || visible_area.y != 0 || visible_area.w != img->w || visible_area.h != img->h) { - if(visible_area.w == 0 || visible_area.h == 0) { - return; - } - - if(visible_area.w > rect.w || visible_area.h > rect.h) { - img.assign(get_surface_portion(img,visible_area)); - img.assign(scale_surface(img,rect.w,rect.h)); - visible_area.x = 0; - visible_area.y = 0; - visible_area.w = img->w; - visible_area.h = img->h; - } else { - target.x = rect.x + (rect.w - visible_area.w)/2; - target.y = rect.y + (rect.h - visible_area.h)/2; - target.w = visible_area.w; - target.h = visible_area.h; - } - - sdl_blit(img,&visible_area,screen_.getSurface(),&target); - } else { - if(img->w != rect.w || img->h != rect.h) { - img.assign(scale_surface(img,rect.w,rect.h)); - } - - sdl_blit(img,nullptr,screen_.getSurface(),&target); - } +#endif } -/** - * Redraws the specified report (if anything has changed). - * If a config is not supplied, it will be generated via - * reports::generate_report(). - */ -void display::refresh_report(const std::string& report_name, const config * new_cfg) +void display::draw() { - const theme::status_item *item = theme_.get_status_item(report_name); - if (!item) { - reportSurfaces_[report_name].assign(nullptr); - return; - } - - // Now we will need the config. Generate one if needed. - - boost::optional mhb = boost::none; - - if (resources::controller) { - mhb = resources::controller->get_mouse_handler_base(); - } - - reports::context temp_context = reports::context(*dc_, *this, *resources::tod_manager, wb_.lock(), mhb); - - const config generated_cfg = new_cfg ? config() : reports_object_->generate_report(report_name, temp_context); - if ( new_cfg == nullptr ) - new_cfg = &generated_cfg; - - SDL_Rect &rect = reportRects_[report_name]; - const SDL_Rect &new_rect = item->location(screen_.screen_area()); - surface &surf = reportSurfaces_[report_name]; - config &report = reports_[report_name]; - - // Report and its location is unchanged since last time. Do nothing. - if (surf && rect == new_rect && report == *new_cfg) { - return; - } - - // Update the config in reports_. - report = *new_cfg; - - if (surf) { - sdl_blit(surf, nullptr, screen_.getSurface(), &rect); - } - - // If the rectangle has just changed, assign the surface to it - if (!surf || new_rect != rect) - { - surf.assign(nullptr); - rect = new_rect; - - // If the rectangle is present, and we are blitting text, - // then we need to backup the surface. - // (Images generally won't need backing up, - // unless they are transparent, but that is done later). - if (rect.w > 0 && rect.h > 0) { - surf.assign(get_surface_portion(screen_.getSurface(), rect)); - if (reportSurfaces_[report_name] == nullptr) { - ERR_DP << "Could not backup background for report!" << std::endl; - } - } - } - - tooltips::clear_tooltips(rect); - - if (report.empty()) return; + // Execute any pre-draw actions from derived classes. + pre_draw(); - int x = rect.x, y = rect.y; + // Draw theme background. + draw_background(); - // Add prefix, postfix elements. - // Make sure that they get the same tooltip - // as the guys around them. - std::string str = item->prefix(); - if (!str.empty()) { - config &e = report.add_child_at("element", config(), 0); - e["text"] = str; - e["tooltip"] = report.child("element")["tooltip"]; - } - str = item->postfix(); - if (!str.empty()) { - config &e = report.add_child("element"); - e["text"] = str; - e["tooltip"] = report.child("element", -1)["tooltip"]; - } + // Progress animations. + invalidate_animations(); - // Loop through and display each report element. - int tallest = 0; - int image_count = 0; - bool used_ellipsis = false; - std::ostringstream ellipsis_tooltip; - SDL_Rect ellipsis_area = rect; + // draw_minimap(); - for (config::const_child_itors elements = report.child_range("element"); - elements.begin() != elements.end(); elements.pop_front()) + // Draw the gamemap and its contents (units, etc) { - SDL_Rect area {x, y, rect.w + rect.x - x, rect.h + rect.y - y}; - if (area.h <= 0) break; - - std::string t = elements.front()["text"]; - if (!t.empty()) - { - if (used_ellipsis) goto skip_element; - - // Draw a text element. - font::pango_text text; - if (item->font_rgb_set()) { - text.set_foreground_color(item->font_rgb()); - } - bool eol = false; - if (t[t.size() - 1] == '\n') { - eol = true; - t = t.substr(0, t.size() - 1); - } - text.set_font_size(item->font_size()); - text.set_text(t, true); - text.set_maximum_width(area.w); - text.set_maximum_height(area.h, false); - surface s = text.render(); - - // check if next element is text with almost no space to show it - const int minimal_text = 12; // width in pixels - config::const_child_iterator ee = elements.begin(); - if (!eol && rect.w - (x - rect.x + s->w) < minimal_text && - ++ee != elements.end() && !(*ee)["text"].empty()) - { - // make this element longer to trigger rendering of ellipsis - // (to indicate that next elements have not enough space) - //NOTE this space should be longer than minimal_text pixels - t = t + " "; - text.set_text(t, true); - s = text.render(); - // use the area of this element for next tooltips - used_ellipsis = true; - ellipsis_area.x = x; - ellipsis_area.y = y; - ellipsis_area.w = s->w; - ellipsis_area.h = s->h; - } - - screen_.blit_surface(x, y, s); - area.w = s->w; - area.h = s->h; - if (area.h > tallest) { - tallest = area.h; - } - if (eol) { - x = rect.x; - y += tallest; - tallest = 0; - } else { - x += area.w; - } - } - else if (!(t = elements.front()["image"].str()).empty()) - { - if (used_ellipsis) goto skip_element; - - // Draw an image element. - surface img(image::get_image(t)); - - if (!img) { - ERR_DP << "could not find image for report: '" << t << "'" << std::endl; - continue; - } - - if (area.w < img->w && image_count) { - // We have more than one image, and this one doesn't fit. - img = image::get_image(game_config::images::ellipsis); - used_ellipsis = true; - } - - if (img->w < area.w) area.w = img->w; - if (img->h < area.h) area.h = img->h; - draw_image_for_report(img, area); - - ++image_count; - if (area.h > tallest) { - tallest = area.h; - } - - if (!used_ellipsis) { - x += area.w; - } else { - ellipsis_area = area; - } - } - else - { - // No text nor image, skip this element - continue; - } - - skip_element: - t = elements.front()["tooltip"].t_str().base_str(); - if (!t.empty()) { - if (!used_ellipsis) { - tooltips::add_tooltip(area, t, elements.front()["help"].t_str().base_str()); - } else { - // Collect all tooltips for the ellipsis. - // TODO: need a better separator - // TODO: assign an action - ellipsis_tooltip << t; - config::const_child_iterator ee = elements.begin(); - if (++ee != elements.end()) - ellipsis_tooltip << "\n _________\n\n"; - } - } - } + SDL_Rect map_area_rect = map_area(); + render_clip_rect_setter setter(&map_area_rect); - if (used_ellipsis) { - tooltips::add_tooltip(ellipsis_area, ellipsis_tooltip.str()); + draw_gamemap(); } -} - -void display::invalidate_all() -{ - DBG_DP << "invalidate_all()\n"; - invalidateAll_ = true; -#ifdef _OPENMP -#pragma omp critical(invalidated_) -#endif //_OPENMP - invalidated_.clear(); -} - -bool display::invalidate(const map_location& loc) -{ - if(invalidateAll_) - return false; - - bool tmp; -#ifdef _OPENMP -#pragma omp critical(invalidated_) -#endif //_OPENMP - tmp = invalidated_.insert(loc).second; - return tmp; -} - -bool display::invalidate(const std::set& locs) -{ - if(invalidateAll_) - return false; - bool ret = false; - for (const map_location& loc : locs) { -#ifdef _OPENMP -#pragma omp critical(invalidated_) -#endif //_OPENMP - ret = invalidated_.insert(loc).second || ret; - } - return ret; -} - -bool display::propagate_invalidation(const std::set& locs) -{ - if(invalidateAll_) - return false; - if(locs.size()<=1) - return false; // propagation never needed + // Draw debugging aids such as the FPS counter. + draw_debugging_aids(); - bool result = false; -#ifdef _OPENMP -#pragma omp critical(invalidated_) -#endif //_OPENMP - { - // search the first hex invalidated (if any) - std::set::const_iterator i = locs.begin(); - for(; i != locs.end() && invalidated_.count(*i) == 0 ; ++i) {} - - if (i != locs.end()) { - - // propagate invalidation - // 'i' is already in, but I suspect that splitting the range is bad - // especially because locs are often adjacents - size_t previous_size = invalidated_.size(); - invalidated_.insert(locs.begin(), locs.end()); - result = previous_size < invalidated_.size(); - } - } - return result; -} + // Draw floating labels (includes map labels). + font::draw_floating_labels(); -bool display::invalidate_visible_locations_in_rect(const SDL_Rect& rect) -{ - return invalidate_locations_in_rect(sdl::intersect_rects(map_area(),rect)); -} + // TODO: what dis? + // events::raise_volatile_draw_event(); + // events::raise_volatile_undraw_event(); -bool display::invalidate_locations_in_rect(const SDL_Rect& rect) -{ - if(invalidateAll_) - return false; - - bool result = false; - for (const map_location &loc : hexes_under_rect(rect)) { - result |= invalidate(loc); - } - return result; -} - -void display::invalidate_animations_location(const map_location& loc) { - if (get_map().is_village(loc)) { - const int owner = dc_->village_owner(loc); - if (owner >= 0 && flags_[owner].need_update() - && (!fogged(loc) || !dc_->teams()[currentTeam_].is_enemy(owner+1))) { - invalidate(loc); - } - } + // Execute any post-draw actions from derived classes. + post_draw(); } -void display::invalidate_animations() +void display::handle_window_event(const SDL_Event& event) { - new_animation_frame(); - animate_map_ = preferences::animate_map(); - if (animate_map_) { - for (const map_location &loc : get_visible_hexes()) - { - if (shrouded(loc)) continue; - if (builder_->update_animation(loc)) { - invalidate(loc); - } else { - invalidate_animations_location(loc); - } - } - } - - for(const unit& u : dc_->units()) { - u.anim_comp().refresh(); - } - for (const unit* u : *fake_unit_man_) { - u->anim_comp().refresh(); - } - - - bool new_inval; - do { - new_inval = false; - for (const unit & u : dc_->units()) { - new_inval |= u.anim_comp().invalidate(*this); - } - for (const unit* u : *fake_unit_man_) { - new_inval |= u->anim_comp().invalidate(*this); + if(event.type == SDL_WINDOWEVENT) { + switch(event.window.event) { + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_EXPOSED: + // TODO: add additional handling here if needed. + break; } - } while (new_inval); -} - -void display::reset_standing_animations() -{ - for(const unit & u : dc_->units()) { - u.anim_comp().set_standing(); } } -void display::add_arrow(arrow& arrow) +void display::handle_event(const SDL_Event& /*event*/) { - const arrow_path_t & arrow_path = arrow.get_path(); - for (const map_location& loc : arrow_path) - { - arrows_map_[loc].push_back(&arrow); - } -} - -void display::remove_arrow(arrow& arrow) -{ - const arrow_path_t & arrow_path = arrow.get_path(); - for (const map_location& loc : arrow_path) - { - arrows_map_[loc].remove(&arrow); - } -} - -void display::update_arrow(arrow & arrow) -{ - const arrow_path_t & previous_path = arrow.get_previous_path(); - for (const map_location& loc : previous_path) - { - arrows_map_[loc].remove(&arrow); - } - const arrow_path_t & arrow_path = arrow.get_path(); - for (const map_location& loc : arrow_path) - { - arrows_map_[loc].push_back(&arrow); - } -} - -map_location display::get_middle_location() const -{ - const SDL_Rect& rect = map_area(); - return pixel_position_to_hex(xpos_ + rect.x + rect.w / 2 , ypos_ + rect.y + rect.h / 2 ); -} - -void display::write(config& cfg) const -{ - cfg["view_locked"] = view_locked_; - cfg["color_adjust_red"] = color_adjust_.r; - cfg["color_adjust_green"] = color_adjust_.g; - cfg["color_adjust_blue"] = color_adjust_.b; - get_middle_location().write(cfg.add_child("location")); -} - -void display::read(const config& cfg) -{ - view_locked_ = cfg["view_locked"].to_bool(false); - color_adjust_.r = cfg["color_adjust_red"].to_int(0); - color_adjust_.g = cfg["color_adjust_green"].to_int(0); - color_adjust_.b = cfg["color_adjust_blue"].to_int(0); -} - -void display::process_reachmap_changes() -{ - if (!reach_map_changed_) return; - if (reach_map_.empty() != reach_map_old_.empty()) { - // Invalidate everything except the non-darkened tiles - reach_map &full = reach_map_.empty() ? reach_map_old_ : reach_map_; - - for (const auto& hex : get_visible_hexes()) { - reach_map::iterator reach = full.find(hex); - if (reach == full.end()) { - // Location needs to be darkened or brightened - invalidate(hex); - } else if (reach->second != 1) { - // Number needs to be displayed or cleared - invalidate(hex); - } - } - } else if (!reach_map_.empty()) { - // Invalidate only changes - reach_map::iterator reach, reach_old; - for (reach = reach_map_.begin(); reach != reach_map_.end(); ++reach) { - reach_old = reach_map_old_.find(reach->first); - if (reach_old == reach_map_old_.end()) { - invalidate(reach->first); - } else { - if (reach_old->second != reach->second) { - invalidate(reach->first); - } - reach_map_old_.erase(reach_old); - } - } - for (reach_old = reach_map_old_.begin(); reach_old != reach_map_old_.end(); ++reach_old) { - invalidate(reach_old->first); - } - } - reach_map_old_ = reach_map_; - reach_map_changed_ = false; -} - -void display::handle_window_event(const SDL_Event& event) { - if (event.type == SDL_WINDOWEVENT) { - switch (event.window.event) { - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_RESTORED: - case SDL_WINDOWEVENT_EXPOSED: - dirty_ = true; - - break; - } - } - - -} - -void display::handle_event(const SDL_Event& event) { - if (gui2::dialogs::loading_screen::displaying()) { + if(gui2::dialogs::loading_screen::displaying()) { return; } - if (event.type == DRAW_ALL_EVENT) { - draw(); - } } -display *display::singleton_ = nullptr; +display* display::singleton_ = nullptr;