Added save/open file dialogs

This commit is contained in:
OmniaX-Dev 2026-04-21 15:28:39 +02:00
parent 8c174fb863
commit 83f4687198
8 changed files with 163 additions and 21 deletions

View file

@ -160,17 +160,8 @@ window.backgroundColor = rgba(160, 160, 160, 255)
useBackgroundGradient = true useBackgroundGradient = true
padding = rect(15, 8, 15, 8) padding = rect(15, 8, 15, 8)
margin = rect(0, 0, 0, 0) margin = rect(0, 0, 0, 0)
showIcon = true showIcon = false
(icon) { (icon) {
path = file("img.png")
animation = anim(frameCount: 36, \
fps: 60, \
columns: 9, \
rows: 4, \
frameWidth: 256, \
frameHeight: 256)
size = vec2(32, 32)
animated = true
} }
} }
(button:hover) { (button:hover) {

View file

@ -41,21 +41,21 @@ Implement following widgets:
***Tab Panel ***Tab Panel
***Button ***Button
***Image/Icon ***Image/Icon
***Save/Open File Dialogs (and folder dialogs)
Radio Button Radio Button
Grouping Grouping
Text Input Progressbar
Horizontal Slider Horizontal Slider
Vertical Slider Vertical Slider
ListBox ListBox
ComboBox ComboBox
TreeView Tooltips
Progressbar
Context Menu Context Menu
Menu Bar Menu Bar
Save/Open File Dialogs (and folder dialogs)
Hex Editor
Layouts Layouts
Vertical Vertical
Horizontal Horizontal
Grid Grid
Tooltips Text Input
TreeView
Hex Editor

View file

@ -369,6 +369,105 @@ namespace ogfx
__on_signal(signal); __on_signal(signal);
} }
stdvec<String> WindowCore::showOpenFileDialog(const stdvec<FileDialogFilter>& filterList, bool multiselect, const String& defaultPath) const
{
struct DialogResult {
stdvec<String> paths;
bool completed { false };
};
DialogResult result;
__store_file_dialog_filters(filterList);
SDL_ShowOpenFileDialog(
[](void* userdata, const char* const* filelist, int filter) {
auto* res = static_cast<DialogResult*>(userdata);
if (filelist) {
for (const char* const* f = filelist; *f; f++)
res->paths.push_back(String(*f));
}
res->completed = true;
},
&result,
m_window,
m_sdlFilters.empty() ? nullptr : m_sdlFilters.data(),
m_sdlFilters.size(),
defaultPath.empty() ? nullptr : defaultPath.c_str(),
multiselect
);
while (!result.completed) {
SDL_PumpEvents();
SDL_Delay(10);
}
return result.paths;
}
stdvec<String> WindowCore::showOpenFolderDialog(bool multiselect, const String& defaultPath) const
{
struct DialogResult {
stdvec<String> paths;
bool completed { false };
};
DialogResult result;
SDL_ShowOpenFolderDialog(
[](void* userdata, const char* const* filelist, int filter) {
auto* res = static_cast<DialogResult*>(userdata);
if (filelist) {
for (const char* const* f = filelist; *f; f++)
res->paths.push_back(String(*f));
}
res->completed = true;
},
&result,
m_window,
defaultPath.empty() ? nullptr : defaultPath.c_str(),
multiselect
);
while (!result.completed) {
SDL_PumpEvents();
SDL_Delay(10);
}
return result.paths;
}
String WindowCore::showSaveFileDialog(const stdvec<FileDialogFilter>& filterList, const String& defaultPath) const
{
struct DialogResult {
String path;
bool completed { false };
};
DialogResult result;
__store_file_dialog_filters(filterList);
SDL_ShowSaveFileDialog(
[](void* userdata, const char* const* filelist, int filter) {
auto* res = static_cast<DialogResult*>(userdata);
if (filelist && *filelist) {
res->path = String(*filelist);
}
res->completed = true;
},
&result,
m_window,
m_sdlFilters.empty() ? nullptr : m_sdlFilters.data(),
m_sdlFilters.size(),
defaultPath.empty() ? nullptr : defaultPath.c_str()
);
while (!result.completed) {
SDL_PumpEvents();
SDL_Delay(10);
}
return result.path;
}
MouseEventData WindowCore::get_mouse_state(SDL_Event& event) MouseEventData WindowCore::get_mouse_state(SDL_Event& event)
{ {
f32 mx = 0, my = 0, _mx = 0, _my = 0; f32 mx = 0, my = 0, _mx = 0, _my = 0;
@ -637,6 +736,32 @@ namespace ogfx
m_defaultStylesheetVariables["text_left"] = { String("").add(cast<i32>(eTextAlign::Left)), true }; m_defaultStylesheetVariables["text_left"] = { String("").add(cast<i32>(eTextAlign::Left)), true };
} }
void WindowCore::__store_file_dialog_filters(const stdvec<FileDialogFilter>& filters) const
{
m_patternStorage.clear();
m_sdlFilters.clear();
m_patternStorage.reserve(filters.size());
m_sdlFilters.reserve(filters.size());
for (const auto& f : filters)
{
// Join extensions with semicolons: {"png", "jpg"} -> "png;jpg"
String pattern;
for (size_t i = 0; i < f.extensionList.size(); i++)
{
if (i > 0) pattern.add(";");
pattern.add(f.extensionList[i]);
}
m_patternStorage.push_back(pattern);
// SDL filter points at strings that must stay alive
m_sdlFilters.push_back({
f.description.c_str(),
m_patternStorage.back().c_str()
});
}
}

