Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
19b45a5
Display world screenshot previews and progress
provigz Dec 4, 2022
058899e
Move preview functionality to menus and menu items
provigz Dec 12, 2022
692d5a0
Merge branch 'master' into world-previews
provigz Apr 29, 2023
c38af90
FileSystemMenu now shows previews of images
provigz Apr 29, 2023
78094b3
Cast integers to floats when generating preview rect
provigz Apr 29, 2023
24dc82f
Cast world entries vector size to int
provigz Apr 29, 2023
5cd4f74
Merge remote-tracking branch 'supertux/master' into world-previews
provigz May 20, 2023
9be8c3d
Don't fade in/out previews when transitions are disabled
provigz May 20, 2023
bbd65fe
Merge branch 'master' into world-previews
provigz May 24, 2023
9a9d115
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Jul 27, 2023
c0ba8e5
Remove some header includes
provigz Jul 27, 2023
3c5ecec
Show progress percentage for worlds
provigz Aug 2, 2023
7cdf956
General code improvements
provigz Aug 5, 2023
062c452
Get level progress from savefile level tables
provigz Aug 5, 2023
827d922
Allow toggling world previews functionality
provigz Aug 5, 2023
1149313
Use unsigned integers for storing progress
provigz Aug 6, 2023
8a23ab0
Add `<sstream>` include
provigz Aug 6, 2023
91ea207
Do not draw menu item previews, if preview overlaps the menu
provigz Aug 6, 2023
17e2259
`WorldMapState` now loads/saves state for all sectors
provigz Aug 6, 2023
08efd94
Merge branch 'master' into world-previews
provigz Oct 27, 2023
3c421d5
Fix macro `;` warning
provigz Nov 18, 2023
41bed28
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Jul 13, 2024
492395c
Fix issues displaying world previews
provigz Jul 14, 2024
78df407
Code fixes [ci skip]
provigz Jul 14, 2024
d8a7c73
Align menu help text to screen center
provigz Jul 15, 2024
9612857
Menu item image preview size is now relative to screen size
provigz Jul 15, 2024
610a448
Calculate world progress percentage using perfect level states
provigz Jul 16, 2024
8763b68
Draw white border around previews
provigz Jul 16, 2024
026573e
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Nov 23, 2024
7a2edb1
Use `std::max` and `DrawingContext` size functions
provigz Nov 23, 2024
82ef7a9
Fix compilation error with `std::max`
provigz Nov 23, 2024
82564d4
Resize menu item preview by preserving its original aspect ratio
provigz Nov 24, 2024
4006ed8
Minor code improvements
provigz Nov 24, 2024
7030989
More minor code improvements
provigz Nov 24, 2024
7bfb703
Merge branch 'master' into world-previews
provigz Dec 14, 2024
828eb04
Level stats for a world without a preview are now also displayed to t…
provigz Apr 2, 2025
d8e4f8b
Don't count progress from temporary Editor levels/worldmaps
provigz Apr 2, 2025
a6367ff
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Apr 2, 2025
cf698d8
Merge branch 'SuperTux:master' into world-previews
provigz Apr 21, 2025
955e88e
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Aug 27, 2025
30792f1
Do not show "No preview data available" text, do not fade out invalid…
provigz Aug 27, 2025
211155e
`WorldPreviewMenu`: Only force previews if there is at least one worl…
provigz Aug 27, 2025
a4f4be5
Fix `WorldMapSector` referencing current sector when it shouldn't have
provigz Aug 27, 2025
247d32f
Allow marking level tiles on worldmap as cutscenes, do not count them…
provigz Aug 27, 2025
9860163
Count levels from unloaded referenced worldmaps via teleporters to th…
provigz Aug 27, 2025
60efe14
Do not take worldmap preview screenshots in Editor
provigz Aug 27, 2025
475aa60
Merge remote-tracking branch 'supertux/master' into world-previews
provigz Dec 14, 2025
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
133 changes: 124 additions & 9 deletions src/gui/menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,17 @@
#include "gui/menu_manager.hpp"
#include "gui/mousecursor.hpp"
#include "math/util.hpp"
#include "supertux/error_handler.hpp"
#include "supertux/gameconfig.hpp"
#include "supertux/globals.hpp"
#include "supertux/resources.hpp"
#include "video/drawing_context.hpp"
#include "video/renderer.hpp"
#include "video/surface.hpp"
#include "video/video_system.hpp"
#include "video/viewport.hpp"

