Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Window manager for properly refreshing and resizing multiple layers of windows #37894

Merged
merged 1 commit into from
Mar 16, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/ncurses_def.cpp
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@
#include "catacharset.h"
#include "color.h"
#include "game_ui.h"
#include "ui_manager.h"

extern int VIEW_OFFSET_X; // X position of terrain window
extern int VIEW_OFFSET_Y; // Y position of terrain window
@@ -204,6 +205,7 @@ void catacurses::resizeterm()
const int new_y = ::getmaxy( stdscr.get<::WINDOW>() );
if( ::is_term_resized( new_x, new_y ) ) {
game_ui::init_ui();
ui_manager::screen_resized();
}
}

107 changes: 70 additions & 37 deletions src/sdltiles.cpp
Original file line number Diff line number Diff line change
@@ -57,6 +57,7 @@
#include "json.h"
#include "optional.h"
#include "point.h"
#include "ui_manager.h"

#if defined(__linux__)
# include <cstdlib> // getenv()/setenv()
@@ -1762,7 +1763,7 @@ bool handle_resize( int w, int h )
TERMINAL_HEIGHT = WindowHeight / fontheight / scaling_factor;
SetupRenderTarget();
game_ui::init_ui();

ui_manager::screen_resized();
return true;
}
return false;
@@ -2824,6 +2825,9 @@ static void CheckMessages()
#endif

last_input = input_event();

bool need_redraw = false;

while( SDL_PollEvent( &ev ) ) {
switch( ev.type ) {
case SDL_WINDOWEVENT:
@@ -2854,17 +2858,14 @@ static void CheckMessages()
break;
#endif
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_MINIMIZED:
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
// Main menu redraw
reinitialize_framebuffer();
// TODO: redraw all game menus if they are open
break;
case SDL_WINDOWEVENT_EXPOSED:
need_redraw = true;
needupdate = true;
break;
case SDL_WINDOWEVENT_RESTORED:
needupdate = true;
#if defined(__ANDROID__)
needs_sdl_surface_visibility_refresh = true;
if( android_is_hardware_keyboard_available() ) {
@@ -2880,6 +2881,10 @@ static void CheckMessages()
break;
}
break;
case SDL_RENDER_TARGETS_RESET:
need_redraw = true;
needupdate = true;
break;
case SDL_KEYDOWN: {
#if defined(__ANDROID__)
// Toggle virtual keyboard with Android back button. For some reason I get double inputs, so ignore everything once it's already down.
@@ -3204,6 +3209,19 @@ static void CheckMessages()
break;
}
}
if( need_redraw ) {
// FIXME: SDL_RENDER_TARGETS_RESET only seems to be fired after the first redraw
// when restoring the window after system sleep, rather than immediately
// on focus gain. This seems to mess up the first redraw and
// causes black screen that lasts ~0.5 seconds before the screen
// contents are redrawn in the following code.

// Main menu redraw
reinitialize_framebuffer();
window_dimensions dim = get_window_dimensions( catacurses::stdscr );
ui_manager::invalidate( rectangle( point_zero, dim.window_size_pixel ) );
ui_manager::redraw();
}
if( needupdate ) {
try_sdl_update();
}
@@ -3690,45 +3708,60 @@ void rescale_tileset( int size )
game_ui::init_ui();
}

