psemek/libs/ui/source/file_dialog.cpp

326 lines
9.4 KiB
C++

#include <psemek/ui/file_dialog.hpp>
#include <psemek/ui/window.hpp>
#include <psemek/ui/grid_layout.hpp>
#include <psemek/ui/button.hpp>
#include <psemek/ui/label.hpp>
#include <psemek/ui/scroller.hpp>
#include <psemek/ui/edit.hpp>
#include <psemek/ui/frame.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/io/memory_stream.hpp>
#include <psemek/io/file_stream.hpp>
#include <psemek/util/unicode.hpp>
#include <psemek/ui/resources/folder_png.hpp>
#include <psemek/ui/resources/file_png.hpp>
#include <psemek/ui/resources/back_png.hpp>
namespace psemek::ui
{
namespace
{
struct frame_style_fix
: ui::single_container
{
struct shape const & shape() const override
{
return child()->shape();
}
void reshape(geom::box<float, 2> const & bbox) override
{
child()->reshape(bbox);
}
geom::box<float, 2> size_constraints() const override
{
return child()->size_constraints();
}
geom::interval<float> width_constraints(float height) const override
{
return child()->width_constraints(height);
}
geom::interval<float> height_constraints(float width) const override
{
return child()->height_constraints(width);
}
void style_updated() const override
{
ui::single_container::style_updated();
auto style = merged_style();
auto new_style = std::make_shared<struct style>();
new_style->bg_color = style->action_color;
child()->set_own_style(new_style);
}
void draw(painter &) const override
{}
};
struct file_dialog_image_provider
: image_provider
{
file_dialog_image_provider()
{
back_.load(gfx::read_png(io::memory_istream(resources::back_png.data)));
back_.linear_filter();
folder_.load(gfx::read_png(io::memory_istream(resources::folder_png.data)));
folder_.linear_filter();
file_.load(gfx::read_png(io::memory_istream(resources::file_png.data)));
file_.linear_filter();
}
gfx::texture_view_2d get(std::string_view const & id) const override
{
if (id == "back")
return gfx::texture_view_2d{&back_};
if (id == "folder")
return gfx::texture_view_2d{&folder_};
if (id == "file")
return gfx::texture_view_2d{&file_};
return {};
}
private:
gfx::texture_2d back_;
gfx::texture_2d folder_;
gfx::texture_2d file_;
};
}
std::shared_ptr<window> make_file_dialog(file_dialog_options const & options)
{
auto window = options.element_factory.make_window(options.caption);
auto main_layout = options.element_factory.make_grid_layout();
main_layout->set_size(4, options.extra_widget ? 3 : 2);
main_layout->set_column_weight(0, 0.f);
if (options.extra_widget)
main_layout->set_column_weight(2, 0.f);
main_layout->set_row_weight(0, 0.f);
main_layout->set_row_weight(2, 0.f);
main_layout->set_row_weight(3, 0.f);
auto image_provider = std::make_shared<file_dialog_image_provider>();
auto current_path = std::make_shared<std::filesystem::path>();
auto selected_path = std::make_shared<std::filesystem::path>();
auto back_button = options.element_factory.make_button(image_provider->get("back"));
back_button->icon()->set_downscale(false);
auto back_button_style = std::make_shared<style>();
back_button_style->inner_margin = geom::vector{2, 2};
back_button->set_style(back_button_style);
auto common_paths_label = options.element_factory.make_label("Home\nRoot");
auto current_path_label = options.element_factory.make_label("");
current_path_label->set_valign(label::valignment::center);
auto directory_view_frame_style_fix = std::make_shared<frame_style_fix>();
auto directory_view_frame = options.element_factory.make_frame();
directory_view_frame->set_fixed_size(geom::vector{600.f, 400.f});
auto directory_view_scroller = options.element_factory.make_scroller();
auto directory_view = options.element_factory.make_label("Folder\nFolder\nFile\nFile");
auto directory_view_style = std::make_shared<style>();
directory_view_style->link_color = gfx::white.as_color_rgba();
directory_view->set_style(directory_view_style);
auto path_edit = options.element_factory.make_edit();
path_edit->set_enabled(options.type == file_dialog_type::save);
auto bottom_layout = options.element_factory.make_grid_layout();
bottom_layout->set_outer_margin(false);
bottom_layout->set_size(1, 4);
bottom_layout->set_column_weight(0, 0.f);
bottom_layout->set_column_weight(2, 0.f);
bottom_layout->set_column_weight(3, 0.f);
auto status_label = options.element_factory.make_label("");
auto status_label_style = std::make_shared<style>();
status_label_style->text_color = gfx::red.as_color_rgba();
status_label->set_style(status_label_style);
auto confirm_button = options.element_factory.make_button(options.type == file_dialog_type::save ? "Save" : "Open");
auto cancel_button = options.element_factory.make_button("Cancel");
confirm_button->set_hidden(true);
bottom_layout->set(0, 1, status_label);
bottom_layout->set(0, 2, confirm_button);
bottom_layout->set(0, 3, cancel_button);
directory_view_scroller->set_child(directory_view);
directory_view_frame->set_child(directory_view_scroller);
directory_view_frame_style_fix->set_child(directory_view_frame);
main_layout->set(0, 0, back_button);
main_layout->set(1, 0, common_paths_label);
main_layout->set(0, 1, current_path_label);
main_layout->set(1, 1, directory_view_frame_style_fix);
main_layout->set(2, 1, path_edit);
main_layout->set(3, 1, bottom_layout);
if (options.extra_widget)
main_layout->set(1, 2, options.extra_widget);
window->set_child(main_layout);
directory_view->set_image_provider(image_provider.get());
directory_view->data() = image_provider;
auto set_path_callback = std::make_shared<std::function<void(std::filesystem::path)>>();
*set_path_callback = [=, type = options.type, on_visited = options.on_visited](std::filesystem::path new_path)
{
new_path = std::filesystem::absolute(new_path);
try
{
[[maybe_unused]] auto test_iterator = std::filesystem::directory_iterator(new_path);
}
catch (std::filesystem::filesystem_error const & error)
{
status_label->set_text(error.code().message());
return;
}
if (on_visited)
on_visited(new_path);
*current_path = new_path;
status_label->set_text("");
back_button->on_click([set_path_callback, new_path]{
(*set_path_callback)(new_path.parent_path());
});
{
std::string path_str;
int index = 0;
for (auto const & component : new_path)
{
if (component.string() != "/")
path_str += "[link:" + util::to_string(++index) + "]" + component.string() + "[/link]/";
else
path_str += "[link:" + util::to_string(++index) + "]" + component.string() + "[/link]";
}
current_path_label->set_tagged_text(std::move(path_str));
current_path_label->on_link_click([set_path_callback, new_path, count = index](std::string_view const & value){
int index = util::from_string<int>(std::string{value});
auto path = new_path;
for (int i = 0; i < count - index; ++i)
path = path.parent_path();
(*set_path_callback)(path);
});
}
{
std::string contents_str;
struct directory_entry
{
bool folder;
std::filesystem::path path;
};
std::vector<directory_entry> entries;
for (auto const & entry : std::filesystem::directory_iterator(new_path))
entries.push_back({std::filesystem::is_directory(entry.path()), entry.path()});
std::sort(entries.begin(), entries.end(), [](directory_entry const & e1, directory_entry const & e2){
if (e1.folder && !e2.folder)
return true;
if (!e1.folder && e2.folder)
return false;
return e1.path.filename().string() < e2.path.filename().string();
});
int index = 0;
for (auto const & entry : entries)
{
if (entry.folder)
contents_str += "[image:folder]";
else
contents_str += "[image:file]";
contents_str += "[link:" + util::to_string(index++) + "]";
contents_str += entry.path.filename().string() + "[/link]\n";
}
directory_view->set_tagged_text(std::move(contents_str));
directory_view->on_link_click([=, entries = std::move(entries)](std::string_view const & value){
int index = util::from_string<int>(std::string{value});
if (entries[index].folder)
(*set_path_callback)(entries[index].path);
else
{
auto path = entries[index].path;
try
{
if (type == file_dialog_type::load)
{
[[maybe_unused]] io::file_istream test(path);
}
else
{
[[maybe_unused]] io::file_ostream test(path, io::file_ostream::append);
}
path_edit->set_text(path.filename().string());
status_label->set_text("");
*selected_path = path;
}
catch (std::exception const & error)
{
status_label->set_text(error.what());
}
}
});
}
};
path_edit->on_text_entered([=](std::u32string_view const & text_u32){
std::string text = util::to_utf8(std::u32string(text_u32));
*selected_path = *current_path / text;
confirm_button->set_hidden(text.empty());
}, false);
cancel_button->on_click([=]{
window->close();
});
window->on_close(options.on_canceled);
confirm_button->on_click([=, on_selected = options.on_selected]{
window->close();
if (on_selected)
on_selected(*selected_path);
});
(*set_path_callback)(options.path);
return window;
}
}