mirror of
https://github.com/wesnoth/wesnoth
synced 2025-05-13 00:11:52 +00:00
522 lines
13 KiB
C++
522 lines
13 KiB
C++
/* $Id$ */
|
|
/*
|
|
Copyright (C) 2007 - 2008 by Mark de Wever <koraq@xs4all.nl>
|
|
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 version 2
|
|
or at your option any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
/**
|
|
* @file window.cpp
|
|
* Implementation of window.hpp.
|
|
*/
|
|
|
|
#include "gui/widgets/window.hpp"
|
|
|
|
#include "cursor.hpp"
|
|
#include "font.hpp"
|
|
#include "log.hpp"
|
|
#include "titlescreen.hpp"
|
|
#include "tstring.hpp"
|
|
#include "video.hpp"
|
|
|
|
#include <cassert>
|
|
|
|
#define DBG_G LOG_STREAM_INDENT(debug, gui)
|
|
#define LOG_G LOG_STREAM_INDENT(info, gui)
|
|
#define WRN_G LOG_STREAM_INDENT(warn, gui)
|
|
#define ERR_G LOG_STREAM_INDENT(err, gui)
|
|
|
|
#define DBG_G_D LOG_STREAM_INDENT(debug, gui_draw)
|
|
#define LOG_G_D LOG_STREAM_INDENT(info, gui_draw)
|
|
#define WRN_G_D LOG_STREAM_INDENT(warn, gui_draw)
|
|
#define ERR_G_D LOG_STREAM_INDENT(err, gui_draw)
|
|
|
|
#define DBG_G_E LOG_STREAM_INDENT(debug, gui_event)
|
|
#define LOG_G_E LOG_STREAM_INDENT(info, gui_event)
|
|
#define WRN_G_E LOG_STREAM_INDENT(warn, gui_event)
|
|
#define ERR_G_E LOG_STREAM_INDENT(err, gui_event)
|
|
|
|
#define DBG_G_P LOG_STREAM_INDENT(debug, gui_parse)
|
|
#define LOG_G_P LOG_STREAM_INDENT(info, gui_parse)
|
|
#define WRN_G_P LOG_STREAM_INDENT(warn, gui_parse)
|
|
#define ERR_G_P LOG_STREAM_INDENT(err, gui_parse)
|
|
|
|
|
|
namespace gui2{
|
|
|
|
namespace {
|
|
|
|
/**
|
|
* The interval between draw events.
|
|
*
|
|
* When the window is shown this value is set, the callback function always
|
|
* uses this value instead of the parameter send, that way the window can stop
|
|
* drawing when it wants.
|
|
*/
|
|
static int draw_interval = 0;
|
|
|
|
/**
|
|
* SDL_AddTimer() callback for the draw event.
|
|
*
|
|
* When this callback is called it pushes a new draw event in the event queue.
|
|
*
|
|
* @returns The new timer interval, 0 to stop.
|
|
*/
|
|
static Uint32 draw_timer(Uint32, void*)
|
|
{
|
|
DBG_G_E << "Pushing draw event in queue.\n";
|
|
|
|
SDL_Event event;
|
|
SDL_UserEvent data;
|
|
|
|
data.type = DRAW_EVENT;
|
|
data.code = 0;
|
|
data.data1 = NULL;
|
|
data.data2 = NULL;
|
|
|
|
event.type = DRAW_EVENT;
|
|
event.user = data;
|
|
|
|
SDL_PushEvent(&event);
|
|
return draw_interval;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
twindow::twindow(CVideo& video,
|
|
tformula<unsigned>x,
|
|
tformula<unsigned>y,
|
|
tformula<unsigned>w,
|
|
tformula<unsigned>h,
|
|
const bool automatic_placement,
|
|
const unsigned horizontal_placement,
|
|
const unsigned vertical_placement,
|
|
const std::string& definition) :
|
|
tpanel(),
|
|
tevent_handler(),
|
|
video_(video),
|
|
status_(NEW),
|
|
retval_(0),
|
|
owner_(0),
|
|
need_layout_(true),
|
|
resized_(true),
|
|
suspend_drawing_(true),
|
|
top_level_(false),
|
|
window_(),
|
|
restorer_(),
|
|
tooltip_(),
|
|
help_popup_(),
|
|
automatic_placement_(automatic_placement),
|
|
horizontal_placement_(horizontal_placement),
|
|
vertical_placement_(vertical_placement),
|
|
x_(x),
|
|
y_(y),
|
|
w_(w),
|
|
h_(h)
|
|
{
|
|
// We load the config in here as exception.
|
|
// Our caller did update the screen size so no need for us to do that again.
|
|
set_definition(definition);
|
|
load_config();
|
|
|
|
tooltip_.set_definition("default");
|
|
tooltip_.set_visible(false);
|
|
|
|
help_popup_.set_definition("default");
|
|
help_popup_.set_visible(false);
|
|
}
|
|
|
|
void twindow::update_screen_size()
|
|
{
|
|
// Only if we're the toplevel window we need to update the size, otherwise
|
|
// it's done in the resize event.
|
|
if(draw_interval == 0) {
|
|
const SDL_Rect rect = screen_area();
|
|
settings::screen_width = rect.w;
|
|
settings::screen_height = rect.h;
|
|
}
|
|
}
|
|
|
|
twindow::tretval twindow::get_retval_by_id(const std::string& id)
|
|
{
|
|
/*WIKI
|
|
* @page = GUIToolkitWML
|
|
* @order = 3_widget_window_2
|
|
*
|
|
* List if the id's that have generate a return value:
|
|
* * ok confirms the dialog.
|
|
* * cancel cancels the dialog.
|
|
*
|
|
*/
|
|
// Note it might change to a map later depending on the number
|
|
// of items.
|
|
if(id == "ok") {
|
|
return OK;
|
|
} else if(id == "cancel") {
|
|
return CANCEL;
|
|
|
|
/**
|
|
* The ones for the title screen.
|
|
*
|
|
* This is a kind of hack, but the values are hardcoded in the titlescreen
|
|
* and don't want to change them at the moment. It would be a good idea to
|
|
* add some namespaces to avoid names clashing.
|
|
*/
|
|
} else if(id == "tutorial") {
|
|
return static_cast<tretval>(gui::TUTORIAL);
|
|
#ifdef USE_EDITOR2
|
|
} else if(id == "editor") {
|
|
return static_cast<tretval>(gui::START_MAP_EDITOR);
|
|
#endif
|
|
} else if(id == "credits") {
|
|
return static_cast<tretval>(gui::SHOW_ABOUT);
|
|
} else if(id == "quit") {
|
|
return static_cast<tretval>(gui::QUIT_GAME);
|
|
|
|
/**
|
|
* The hacks which are here so the old engine can handle the event. The new
|
|
* engine can't handle all dialogs yet, so it needs to fall back to the old
|
|
* engine to make certain things happen.
|
|
*/
|
|
} else if(id == "campaign") {
|
|
return static_cast<tretval>(gui::NEW_CAMPAIGN);
|
|
} else if(id == "multiplayer") {
|
|
return static_cast<tretval>(gui::MULTIPLAYER);
|
|
} else if(id == "load") {
|
|
return static_cast<tretval>(gui::LOAD_GAME);
|
|
} else if(id == "addons") {
|
|
return static_cast<tretval>(gui::GET_ADDONS);
|
|
} else if(id == "preferences") {
|
|
return static_cast<tretval>(gui::EDIT_PREFERENCES);
|
|
|
|
// default if nothing matched
|
|
} else {
|
|
return NONE;
|
|
}
|
|
}
|
|
|
|
int twindow::show(const bool restore, void* /*flip_function*/)
|
|
{
|
|
log_scope2(gui_draw, "Window: show.");
|
|
|
|
assert(status_ == NEW);
|
|
|
|
top_level_ = (draw_interval == 0);
|
|
if(top_level_) {
|
|
draw_interval = 30;
|
|
SDL_AddTimer(draw_interval, draw_timer, NULL);
|
|
|
|
// There might be some time between creation and showing so reupdate
|
|
// the sizes.
|
|
update_screen_size();
|
|
}
|
|
|
|
suspend_drawing_ = false;
|
|
|
|
// Start our loop drawing will happen here as well.
|
|
for(status_ = SHOWING; status_ != REQUEST_CLOSE; ) {
|
|
process_events();
|
|
// Add a delay so we don't keep spinning if there's no event.
|
|
SDL_Delay(10);
|
|
}
|
|
|
|
suspend_drawing_ = true;
|
|
|
|
if(top_level_) {
|
|
draw_interval = 0;
|
|
}
|
|
|
|
// restore area
|
|
if(restore) {
|
|
SDL_Rect rect = get_rect();
|
|
SDL_BlitSurface(restorer_, 0, video_.getSurface(), &rect);
|
|
update_rect(get_rect());
|
|
}
|
|
|
|
return retval_;
|
|
}
|
|
|
|
void twindow::draw()
|
|
{
|
|
// NOTE since we're single threaded there's no need to create a critical
|
|
// section in this drawing routine.
|
|
|
|
// Prohibited from drawing?
|
|
if(suspend_drawing_) {
|
|
return;
|
|
}
|
|
|
|
// Drawing not required?
|
|
if(!resized_ && !need_layout_ && !is_dirty()) {
|
|
return;
|
|
}
|
|
|
|
surface frame_buffer = get_video_surface();
|
|
|
|
const bool full_redraw = resized_ || need_layout_;
|
|
|
|
if(resized_) {
|
|
// Restore old surface.
|
|
if(restorer_) {
|
|
SDL_Rect rect = get_rect();
|
|
SDL_BlitSurface(restorer_, 0, frame_buffer, &rect);
|
|
}
|
|
|
|
layout();
|
|
|
|
// Get new surface
|
|
SDL_Rect rect = get_rect();
|
|
restorer_ = get_surface_portion(video_.getSurface(), rect);
|
|
window_ = make_neutral_surface(restorer_); // should be copy surface...
|
|
|
|
resized_ = false;
|
|
}
|
|
assert(window_ && restorer_);
|
|
|
|
if(need_layout_) {
|
|
layout();
|
|
}
|
|
|
|
if(full_redraw) {
|
|
canvas(0).draw();
|
|
blit_surface(canvas(0).surf(), 0, window_, 0);
|
|
}
|
|
|
|
for(tgrid::iterator itor = begin(); itor != end(); ++itor) {
|
|
if(! *itor || !itor->is_dirty()) {
|
|
continue;
|
|
}
|
|
|
|
log_scope2(gui_draw, "Window: draw child.");
|
|
|
|
itor->draw(window_, full_redraw, full_redraw);
|
|
}
|
|
|
|
if(full_redraw) {
|
|
canvas(1).draw();
|
|
blit_surface(canvas(1).surf(), 0, window_, 0);
|
|
}
|
|
|
|
if(tooltip_.is_dirty()) {
|
|
tooltip_.draw(window_);
|
|
}
|
|
|
|
if(help_popup_.is_dirty()) {
|
|
help_popup_.draw(window_);
|
|
}
|
|
|
|
// Floating label hack
|
|
font::draw_floating_labels(frame_buffer);
|
|
|
|
SDL_Rect rect = get_rect();
|
|
SDL_BlitSurface(window_, 0, frame_buffer, &rect);
|
|
update_rect(get_rect());
|
|
set_dirty(false);
|
|
|
|
cursor::draw(frame_buffer);
|
|
video_.flip();
|
|
cursor::undraw(frame_buffer);
|
|
// Floating hack part 2.
|
|
font::undraw_floating_labels(frame_buffer);
|
|
}
|
|
|
|
void twindow::window_resize(tevent_handler&,
|
|
const unsigned new_width, const unsigned new_height)
|
|
{
|
|
settings::screen_width = new_width;
|
|
settings::screen_height = new_height;
|
|
resized_ = true;
|
|
}
|
|
|
|
void twindow::key_press(tevent_handler& /*event_handler*/, bool& handled,
|
|
SDLKey key, SDLMod /*modifier*/, Uint16 /*unicode*/)
|
|
{
|
|
if(key == SDLK_KP_ENTER || key == SDLK_RETURN) {
|
|
set_retval(OK);
|
|
handled = true;
|
|
} else if(key == SDLK_ESCAPE) {
|
|
set_retval(CANCEL);
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
SDL_Rect twindow::get_client_rect() const
|
|
{
|
|
boost::intrusive_ptr<const twindow_definition::tresolution> conf =
|
|
boost::dynamic_pointer_cast<const twindow_definition::tresolution>(config());
|
|
assert(conf);
|
|
|
|
SDL_Rect result = get_rect();
|
|
result.x = conf->left_border;
|
|
result.y = conf->top_border;
|
|
result.w -= conf->left_border + conf->right_border;
|
|
result.h -= conf->top_border + conf->bottom_border;
|
|
|
|
// FIXME validate for an available client area.
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
void twindow::layout()
|
|
{
|
|
if(automatic_placement_) {
|
|
|
|
log_scope2(gui, "Window: Recalculate size");
|
|
|
|
tpoint size = get_best_size();
|
|
DBG_G << "Window best size : " << size << " screen size "
|
|
<< settings::screen_width << ',' << settings::screen_height << ".\n";
|
|
|
|
// If too big try it gracefully.
|
|
if(size.x > settings::screen_width
|
|
|| size.y > settings::screen_height) {
|
|
|
|
size = get_best_size(
|
|
tpoint(settings::screen_width, settings::screen_height));
|
|
DBG_G << "Window best size : After resize request : " << size << ".\n";
|
|
}
|
|
// If still to big, just resize.
|
|
size.x = size.x < static_cast<int>(settings::screen_width)
|
|
? size.x : static_cast<int>(settings::screen_width);
|
|
|
|
size.y = size.y < static_cast<int>(settings::screen_height)
|
|
? size.y : static_cast<int>(settings::screen_height);
|
|
|
|
DBG_G << "Window final size " << size << ".\n";
|
|
|
|
tpoint position(0, 0);
|
|
switch(horizontal_placement_) {
|
|
case tgrid::HORIZONTAL_ALIGN_LEFT :
|
|
// Do nothing
|
|
break;
|
|
case tgrid::HORIZONTAL_ALIGN_CENTER :
|
|
position.x = (settings::screen_width - size.x) / 2;
|
|
break;
|
|
case tgrid::HORIZONTAL_ALIGN_RIGHT :
|
|
position.x = settings::screen_width - size.x;
|
|
break;
|
|
default :
|
|
assert(false);
|
|
}
|
|
switch(vertical_placement_) {
|
|
case tgrid::VERTICAL_ALIGN_TOP :
|
|
// Do nothing
|
|
break;
|
|
case tgrid::VERTICAL_ALIGN_CENTER :
|
|
position.y = (settings::screen_height - size.y) / 2;
|
|
break;
|
|
case tgrid::VERTICAL_ALIGN_BOTTOM :
|
|
position.y = settings::screen_height - size.y;
|
|
break;
|
|
default :
|
|
assert(false);
|
|
}
|
|
|
|
set_size(create_rect(position, size));
|
|
} else {
|
|
game_logic::map_formula_callable variables;
|
|
variables.add("screen_width", variant(settings::screen_width));
|
|
variables.add("screen_height", variant(settings::screen_height));
|
|
|
|
set_size(::create_rect(
|
|
x_(variables), y_(variables), w_(variables), h_(variables)));
|
|
}
|
|
|
|
need_layout_ = false;
|
|
}
|
|
|
|
void twindow::do_show_tooltip(const tpoint& location, const t_string& tooltip)
|
|
{
|
|
DBG_G << "Showing tooltip message: '" << tooltip << "'.\n";
|
|
|
|
assert(!tooltip.empty());
|
|
|
|
twidget* widget = find_widget(location, true);
|
|
assert(widget);
|
|
|
|
const SDL_Rect widget_rect = widget->get_rect();
|
|
const SDL_Rect client_rect = get_client_rect();
|
|
|
|
tooltip_.set_label(tooltip);
|
|
const tpoint size = tooltip_.get_best_size();
|
|
|
|
SDL_Rect tooltip_rect = {0, 0, size.x, size.y};
|
|
|
|
// Find the best position to place the widget
|
|
if(widget_rect.y - size.y > 0) {
|
|
// put above
|
|
tooltip_rect.y = widget_rect.y - size.y;
|
|
} else {
|
|
//put below no test
|
|
tooltip_rect.y = widget_rect.y + widget_rect.h;
|
|
}
|
|
|
|
if(widget_rect.x + size.x < client_rect.w) {
|
|
// Directly above the mouse
|
|
tooltip_rect.x = widget_rect.x;
|
|
} else {
|
|
// shift left, no test
|
|
tooltip_rect.x = client_rect.w - size.x;
|
|
}
|
|
|
|
tooltip_.set_size(tooltip_rect);
|
|
tooltip_.set_visible();
|
|
}
|
|
|
|
void twindow::do_show_help_popup(const tpoint& location, const t_string& help_popup)
|
|
{
|
|
// Note copy past of twindow::do_show_tooltip except that the help may be empty.
|
|
DBG_G << "Showing help message: '" << help_popup << "'.\n";
|
|
|
|
if(help_popup.empty()) {
|
|
return;
|
|
}
|
|
twidget* widget = find_widget(location, true);
|
|
assert(widget);
|
|
|
|
const SDL_Rect widget_rect = widget->get_rect();
|
|
const SDL_Rect client_rect = get_client_rect();
|
|
|
|
help_popup_.set_label(help_popup);
|
|
const tpoint size = help_popup_.get_best_size();
|
|
|
|
SDL_Rect help_popup_rect = {0, 0, size.x, size.y};
|
|
|
|
// Find the best position to place the widget
|
|
if(widget_rect.y - size.y > 0) {
|
|
// put above
|
|
help_popup_rect.y = widget_rect.y - size.y;
|
|
} else {
|
|
//put below no test
|
|
help_popup_rect.y = widget_rect.y + widget_rect.h;
|
|
}
|
|
|
|
if(widget_rect.x + size.x < client_rect.w) {
|
|
// Directly above the mouse
|
|
help_popup_rect.x = widget_rect.x;
|
|
} else {
|
|
// shift left, no test
|
|
help_popup_rect.x = client_rect.w - size.x;
|
|
}
|
|
|
|
help_popup_.set_size(help_popup_rect);
|
|
help_popup_.set_visible();
|
|
}
|
|
|
|
void twindow::draw(surface& /*surf*/, const bool /*force*/,
|
|
const bool /*invalidate_background*/)
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
} // namespace gui2
|
|
|