#include "supertux/error_handler.hpp"
const float Menu::s_preview_fade_time = 0.1f;

Menu::Menu() :
m_pos(Vector(static_cast<float>(SCREEN_WIDTH) / 2.0f,
Expand All @@ -65,14 +67,45 @@ Menu::Menu() :
m_menu_help_height(0.0f),
m_items(),
m_arrange_left(0),
m_active_item(-1)
m_active_item(-1),
m_force_previews(false),
m_has_previews(false),
m_last_preview_item(-1),
m_last_preview_item_valid(false),
m_preview_fade_timer(),
m_preview_fade_active(false),
m_preview_fading_out(false)
{
m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0);
}

Menu::~Menu()
{
}

void
Menu::align_for_previews(float x_offset)
{
m_has_previews = m_force_previews;
if (!m_has_previews)
{
for (const auto& item : m_items)
{
if (item->get_preview())
{
m_has_previews = true;
break;
}
}
if (!m_has_previews)
return;
}

// Adjust center position to give space for displaying previews.
set_center_pos(static_cast<float>(SCREEN_WIDTH) / 2 - get_width() / 2 - x_offset,
static_cast<float>(SCREEN_HEIGHT) / 2);
}

/* Add an item to a menu */
MenuItem&
Menu::add_item(std::unique_ptr<MenuItem> new_item)
Expand Down Expand Up @@ -496,6 +529,8 @@ Menu::recalculate_position_and_size()

calculate_width();
calculate_height();

align_for_previews();
}

void
Expand Down Expand Up @@ -524,13 +559,12 @@ Menu::draw(DrawingContext& context)
const int text_width = static_cast<int>(Resources::normal_font->get_text_width(m_items[m_active_item]->get_help()));
const int text_height = static_cast<int>(Resources::normal_font->get_text_height(m_items[m_active_item]->get_help()));

const Rectf text_rect(m_pos.x - static_cast<float>(text_width) / 2.0f - 8.0f,
static_cast<float>(SCREEN_HEIGHT) - 48.0f - static_cast<float>(text_height) / 2.0f - 4.0f,
m_pos.x + static_cast<float>(text_width) / 2.0f + 8.0f,
static_cast<float>(SCREEN_HEIGHT) - 48.0f + static_cast<float>(text_height) / 2.0f + 4.0f);
const Rectf text_rect(context.get_width() / 2 - static_cast<float>(text_width) / 2.0f - 8.0f,
context.get_height() - 48.0f - static_cast<float>(text_height) / 2.0f - 4.0f,
context.get_width() / 2 + static_cast<float>(text_width) / 2.0f + 8.0f,
context.get_height() - 48.0f + static_cast<float>(text_height) / 2.0f + 4.0f);

context.color().draw_filled_rect(Rectf(text_rect.p1() - Vector(4,4),
text_rect.p2() + Vector(4,4)),
context.color().draw_filled_rect(text_rect.grown(4),
g_config->menuhelpbackcolor,
g_config->menuroundness + 4.f,
LAYER_GUI);
Expand All @@ -541,9 +575,90 @@ Menu::draw(DrawingContext& context)
LAYER_GUI);

context.color().draw_text(Resources::normal_font, m_items[m_active_item]->get_help(),
Vector(m_pos.x, static_cast<float>(SCREEN_HEIGHT) - 48.0f - static_cast<float>(text_height) / 2.0f),
Vector(context.get_width() / 2, context.get_height() - 48.0f - static_cast<float>(text_height) / 2.0f),
ALIGN_CENTER, LAYER_GUI);
}

if (m_has_previews) draw_preview(context);
}

void
Menu::draw_preview(DrawingContext& context)
{
bool valid_last_preview = last_preview_valid();

// Update fade.
if (m_active_item != m_last_preview_item && !m_preview_fade_active) // Index has changed, there is no current fade.
{
if (valid_last_preview || (m_force_previews && m_last_preview_item > -1 && m_last_preview_item_valid)) // Fade out only if the last index is valid.
m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0.f);
m_preview_fading_out = true;
m_preview_fade_active = true;
}
float timeleft = m_preview_fade_timer.get_timeleft();
if (timeleft < 0 && m_preview_fade_active) // Current fade is over.
{
// Update preview item.
m_last_preview_item = m_active_item;
m_last_preview_item_valid = is_preview_item_valid(*m_items[m_last_preview_item]);

valid_last_preview = last_preview_valid(); // Repeat valid last index check
if (m_preview_fading_out) // After a fade-out, a fade-in should follow up.
{
m_preview_fade_timer.start(g_config->transitions_enabled ? s_preview_fade_time : 0.f);
timeleft = m_preview_fade_timer.get_timeleft();
m_preview_fading_out = false;
}
else
{
m_preview_fade_active = false;
}
}

