Skip to content

Commit

Permalink
Window manager (#37894)
Browse files Browse the repository at this point in the history
For properly refreshing and resizing multiple layers of windows
  • Loading branch information
Qrox authored and ZhilkinSerg committed Apr 1, 2020
1 parent 8d77285 commit ee5834a
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 37 deletions.
2 changes: 2 additions & 0 deletions src/ncurses_def.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
}
}

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

#if defined(__linux__)
# include <cstdlib> // getenv()/setenv()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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() ) {
Expand All @@ -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.
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -3693,45 +3711,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 ) {
// 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 ) ) {
Expand All @@ -3754,7 +3787,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() );
Expand Down
15 changes: 15 additions & 0 deletions src/sdltiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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

0 comments on commit ee5834a

Please sign in to comment.