View file

@ -34,6 +34,11 @@ namespace ogfx
{ {
class WindowCore : public ostd::BaseObject class WindowCore : public ostd::BaseObject
{ {
public: struct FileDialogFilter
{
String description { "" };
stdvec<String> extensionList;
};
public: enum class eCursor : u8 public: enum class eCursor : u8
{ {
Default = 0, Default = 0,
@ -79,6 +84,7 @@ namespace ogfx
bool maximized { false }; bool maximized { false };
bool alwaysOnTop { false }; bool alwaysOnTop { false };
}; };
public: using FileDialogFilterList = stdvec<ogfx::WindowCore::FileDialogFilter>;
public: public:
inline WindowCore(void) { } inline WindowCore(void) { }
virtual ~WindowCore(void); virtual ~WindowCore(void);
@ -99,7 +105,12 @@ namespace ogfx
void setBlockingEventsRefreshFPS(u32 fps); void setBlockingEventsRefreshFPS(u32 fps);
void requestRedraw(void); void requestRedraw(void);
void handleSignal(ostd::Signal& signal) override; void handleSignal(ostd::Signal& signal) override;
stdvec<String> showOpenFileDialog(const FileDialogFilterList& filterList, bool multiselect, const String& defaultPath = "") const;
stdvec<String> showOpenFolderDialog(bool multiselect, const String& defaultPath = "") const;
String showSaveFileDialog(const FileDialogFilterList& filterList, const String& defaultPath = "") const;
inline String showOpenFileDialog(const FileDialogFilterList& filterList) const { auto list = showOpenFileDialog(filterList, false); return (list.size() == 0 ? "" : list[0]); }
inline String showOpenFolderDialog(void) const { auto list = showOpenFolderDialog(false); return (list.size() == 0 ? "" : list[0]); }
inline const ostd::Stylesheet* theme(void) const { return m_guiTheme; } inline const ostd::Stylesheet* theme(void) const { return m_guiTheme; }
inline void loadDefaultTHeme(void) { setTheme(DefaultTheme); } inline void loadDefaultTHeme(void) { setTheme(DefaultTheme); }
inline ostd::Stylesheet::VariableList& getDefaultStylesheetVariableList(void) { return m_defaultStylesheetVariables; } inline ostd::Stylesheet::VariableList& getDefaultStylesheetVariableList(void) { return m_defaultStylesheetVariables; }
@ -142,6 +153,7 @@ namespace ogfx
private: private:
void __handle_event(SDL_Event& event); void __handle_event(SDL_Event& event);
void __load_default_stylesheet_variables(void); void __load_default_stylesheet_variables(void);
void __store_file_dialog_filters(const FileDialogFilterList& filters) const;
protected: protected:
SDL_Window* m_window { nullptr }; SDL_Window* m_window { nullptr };
@ -191,6 +203,9 @@ namespace ogfx
SDL_Cursor* m_cursor_SW_Resize { nullptr }; SDL_Cursor* m_cursor_SW_Resize { nullptr };
SDL_Cursor* m_cursor_W_Resize { nullptr }; SDL_Cursor* m_cursor_W_Resize { nullptr };
mutable stdvec<String> m_patternStorage;
mutable stdvec<SDL_DialogFileFilter> m_sdlFilters;
public: public:
inline static constexpr i32 MaxBlockingEventsFPS { 240 }; inline static constexpr i32 MaxBlockingEventsFPS { 240 };
inline static constexpr i32 DefaultBlockingEventsFPS { 30 }; inline static constexpr i32 DefaultBlockingEventsFPS { 30 };

View file

@ -43,11 +43,10 @@ namespace ogfx
void Button::onDraw(ogfx::BasicRenderer2D& gfx) void Button::onDraw(ogfx::BasicRenderer2D& gfx)
{ {
// if (m_textChanged) if (m_textChanged)
__update_size(); __update_size();
if (isIconEnabled()) if (isIconEnabled())
{ {
m_realIconSize = getIconSize().propy(getGlobalContentBounds().getSize().y);
if (isAnimatedEnabled()) if (isAnimatedEnabled())
gfx.drawAnimation(m_anim, getGlobalContentPosition(), m_realIconSize); gfx.drawAnimation(m_anim, getGlobalContentPosition(), m_realIconSize);
@ -88,7 +87,7 @@ namespace ogfx
String filePath = getThemeValue<String>(theme, "icon.path", m_icon.getFilePath()); String filePath = getThemeValue<String>(theme, "icon.path", m_icon.getFilePath());
if (filePath != m_icon.getFilePath()) if (filePath != m_icon.getFilePath())
setIcon(filePath); setIcon(filePath);
setIconSize(getThemeValue<Vec2>(theme, "icon.size", getSize())); setIconSize(getThemeValue<Vec2>(theme, "icon.size", getIconSize()));
if (isAnimatedEnabled()) if (isAnimatedEnabled())
{ {
m_anim.create(m_animData); m_anim.create(m_animData);
@ -99,6 +98,7 @@ namespace ogfx
void Button::__update_size(void) void Button::__update_size(void)
{ {
m_realIconSize = getIconSize().propy(getGlobalContentBounds().getSize().y);
auto size = getWindow().getGFX().getStringDimensions(getText(), getFontSize()); auto size = getWindow().getGFX().getStringDimensions(getText(), getFontSize());
size.x += getPadding().left(); size.x += getPadding().left();
size.x += getPadding().right(); size.x += getPadding().right();

View file

@ -41,9 +41,9 @@ namespace ogfx
void setText(const String& text); void setText(const String& text);
void setIcon(const String& filePath); void setIcon(const String& filePath);
void applyTheme(const ostd::Stylesheet& theme) override; void applyTheme(const ostd::Stylesheet& theme) override;
OSTD_BOOL_PARAM_GETSET_E(Animated, m_animated);
inline String getText(void) const { return m_text; } inline String getText(void) const { return m_text; }
inline Image& getIcon(void) { return m_icon; } inline Image& getIcon(void) { return m_icon; }
OSTD_BOOL_PARAM_GETSET_E(Animated, m_animated);
OSTD_BOOL_PARAM_GETSET_E(Icon, m_showIcon); OSTD_BOOL_PARAM_GETSET_E(Icon, m_showIcon);
OSTD_PARAM_GETSET(ostd::AnimationData, AnimationData, m_animData); OSTD_PARAM_GETSET(ostd::AnimationData, AnimationData, m_animData);
OSTD_PARAM_GETSET(Vec2, IconSize, m_iconSize); OSTD_PARAM_GETSET(Vec2, IconSize, m_iconSize);

View file

@ -84,6 +84,7 @@ namespace ostd
inline String& clr(void) { m_data = ""; return *this; } inline String& clr(void) { m_data = ""; return *this; }
inline String& set(const cpp_string& str) { m_data = str; return *this; } inline String& set(const cpp_string& str) { m_data = str; return *this; }
inline stdvec<u32> getUTF8Codepoints(void) const { return decodeUTF8(m_data); } inline stdvec<u32> getUTF8Codepoints(void) const { return decodeUTF8(m_data); }
inline bool empty(void) const { return m_data.empty(); }
inline auto begin(void) { return m_data.begin(); } inline auto begin(void) { return m_data.begin(); }
inline auto end(void) { return m_data.end(); } inline auto end(void) { return m_data.end(); }

View file

@ -23,6 +23,11 @@
#include <ogfx/utils/Keycodes.hpp> #include <ogfx/utils/Keycodes.hpp>
#include <ogfx/ogfx.hpp> #include <ogfx/ogfx.hpp>
ogfx::WindowCore::FileDialogFilterList filters = {
{ "Image files", { "png", "jpg", "jpeg", "bmp" } },
{ "All files", { "*" } }
};
ostd::ConsoleOutputHandler out; ostd::ConsoleOutputHandler out;
using namespace ogfx::gui::widgets; using namespace ogfx::gui::widgets;
@ -69,8 +74,13 @@ class Window : public ogfx::gui::Window
m_btn1.setText("BUTTON"); m_btn1.setText("BUTTON");
m_btn1.setCallback(ogfx::gui::Widget::eCallback::MousePressed, [&](const ogfx::gui::Event& event) -> void { m_btn1.setCallback(ogfx::gui::Widget::eCallback::MousePressed, [&](const ogfx::gui::Event& event) -> void {
std::cout << showOpenFileDialog(filters) << "\n";
}); });
m_btn1.addThemeOverride("@.button.showIcon", true);
m_btn1.setAnimationData(ad);
m_btn1.setIcon("./img.png");
m_btn1.setIconSize({ 64, 64 });
m_btn1.enableAnimated();
m_label2.setText("Label2"); m_label2.setText("Label2");
m_label3.setText("Label3"); m_label3.setText("Label3");