// Set alpha according to fade.
float alpha = 1.f;
if (timeleft > 0)
{
const float alpha_val = timeleft * (1.f / s_preview_fade_time);
alpha = m_preview_fading_out ? alpha_val : 1.f - alpha_val;
}

const Sizef preview_size(context.get_width() / 2.5f, context.get_height() / 2.5f);
Rectf preview_rect(Vector(context.get_width() * 0.73f - preview_size.width / 2,
context.get_height() / 2 - preview_size.height / 2),
Sizef(preview_size.width, preview_size.height / 2));

// Perform actions only if current preview is valid.
if (valid_last_preview)
{
// Draw progress preview of current item.
SurfacePtr preview = m_items[m_last_preview_item]->get_preview();
preview_rect.set_size(static_cast<float>(preview->get_width()),
static_cast<float>(preview->get_height()));
preview_rect.fit_centered(preview_size);

PaintStyle style;
style.set_alpha(alpha);
context.color().draw_surface_scaled(preview, preview_rect, LAYER_GUI + 1, style);

// Draw a border around the preview.
context.color().draw_filled_rect(preview_rect.grown(2.f),
Color(1.f, 1.f, 1.f, alpha), 2.f, LAYER_GUI);

// Draw other data, alongside the preview, if available.
draw_preview_data(context, *m_items[m_last_preview_item], preview_rect, alpha);
}
else if (m_force_previews && m_last_preview_item > -1 && m_last_preview_item_valid)
{
// Draw other data, if available.
draw_preview_data(context, *m_items[m_last_preview_item], preview_rect, alpha);
}
}

bool
Menu::last_preview_valid() const
{
return m_last_preview_item > -1 && m_last_preview_item_valid && m_items[m_last_preview_item]->get_preview();
}

MenuItem&
Expand Down
27 changes: 27 additions & 0 deletions src/gui/menu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
#include <SDL.h>

#include "gui/menu_action.hpp"
#include "math/rectf.hpp"
#include "math/vector.hpp"
#include "supertux/timer.hpp"
#include "video/color.hpp"
#include "video/drawing_context.hpp"

Expand Down Expand Up @@ -52,6 +54,9 @@ class PathObject;

class Menu
{
protected:
static const float s_preview_fade_time;

public:
Menu();
virtual ~Menu();
Expand Down Expand Up @@ -112,6 +117,9 @@ class Menu
/** Remove all entries from the menu */
void clear();

/** Align the menu to the left side, if any previews are available. */
void align_for_previews(float x_offset = 30.f);

MenuItem& get_item(int index) { return *(m_items[index]); }

MenuItem& get_item_by_id(int id);
Expand Down Expand Up @@ -147,10 +155,17 @@ class Menu
/** Recalculates the height for this menu */
void calculate_height();

/** Draw additional data to accompany item previews. */
virtual void draw_preview_data(DrawingContext& context, const MenuItem& item, const Rectf& preview_rect, float alpha) {}

virtual bool is_preview_item_valid(const MenuItem& item) const { return true; }

private:
void recalculate_position_and_size();
void check_controlfield_change_event(const SDL_Event& event);
void draw_item(DrawingContext& context, int index, float y_pos);
void draw_preview(DrawingContext& context);
bool last_preview_valid() const;

private:
/** position of the menu (ie. center of the menu, not top/left) */
Expand All @@ -172,6 +187,18 @@ class Menu
protected:
int m_active_item;

/** Set this to true if the menu should be aligned for and always draw preview data, even if no item preview is available */
bool m_force_previews;

private:
/* Preview implementation variables. */
bool m_has_previews;
int m_last_preview_item;
bool m_last_preview_item_valid;
Timer m_preview_fade_timer;
bool m_preview_fade_active;
bool m_preview_fading_out;

private:
Menu(const Menu&) = delete;
Menu& operator=(const Menu&) = delete;
Expand Down
31 changes: 26 additions & 5 deletions src/gui/menu_filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include "util/gettext.hpp"
#include "util/string_util.hpp"

static const size_t MAX_TITLE_CHARS = 30;
static const std::vector<std::string> IMAGE_EXTENSIONS = { ".jpg", ".png", ".surface" };
Copy link
Member

Choose a reason for hiding this comment

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

std array or just a C style array. Try std stringview


FileSystemMenu::FileSystemMenu(std::string* filename, const std::vector<std::string>& extensions,
const std::string& basedir, bool path_relative_to_basedir, std::function<void(const std::string&)> callback,
const std::function<void (MenuItem&)>& item_processor) :
Expand Down Expand Up @@ -65,7 +68,13 @@ FileSystemMenu::refresh_items()
m_files.clear();
m_directory = FileSystem::normalize(m_directory);

add_label(m_directory);
// Make sure label doesn't get too long.
std::string title = m_directory;
const bool title_large = title.size() > MAX_TITLE_CHARS;
while (title.size() > MAX_TITLE_CHARS)
title = title.substr(title.size() - MAX_TITLE_CHARS);

add_label((title_large ? "..." : "") + title);
add_hl();

int item_id = 0;
Expand Down Expand Up @@ -109,8 +118,11 @@ FileSystemMenu::refresh_items()
for (const auto& item : m_files)
{
MenuItem& menu_item = add_entry(item_id, item);

if (in_basedir && m_item_processor)
m_item_processor(menu_item);
if (is_image(item))
menu_item.set_preview(FileSystem::join(m_directory, item));

item_id++;
}
Expand All @@ -124,6 +136,7 @@ FileSystemMenu::refresh_items()

// Re-center menu
on_window_resize();
align_for_previews(25.f);
}