cata::optional<tripoint> input_context::get_coordinates( const catacurses::window &capture_win_ )
window_dimensions get_window_dimensions( const catacurses::window &win )
{
if( !coordinate_input_received ) {
return cata::nullopt;
}
cata_cursesport::WINDOW *const pwin = win.get<cata_cursesport::WINDOW>();

cata_cursesport::WINDOW *const capture_win = ( capture_win_.get() ? capture_win_ :
g->w_terrain ).get<cata_cursesport::WINDOW>();

// this contains the font dimensions of the capture_win,
// not necessarily the global standard font dimensions.
int fw = fontwidth;
int fh = fontheight;
// tiles might have different dimensions than standard font
if( use_tiles && capture_win == g->w_terrain ) {
fw = tilecontext->get_tile_width();
fh = tilecontext->get_tile_height();
// add_msg( m_info, "tile map fw %d fh %d", fw, fh);
} else if( map_font && capture_win == g->w_terrain ) {
window_dimensions dim;
if( use_tiles && win == g->w_terrain ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently sometimes g could be empty when there are errors (see #39258), so we need to check for non-emptiness prior to comparing win with members of g, e.g.:

    window_dimensions dim;
    dim.scaled_font_size.x = fontwidth;
    dim.scaled_font_size.y = fontheight;
    if( g ) {
        if( use_tiles && win == g->w_terrain ) {
            // tiles might have different dimensions than standard font
            dim.scaled_font_size.x = tilecontext->get_tile_width();
            dim.scaled_font_size.y = tilecontext->get_tile_height();
        } else if( map_font && win == g->w_terrain ) {
            // map font (if any) might differ from standard font
            dim.scaled_font_size.x = map_font->fontwidth;
            dim.scaled_font_size.y = map_font->fontheight;
        } else if( overmap_font && win == g->w_overmap ) {
            dim.scaled_font_size.x = overmap_font->fontwidth;
            dim.scaled_font_size.y = overmap_font->fontheight;
        }
    }

// tiles might have different dimensions than standard font
dim.scaled_font_size.x = tilecontext->get_tile_width();
dim.scaled_font_size.y = tilecontext->get_tile_height();
} else if( map_font && win == g->w_terrain ) {
// map font (if any) might differ from standard font
fw = map_font->fontwidth;
fh = map_font->fontheight;
} else if( overmap_font && capture_win == g->w_overmap ) {
fw = overmap_font->fontwidth;
fh = overmap_font->fontheight;
dim.scaled_font_size.x = map_font->fontwidth;
dim.scaled_font_size.y = map_font->fontheight;
} else if( overmap_font && win == g->w_overmap ) {
dim.scaled_font_size.x = overmap_font->fontwidth;
dim.scaled_font_size.y = overmap_font->fontheight;
} else {
dim.scaled_font_size.x = fontwidth;
dim.scaled_font_size.y = fontheight;
}

// multiplied by the user's specified scaling factor regardless of whether tiles are in use
fw = fw * get_scaling_factor();
fh = fh * get_scaling_factor();
dim.scaled_font_size *= get_scaling_factor();

dim.window_pos_cell = pwin->pos;
dim.window_size_cell.x = pwin->width;
dim.window_size_cell.y = pwin->height;

// Translate mouse coordinates to map coordinates based on tile size,
// the window position is *always* in standard font dimensions!
const point win_min( capture_win->pos.x * fontwidth, capture_win->pos.y * fontheight );
dim.window_pos_pixel = point( dim.window_pos_cell.x * fontwidth,
dim.window_pos_cell.y * fontheight );
// But the size of the window is in the font dimensions of the window.
const point win_size( capture_win->width * fw, capture_win->height * fh );
dim.window_size_pixel.x = dim.window_size_cell.x * dim.scaled_font_size.x;
dim.window_size_pixel.y = dim.window_size_cell.y * dim.scaled_font_size.y;

return dim;
}

cata::optional<tripoint> input_context::get_coordinates( const catacurses::window &capture_win_ )
{
if( !coordinate_input_received ) {
return cata::nullopt;
}

const catacurses::window &capture_win = capture_win_ ? capture_win_ : g->w_terrain;
const window_dimensions dim = get_window_dimensions( capture_win );

const int &fw = dim.scaled_font_size.x;
const int &fh = dim.scaled_font_size.y;
const point &win_min = dim.window_pos_pixel;
const point &win_size = dim.window_size_pixel;
const point win_max = win_min + win_size;
// add_msg( m_info, "win_ left %d top %d right %d bottom %d", win_left,win_top,win_right,win_bottom);
// add_msg( m_info, "coordinate_ x %d y %d", coordinate_x, coordinate_y);

// Translate mouse coordinates to map coordinates based on tile size
// Check if click is within bounds of the window we care about
const rectangle win_bounds( win_min, win_max );
if( !win_bounds.contains_inclusive( coordinate ) ) {
@@ -3751,7 +3784,7 @@ cata::optional<tripoint> input_context::get_coordinates( const catacurses::windo
p = view_offset + selected;
} else {
const point selected( screen_pos.x / fw, screen_pos.y / fh );
p = view_offset + selected - point( capture_win->width / 2, capture_win->height / 2 );
p = view_offset + selected - dim.window_size_cell / 2;
}

return tripoint( p, g->get_levz() );
15 changes: 15 additions & 0 deletions src/sdltiles.h
Original file line number Diff line number Diff line change
@@ -9,10 +9,16 @@
#include <memory>

#include "color_loader.h"
#include "point.h"
#include "sdl_wrappers.h"

class cata_tiles;

namespace catacurses
{
class window;
} // namespace catacurses

extern SDL_Texture_Ptr alt_rect_tex;
extern bool alt_rect_tex_enabled;
extern std::unique_ptr<cata_tiles> tilecontext;
@@ -25,6 +31,15 @@ void rescale_tileset( int size );
bool save_screenshot( const std::string &file_path );
void toggle_fullscreen_window();

struct window_dimensions {
point scaled_font_size;
point window_pos_cell;
point window_size_cell;
point window_pos_pixel;
point window_size_pixel;
};
window_dimensions get_window_dimensions( const catacurses::window &win );

#endif // TILES

#endif // CATA_SDLTILES_H
126 changes: 126 additions & 0 deletions src/ui_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#include "ui_manager.h"

#include <vector>

#include "cursesdef.h"
#include "point.h"
#include "sdltiles.h"

static std::vector<std::reference_wrapper<ui_adaptor>> ui_stack;

ui_adaptor::ui_adaptor() : invalidated( false )
{
ui_stack.emplace_back( *this );
}

ui_adaptor::~ui_adaptor()
{
for( auto it = ui_stack.rbegin(); it < ui_stack.rend(); ++it ) {
if( &it->get() == this ) {
ui_stack.erase( std::prev( it.base() ) );
// TODO avoid invalidating portions that do not need to be redrawn
ui_manager::invalidate( dimensions );
break;
}
}
}

void ui_adaptor::position_from_window( const catacurses::window &win )
{
const rectangle old_dimensions = dimensions;
// ensure position is updated before calling invalidate
#ifdef TILES
const window_dimensions dim = get_window_dimensions( win );
dimensions = rectangle( dim.window_pos_pixel, dim.window_pos_pixel + dim.window_size_pixel );
#else
const point origin( getbegx( win ), getbegy( win ) );
dimensions = rectangle( origin, origin + point( getmaxx( win ), getmaxy( win ) ) );
#endif
invalidated = true;
ui_manager::invalidate( old_dimensions );
}

void ui_adaptor::on_redraw( const redraw_callback_t &fun )
{
redraw_cb = fun;
}

void ui_adaptor::on_screen_resize( const screen_resize_callback_t &fun )
{
screen_resized_cb = fun;
}

static bool contains( const rectangle &lhs, const rectangle &rhs )
{
return rhs.p_min.x >= lhs.p_min.x && rhs.p_max.x <= lhs.p_max.x &&
rhs.p_min.y >= lhs.p_min.y && rhs.p_max.y <= lhs.p_max.y;
}

static bool overlap( const rectangle &lhs, const rectangle &rhs )
{
return lhs.p_min.x < rhs.p_max.x && lhs.p_min.y < rhs.p_max.y &&
rhs.p_min.x < lhs.p_max.x && rhs.p_min.y < lhs.p_max.y;
}

void ui_adaptor::invalidate( const rectangle &rect )
{
if( rect.p_min.x >= rect.p_max.x || rect.p_min.y >= rect.p_max.y ) {
return;
}
// TODO avoid invalidating portions that do not need to be redrawn
for( auto it = ui_stack.crbegin(); it < ui_stack.crend(); ++it ) {
const ui_adaptor &ui = it->get();
if( overlap( ui.dimensions, rect ) ) {
ui.invalidated = true;
if( contains( ui.dimensions, rect ) ) {
break;
}
}
}
}

void ui_adaptor::redraw()
{
// TODO refresh only when all stacked UIs are drawn
if( !ui_stack.empty() ) {
ui_stack.back().get().invalidated = true;
for( const ui_adaptor &ui : ui_stack ) {
if( ui.invalidated ) {
if( ui.redraw_cb ) {
ui.redraw_cb( ui );
}
ui.invalidated = false;
}
}
}
}

void ui_adaptor::screen_resized()
{
for( ui_adaptor &ui : ui_stack ) {
if( ui.screen_resized_cb ) {
ui.screen_resized_cb( ui );
}
}
redraw();
}

namespace ui_manager
{

void invalidate( const rectangle &rect )
{
ui_adaptor::invalidate( rect );
}

void redraw()
{
ui_adaptor::redraw();
}

void screen_resized()
{
ui_adaptor::screen_resized();
}

} // namespace ui_manager
Loading