|
@@ -344,282 +344,282 @@ protected:
|
|
|
case SPET_BUTTON_PUSH:
|
|
|
case SPET_BUTTON_TILE:
|
|
|
case SPET_BUTTON_VEHICLE: {
|
|
|
Dimension dim = GetStringBoundingBox(pe.text, FS_NORMAL);
|
|
|
return dim.height + WD_BEVEL_TOP + WD_BEVEL_BOTTOM + WD_FRAMETEXT_TOP + WD_FRAMETEXT_BOTTOM;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
NOT_REACHED();
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get the float style of a page element.
|
|
|
* @param pe The story page element.
|
|
|
* @return The float style.
|
|
|
*/
|
|
|
ElementFloat GetPageElementFloat(const StoryPageElement &pe) const
|
|
|
{
|
|
|
switch (pe.type) {
|
|
|
case SPET_BUTTON_PUSH:
|
|
|
case SPET_BUTTON_TILE:
|
|
|
case SPET_BUTTON_VEHICLE: {
|
|
|
StoryPageButtonFlags flags = StoryPageButtonData{ pe.referenced_id }.GetFlags();
|
|
|
if (flags & SPBF_FLOAT_LEFT) return ElementFloat::Left;
|
|
|
if (flags & SPBF_FLOAT_RIGHT) return ElementFloat::Right;
|
|
|
return ElementFloat::None;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
return ElementFloat::None;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get the width a page element would use if it was floating left or right.
|
|
|
* @param pe The story page element.
|
|
|
* @return The calculated width of the element.
|
|
|
*/
|
|
|
int GetPageElementFloatWidth(const StoryPageElement &pe) const
|
|
|
{
|
|
|
switch (pe.type) {
|
|
|
case SPET_BUTTON_PUSH:
|
|
|
case SPET_BUTTON_TILE:
|
|
|
case SPET_BUTTON_VEHICLE: {
|
|
|
Dimension dim = GetStringBoundingBox(pe.text, FS_NORMAL);
|
|
|
return dim.width + WD_BEVEL_LEFT + WD_BEVEL_RIGHT + WD_FRAMETEXT_LEFT + WD_FRAMETEXT_RIGHT;
|
|
|
}
|
|
|
|
|
|
default:
|
|
|
NOT_REACHED(); // only buttons can float
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** Invalidate the current page layout */
|
|
|
void InvalidateStoryPageElementLayout()
|
|
|
{
|
|
|
this->layout_cache.clear();
|
|
|
}
|
|
|
|
|
|
/** Create the page layout if it is missing */
|
|
|
void EnsureStoryPageElementLayout() const
|
|
|
{
|
|
|
/* Assume if the layout cache has contents it is valid */
|
|
|
if (!this->layout_cache.empty()) return;
|
|
|
|
|
|
StoryPage *page = this->GetSelPage();
|
|
|
if (page == nullptr) return;
|
|
|
int max_width = GetAvailablePageContentWidth();
|
|
|
int element_dist = FONT_HEIGHT_NORMAL;
|
|
|
|
|
|
/* Make space for the header */
|
|
|
int main_y = GetHeadHeight(max_width) + element_dist;
|
|
|
|
|
|
/* Current bottom of left/right column */
|
|
|
int left_y = main_y;
|
|
|
int right_y = main_y;
|
|
|
/* Current width of left/right column, 0 indicates no content in column */
|
|
|
int left_width = 0;
|
|
|
int right_width = 0;
|
|
|
/* Indexes into element cache for yet unresolved floats */
|
|
|
std::vector<size_t> left_floats;
|
|
|
std::vector<size_t> right_floats;
|
|
|
|
|
|
/* Build layout */
|
|
|
for (const StoryPageElement *pe : this->story_page_elements) {
|
|
|
ElementFloat fl = this->GetPageElementFloat(*pe);
|
|
|
|
|
|
if (fl == ElementFloat::None) {
|
|
|
/* Verify available width */
|
|
|
const int min_required_width = 10 * FONT_HEIGHT_NORMAL;
|
|
|
int left_offset = (left_width == 0) ? 0 : (left_width + element_dist);
|
|
|
int right_offset = (right_width == 0) ? 0 : (right_width + element_dist);
|
|
|
if (left_offset + right_offset + min_required_width >= max_width) {
|
|
|
/* Width of floats leave too little for main content, push down */
|
|
|
main_y = max(main_y, left_y);
|
|
|
main_y = max(main_y, right_y);
|
|
|
main_y = std::max(main_y, left_y);
|
|
|
main_y = std::max(main_y, right_y);
|
|
|
left_width = right_width = 0;
|
|
|
left_offset = right_offset = 0;
|
|
|
/* Do not add element_dist here, to keep together elements which were supposed to float besides each other. */
|
|
|
}
|
|
|
/* Determine height */
|
|
|
const int available_width = max_width - left_offset - right_offset;
|
|
|
const int height = GetPageElementHeight(*pe, available_width);
|
|
|
/* Check for button that needs extra margin */
|
|
|
if (left_offset == 0 && right_offset == 0) {
|
|
|
switch (pe->type) {
|
|
|
case SPET_BUTTON_PUSH:
|
|
|
case SPET_BUTTON_TILE:
|
|
|
case SPET_BUTTON_VEHICLE:
|
|
|
left_offset = right_offset = available_width / 5;
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
/* Position element in main column */
|
|
|
LayoutCacheElement ce{ pe, {} };
|
|
|
ce.bounds.left = left_offset;
|
|
|
ce.bounds.right = max_width - right_offset;
|
|
|
ce.bounds.top = main_y;
|
|
|
main_y += height;
|
|
|
ce.bounds.bottom = main_y;
|
|
|
this->layout_cache.push_back(ce);
|
|
|
main_y += element_dist;
|
|
|
/* Clear all floats */
|
|
|
left_width = right_width = 0;
|
|
|
left_y = right_y = main_y = max(main_y, max(left_y, right_y));
|
|
|
left_y = right_y = main_y = std::max({main_y, left_y, right_y});
|
|
|
left_floats.clear();
|
|
|
right_floats.clear();
|
|
|
} else {
|
|
|
/* Prepare references to correct column */
|
|
|
int &cur_width = (fl == ElementFloat::Left) ? left_width : right_width;
|
|
|
int &cur_y = (fl == ElementFloat::Left) ? left_y : right_y;
|
|
|
std::vector<size_t> &cur_floats = (fl == ElementFloat::Left) ? left_floats : right_floats;
|
|
|
/* Position element */
|
|
|
cur_width = max(cur_width, this->GetPageElementFloatWidth(*pe));
|
|
|
cur_width = std::max(cur_width, this->GetPageElementFloatWidth(*pe));
|
|
|
LayoutCacheElement ce{ pe, {} };
|
|
|
ce.bounds.left = (fl == ElementFloat::Left) ? 0 : (max_width - cur_width);
|
|
|
ce.bounds.right = (fl == ElementFloat::Left) ? cur_width : max_width;
|
|
|
ce.bounds.top = cur_y;
|
|
|
cur_y += GetPageElementHeight(*pe, cur_width);
|
|
|
ce.bounds.bottom = cur_y;
|
|
|
cur_floats.push_back(this->layout_cache.size());
|
|
|
this->layout_cache.push_back(ce);
|
|
|
cur_y += element_dist;
|
|
|
/* Update floats in column to all have the same width */
|
|
|
for (size_t index : cur_floats) {
|
|
|
LayoutCacheElement &ce = this->layout_cache[index];
|
|
|
ce.bounds.left = (fl == ElementFloat::Left) ? 0 : (max_width - cur_width);
|
|
|
ce.bounds.right = (fl == ElementFloat::Left) ? cur_width : max_width;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get the total height of the content displayed in this window.
|
|
|
* @return the height in pixels
|
|
|
*/
|
|
|
uint GetContentHeight()
|
|
|
{
|
|
|
this->EnsureStoryPageElementLayout();
|
|
|
|
|
|
/* The largest bottom coordinate of any element is the height of the content */
|
|
|
uint max_y = std::accumulate(this->layout_cache.begin(), this->layout_cache.end(), 0, [](uint max_y, const LayoutCacheElement &ce) -> uint { return max<uint>(max_y, ce.bounds.bottom); });
|
|
|
uint max_y = std::accumulate(this->layout_cache.begin(), this->layout_cache.end(), 0, [](uint max_y, const LayoutCacheElement &ce) -> uint { return std::max<uint>(max_y, ce.bounds.bottom); });
|
|
|
|
|
|
return max_y;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Draws a page element that is composed of a sprite to the left and a single line of
|
|
|
* text after that. These page elements are generally clickable and are thus called
|
|
|
* action elements.
|
|
|
* @param y_offset Current y_offset which will get updated when this method has completed its drawing.
|
|
|
* @param width Width of the region available for drawing.
|
|
|
* @param line_height Height of one line of text.
|
|
|
* @param action_sprite The sprite to draw.
|
|
|
* @param string_id The string id to draw.
|
|
|
* @return the number of lines.
|
|
|
*/
|
|
|
void DrawActionElement(int &y_offset, int width, int line_height, SpriteID action_sprite, StringID string_id = STR_JUST_RAW_STRING) const
|
|
|
{
|
|
|
Dimension sprite_dim = GetSpriteSize(action_sprite);
|
|
|
uint element_height = max(sprite_dim.height, (uint)line_height);
|
|
|
uint element_height = std::max(sprite_dim.height, (uint)line_height);
|
|
|
|
|
|
uint sprite_top = y_offset + (element_height - sprite_dim.height) / 2;
|
|
|
uint text_top = y_offset + (element_height - line_height) / 2;
|
|
|
|
|
|
DrawSprite(action_sprite, PAL_NONE, 0, sprite_top);
|
|
|
DrawString(sprite_dim.width + WD_FRAMETEXT_LEFT, width, text_top, string_id, TC_BLACK);
|
|
|
|
|
|
y_offset += element_height;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Internal event handler for when a page element is clicked.
|
|
|
* @param pe The clicked page element.
|
|
|
*/
|
|
|
void OnPageElementClick(const StoryPageElement& pe)
|
|
|
{
|
|
|
switch (pe.type) {
|
|
|
case SPET_TEXT:
|
|
|
/* Do nothing. */
|
|
|
break;
|
|
|
|
|
|
case SPET_LOCATION:
|
|
|
if (_ctrl_pressed) {
|
|
|
ShowExtraViewportWindow((TileIndex)pe.referenced_id);
|
|
|
} else {
|
|
|
ScrollMainWindowToTile((TileIndex)pe.referenced_id);
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
case SPET_GOAL:
|
|
|
ShowGoalsList((CompanyID)this->window_number);
|
|
|
break;
|
|
|
|
|
|
case SPET_BUTTON_PUSH:
|
|
|
if (this->active_button_id != INVALID_STORY_PAGE_ELEMENT) ResetObjectToPlace();
|
|
|
this->active_button_id = pe.index;
|
|
|
this->SetTimeout();
|
|
|
this->SetWidgetDirty(WID_SB_PAGE_PANEL);
|
|
|
|
|
|
DoCommandP(0, pe.index, 0, CMD_STORY_PAGE_BUTTON);
|
|
|
break;
|
|
|
|
|
|
case SPET_BUTTON_TILE:
|
|
|
if (this->active_button_id == pe.index) {
|
|
|
ResetObjectToPlace();
|
|
|
this->active_button_id = INVALID_STORY_PAGE_ELEMENT;
|
|
|
} else {
|
|
|
CursorID cursor = TranslateStoryPageButtonCursor(StoryPageButtonData{ pe.referenced_id }.GetCursor());
|
|
|
SetObjectToPlaceWnd(cursor, PAL_NONE, HT_RECT, this);
|
|
|
this->active_button_id = pe.index;
|
|
|
}
|
|
|
this->SetWidgetDirty(WID_SB_PAGE_PANEL);
|
|
|
break;
|
|
|
|
|
|
case SPET_BUTTON_VEHICLE:
|
|
|
if (this->active_button_id == pe.index) {
|
|
|
ResetObjectToPlace();
|
|
|
this->active_button_id = INVALID_STORY_PAGE_ELEMENT;
|
|
|
} else {
|
|
|
CursorID cursor = TranslateStoryPageButtonCursor(StoryPageButtonData{ pe.referenced_id }.GetCursor());
|
|
|
SetObjectToPlaceWnd(cursor, PAL_NONE, HT_VEHICLE, this);
|
|
|
this->active_button_id = pe.index;
|
|
|
}
|
|
|
this->SetWidgetDirty(WID_SB_PAGE_PANEL);
|
|
|
break;
|
|
|
|
|
|
default:
|
|
|
NOT_REACHED();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public:
|
|
|
StoryBookWindow(WindowDesc *desc, WindowNumber window_number) : Window(desc)
|
|
|
{
|
|
|
this->CreateNestedTree();
|
|
|
this->vscroll = this->GetScrollbar(WID_SB_SCROLLBAR);
|
|
|
this->vscroll->SetStepSize(FONT_HEIGHT_NORMAL);
|
|
|
|
|
|
/* Initialize page sort. */
|
|
|
this->story_pages.SetSortFuncs(StoryBookWindow::page_sorter_funcs);
|
|
|
this->story_pages.ForceRebuild();
|
|
|
this->BuildStoryPageList();
|
|
|
this->story_page_elements.SetSortFuncs(StoryBookWindow::page_element_sorter_funcs);
|
|
|
/* story_page_elements will get built by SetSelectedPage */
|
|
|
|
|
|
this->FinishInitNested(window_number);
|
|
|
this->owner = (Owner)this->window_number;
|
|
|
|
|
|
/* Initialize selected vars. */
|
|
|
this->selected_generic_title[0] = '\0';
|
|
|
this->selected_page_id = INVALID_STORY_PAGE;
|
|
|
|
|
|
this->active_button_id = INVALID_STORY_PAGE_ELEMENT;
|
|
|
|
|
|
this->OnInvalidateData(-1);
|
|
|
}
|