bool
Expand All @@ -132,12 +145,20 @@ FileSystemMenu::has_right_suffix(const std::string& file) const
if (m_extensions.empty())
return true;

for (const auto& extension : m_extensions) {
for (const auto& extension : m_extensions)
if (StringUtil::has_suffix(file, extension))
{
return true;
}
}

return false;
}

bool
FileSystemMenu::is_image(const std::string& file) const
Copy link
Member

Choose a reason for hiding this comment

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

smells like unsound logic anyway. sdl image might have something to check this? try running it through and seeing if a valid surface is returned if it doesnt.

Copy link
Member

Choose a reason for hiding this comment

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

granted, thats kinda rare. plus, what about .jpeg and whatever? maybe im looking at this wrong

{
for (const auto& extension : IMAGE_EXTENSIONS)
if (StringUtil::has_suffix(file, extension))
return true;

return false;
}

Expand Down
1 change: 1 addition & 0 deletions src/gui/menu_filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class FileSystemMenu final : public Menu
private:
void refresh_items();
bool has_right_suffix(const std::string& file) const;
bool is_image(const std::string& file) const;

private:
std::string* m_filename;
Expand Down
10 changes: 9 additions & 1 deletion src/gui/menu_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "supertux/globals.hpp"
#include "supertux/resources.hpp"
#include "video/drawing_context.hpp"
#include "video/surface.hpp"

static const float HELP_TEXT_WIDTH = 800.f;

Expand All @@ -30,7 +31,8 @@ MenuItem::MenuItem(const std::string& text, int id, const std::optional<Color>&
m_text(text),
m_help(),
m_font(Resources::normal_font),
m_text_color(text_color)
m_text_color(text_color),
m_preview()
{
}

Expand All @@ -50,6 +52,12 @@ MenuItem::set_help(const std::string& help_text)
}
}

void
MenuItem::set_preview(const std::string& preview_file)
{
m_preview = Surface::from_file(preview_file);
Copy link
Member

Choose a reason for hiding this comment

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

error check

}

void
MenuItem::draw(DrawingContext& context, const Vector& pos, int menu_width, bool active)
{
Expand Down
7 changes: 7 additions & 0 deletions src/gui/menu_item.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include "gui/menu.hpp"
#include <optional>

#include "video/surface_ptr.hpp"

class MenuItem
{
public:
Expand All @@ -37,6 +39,10 @@ class MenuItem
inline void set_font(const FontPtr font) { m_font = font; }
inline const FontPtr& get_font() const { return m_font; }

void set_preview(const std::string& preview_file);
inline void set_preview(SurfacePtr preview) { m_preview = preview; }
inline SurfacePtr get_preview() const { return m_preview; }

/** Draws the menu item. */
virtual void draw(DrawingContext&, const Vector& pos, int menu_width, bool active);

Expand Down Expand Up @@ -88,6 +94,7 @@ class MenuItem
std::string m_help;
FontPtr m_font;
std::optional<Color> m_text_color;
SurfacePtr m_preview;

private:
MenuItem(const MenuItem&) = delete;
Expand Down
Loading
Loading