refactor custom graphics dialog into a class

This commit is contained in:
2025-06-30 22:17:44 -05:00
parent 8587af17ef
commit 27b8fba4dc

View File

@@ -3715,9 +3715,29 @@ void edit_custom_pics_types() {
extern fs::path tempDir;
extern std::string scenario_temp_dir_name;
class cCustomGraphicsDialog {
private:
cDialog dlg;
size_t cur = 0;
std::unordered_map<size_t, sf::Image> sheets;
// Everything you do in this dialog can be undone when you hit cancel, leaving no trace in the main history.
std::vector<action_ptr> deferred_actions;
int max_pic = -1;
std::vector<int> all_pics;
fs::path pic_dir;
// Icon selection info
int icon_start = 0;
int icon_end = 0;
int icon_min = 0;
int icon_max = 0;
// Buttons for working with icons
std::vector<std::string> icon_buttons = {"icon-terr", "strip-terr", "add-terr"};
// Keep a single RenderTexture for splicing together custom graphics on a sheet
bool canvas_created = false;
static sf::RenderTexture& canvas() {
static bool canvas_created = false;
static sf::RenderTexture instance;
if(!canvas_created){
instance.create(280, 360);
@@ -3726,8 +3746,9 @@ static sf::RenderTexture& canvas() {
return instance;
}
bool scratch_created = false;
// Keep a 1-tile RenderTexture for rendering new tiles
static sf::RenderTexture& scratch() {
static bool scratch_created = false;
static sf::RenderTexture instance;
if(!scratch_created){
instance.create(28, 36);
@@ -3736,8 +3757,8 @@ static sf::RenderTexture& scratch() {
return instance;
}
static void set_up_canvas(size_t sheet){
fs::path pic_dir = tempDir/scenario_temp_dir_name/"graphics";
// Set up the canvas texture for modifying a given graphics sheet
void set_up_canvas(size_t sheet){
fs::path fromPath = pic_dir/("sheet" + std::to_string(sheet) + ".png");
sf::Texture texture;
@@ -3747,29 +3768,414 @@ static void set_up_canvas(size_t sheet){
canvas().draw(s);
}
static void set_dlg_custom_sheet(cDialog& me, size_t sheet) {
me["num"].setTextToNum(sheet);
dynamic_cast<cPict&>(me["sheet"]).setPict(sheet, PIC_FULL);
// Set which custom sheet is viewed
void set_dlg_custom_sheet(size_t sheet) {
dlg["num"].setTextToNum(sheet);
dynamic_cast<cPict&>(dlg["sheet"]).setPict(sheet, PIC_FULL);
}
// Faster -- could this be used for threshold setting without sqrt?
static int color_diff2(sf::Color c1, sf::Color c2) {
return pow(c1.r - c2.r, 2) + pow(c1.g - c2.g, 2) + pow(c1.b - c2.b, 2);
}
static float color_diff(sf::Color c1, sf::Color c2) {
return sqrt(color_diff2(c1, c2));
// Replace the current sheet with the image in a file
bool replace_from_file(std::string action_name, fs::path fpath) {
sf::Image img;
if(!img.loadFromFile(fpath.string().c_str())) {
beep();
return true;
}
if(cur >= spec_scen_g.numSheets) {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
sf::Image image_for_undo;
// TODO for reload, old image and new image will be the same! This could possibly be addressed by loading the image when edit is clicked?
// But there is no guarantee Edit is clicked first--the designer could externally edit the full-sheet image whenever they want.
// Also comparing image blobs for equality is probably expensive, but we should have a check to prevent adding undo actions if the same file was imported
// or reload is clicked when the file isn't changed.
image_for_undo.loadFromFile(toPath.string());
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], image_for_undo, img)));
img.saveToFile(toPath.string().c_str());
ResMgr::graphics.free(resName);
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], sheets[cur], img)));
sheets[cur] = img;
spec_scen_g.replace_sheet(cur, img);
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
void edit_custom_sheets() {
// Everything you do in this dialog can be undone when you hit cancel, leaving no trace in the main history.
std::vector<action_ptr> deferred_actions;
// Replace the current sheet with an sf::Image
bool replace_from_image(std::string action_name, sf::Image& image) {
if(cur >= spec_scen_g.numSheets) {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
int max_pic = -1;
std::vector<int> all_pics;
fs::path pic_dir = tempDir/scenario_temp_dir_name/"graphics";
sf::Image image_for_undo;
image_for_undo.loadFromFile(toPath.string());
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], image_for_undo, image)));
image.saveToFile(toPath.string().c_str());
ResMgr::graphics.free(resName);
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, cur, sheets[cur], image)));
sheets[cur] = image;
spec_scen_g.replace_sheet(cur, image);
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
// Copy the current graphic sheet to system clipboard
bool copy_sheet() {
if(cur >= spec_scen_g.numSheets) {
fs::path fromPath = pic_dir/("sheet" + std::to_string(all_pics[cur]) + ".png");
sf::Image img;
img.loadFromFile(fromPath.string().c_str());
set_clipboard_img(img);
return true;
}
set_clipboard_img(sheets[cur]);
return true;
}
// Paste image from system clipboard over current sheet
bool paste_sheet() {
auto img = get_clipboard_img();
if(img == nullptr) {
beep();
return true;
}
return replace_from_image("Paste Graphics Sheet", *img);
}
bool edit_sheet_external() {
fs::path image_editor = get_string_pref("ImageEditor");
if(image_editor.empty()){
showWarning("Choose an external image editor in the preferences window first.");
return true;
}
#ifdef SFML_SYSTEM_MACOS
// Find actual binary inside the .app bundle:
image_editor = image_editor / "Contents" / "MacOS" / image_editor.stem();
#endif
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
std::vector<std::string> args = {toPath.string()};
try{
bp::child ch(image_editor, args, bp::std_out > stdout);
ch.detach();
}
catch(std::exception& x) {
showError(std::string{"Error running your image editor: "} + x.what());
} catch(std::string& x) {
showError("Error running your image editor: " + x);
} catch(...) {
showError("An unknown error occurred running your image editor!");
}
return true;
}
// Reload external changes to current graphics sheet
bool reload_sheet() {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path sheetPath = pic_dir/(resName + ".png");
return replace_from_file("Reload Graphics Sheet", sheetPath);
}
// Import image as graphics sheet
bool import_sheet() {
fs::path fpath = nav_get_rsrc({"png", "bmp", "jpg", "jpeg", "gif", "psd"});
if(fpath.empty()) return true;
replace_from_file("Import Graphics Sheet", fpath);
return true;
}
// Export current sheet to image file
bool export_sheet() {
fs::path fpath = nav_put_rsrc({"png", "bmp", "jpg", "jpeg"});
if(fpath.empty()) return true;
if(cur >= spec_scen_g.numSheets) {
fs::path fromPath = pic_dir/("sheet" + std::to_string(all_pics[cur]) + ".png");
sf::Image img;
img.loadFromFile(fromPath.string().c_str());
img.saveToFile(fpath.string().c_str());
return true;
}
sheets[cur].saveToFile(fpath.string().c_str());
return true;
}
// Create new sheet
bool new_sheet() {
cChoiceDlog pickNum("add-new-sheet", {"cancel", "new"}, &dlg);
pickNum->getControl("num").setTextToNum(spec_scen_g.numSheets);
if(pickNum.show() == "cancel") return true;
int newSheet = pickNum->getControl("num").getTextAsNum();
fs::path sheetPath = pic_dir/("sheet" + std::to_string(newSheet) + ".png");
if(newSheet == spec_scen_g.numSheets) {
spec_scen_g.sheets.emplace_back();
spec_scen_g.init_sheet(newSheet);
spec_scen_g.sheets[newSheet]->copyToImage().saveToFile(sheetPath.string().c_str());
spec_scen_g.numSheets++;
auto iter = all_pics.insert(std::upper_bound(all_pics.begin(), all_pics.end(), newSheet), newSheet);
cur = iter - all_pics.begin();
} else {
auto iter = std::lower_bound(all_pics.begin(), all_pics.end(), newSheet);
if(*iter == newSheet) {
showError("Sorry, but that sheet already exists! Try creating a sheet with a different number.", "Sheet number: " + std::to_string(newSheet), &dlg);
return true;
}
iter = all_pics.insert(iter, newSheet);
cur = iter - all_pics.begin();
sf::Image img;
img.create(280, 360);
img.saveToFile(sheetPath.string().c_str());
}
dlg["left"].show();
dlg["right"].show();
set_dlg_custom_sheet(all_pics[cur]);
deferred_actions.push_back(action_ptr(new aCreateGraphicsSheet(newSheet)));
return true;
}
// Delete the current sheet
bool delete_sheet() {
int which_pic = all_pics[cur];
fs::path fpath = pic_dir/("sheet" + std::to_string(which_pic) + ".png");
bool moved = false;
sf::Image image_for_undo;
image_for_undo.loadFromFile(fpath.string());
if(which_pic < spec_scen_g.numSheets) {
std::string choice = "del";
if(which_pic < spec_scen_g.numSheets - 1)
choice = cChoiceDlog("must-delete-in-order", {"cancel", "del", "move"}, &dlg).show();
if(choice == "cancel") return true;
if(choice == "move") {
moved = true;
spec_scen_g.sheets.erase(spec_scen_g.sheets.begin() + which_pic);
spec_scen_g.numSheets--;
for(; which_pic < spec_scen_g.numSheets; which_pic++) {
fs::path from = pic_dir/("sheet" + std::to_string(which_pic + 1) + ".png");
fs::path to = pic_dir/("sheet" + std::to_string(which_pic) + ".png");
if(!fs::exists(from)) continue; // Just in case
fs::remove(to);
fs::rename(from, to);
ResMgr::graphics.free("sheet" + std::to_string(which_pic));
}
auto end = std::find(all_pics.begin() + cur, all_pics.end(), which_pic - 1);
if(end != all_pics.end())
all_pics.erase(end);
else {
// This shouldn't be reached
std::cerr << "Whoops! Somehow failed to remove the index of the deleted sheet!" << std::endl;
}
} else if(choice == "del") {
all_pics.erase(all_pics.begin() + cur);
spec_scen_g.numSheets = which_pic;
spec_scen_g.sheets.resize(which_pic);
ResMgr::graphics.free("sheet" + std::to_string(which_pic));
}
}else{
all_pics.erase(all_pics.begin() + cur);
}
if(fs::exists(fpath)) fs::remove(fpath);
deferred_actions.push_back(action_ptr(new aDeleteGraphicsSheet(which_pic, moved, image_for_undo)));
if(all_pics.size() == 1) {
dlg["left"].hide();
dlg["right"].hide();
} else if(all_pics.empty()) {
cStrDlog("You've just deleted the last custom graphics sheet, so this dialog will now close. If you want to add more sheets, you can of course reopen the dialog.", "", "Last Sheet Deleted", 16, PIC_DLOG).show();
dlg.toast(true);
return true;
}
if(cur > 0) cur--;
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
// Show which icons are selected
void show_icon_selection() {
dlg["icon-min"].setTextToNum(icon_min);
dlg["icon-max"].setTextToNum(icon_max);
// TODO highlight the selected icons visually
if(icon_min != 0){
for(auto name : icon_buttons){
dlg[name].show();
}
dlg["diff-threshold"].show();
}else{
for(auto name : icon_buttons){
dlg[name].hide();
}
dlg["diff-threshold"].hide();
}
};
bool arrow_button_filter(std::string dir) {
size_t old_cur = cur;
if(dir == "left") {
if(cur == 0)
cur = all_pics.size() - 1;
else cur--;
} else if(dir == "right") {
cur++;
if(cur >= all_pics.size())
cur = 0;
} else return true;
if(cur != old_cur){
icon_start = icon_end = icon_min = icon_max = 0;
show_icon_selection();
}
set_dlg_custom_sheet(all_pics[cur]);
return true;
}
bool click_on_sheet() {
eKeyMod mod = current_key_mod();
location where_curs = sf::Mouse::getPosition(dlg.getWindow());
where_curs = dlg.getWindow().mapPixelToCoords(where_curs);
rectangle sheet_bounds = dlg["sheet"].getBounds();
where_curs.x -= sheet_bounds.left;
where_curs.y -= sheet_bounds.top;
where_curs.x /= 28;
where_curs.y /= 36;
int icon_hit = 1000 + 100 * all_pics[cur] + 10 * where_curs.y + where_curs.x;
if(mod_contains(mod, mod_shift)){
icon_end = icon_hit;
}else{
icon_start = icon_end = icon_hit;
}
icon_min = min(icon_start, icon_end);
icon_max = max(icon_start, icon_end);
show_icon_selection();
return true;
}
bool import_ter_icon() {
int sheet_start = 1000 + 100 * all_pics[cur];
set_up_canvas(all_pics[cur]);
pic_num_t icon = choose_graphic(0, PIC_TER, &dlg);
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
cPict::drawAt(mainPtr(),canvas(),dest,icon+i,PIC_TER,false);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Import Terrain Icon", img);
}
bool strip_ter_floor() {
int sheet_start = 1000 + 100 * all_pics[cur];
static sf::BlendMode blend_replace(sf::BlendMode::Factor::One, sf::BlendMode::Factor::Zero, sf::BlendMode::Equation::Add);
pic_num_t icon = choose_graphic(0, PIC_TER, &dlg);
scratch().clear(sf::Color(0,0,0,0));
scratch().display();
sf::Image without_floor = scratch().getTexture().copyToImage();
cPict::drawAt(mainPtr(),scratch(),{0,0,36,28},icon,PIC_TER,false);
scratch().display();
sf::Image floor_to_strip = scratch().getTexture().copyToImage();
set_up_canvas(all_pics[cur]);
sf::Image canvas_image = canvas().getTexture().copyToImage();
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
sf::Image copy = without_floor;
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
for(int y = 0; y < 36; ++y){
for(int x = 0; x < 28; ++x){
sf::Color source = canvas_image.getPixel(dest.left + x, dest.top + y);
sf::Color floor_source = floor_to_strip.getPixel(x, y);
float epsilon = dlg["diff-threshold"].getTextAsNum();
if(color_diff2(source, floor_source) > pow(epsilon, 2)){
copy.setPixel(x, y, source);
}
}
}
sf::Texture t;
t.loadFromImage(copy);
sf::Sprite s(t);
s.setPosition(dest.left, dest.top);
canvas().draw(s, blend_replace);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Strip Terrain Background", img);
}
bool add_ter_floor() {
int sheet_start = 1000 + 100 * all_pics[cur];
static sf::BlendMode blend_replace(sf::BlendMode::Factor::One, sf::BlendMode::Factor::Zero, sf::BlendMode::Equation::Add);
pic_num_t icon = choose_graphic(0, PIC_TER, &dlg);
set_up_canvas(all_pics[cur]);
scratch().clear(sf::Color(0,0,0,0));
cPict::drawAt(mainPtr(),scratch(),{0,0,36,28},icon,PIC_TER,false);
scratch().display();
sf::Image floor_to_add = scratch().getTexture().copyToImage();
sf::Texture floor_t;
floor_t.loadFromImage(floor_to_add);
sf::Sprite floor_s(floor_t);
sf::Texture sheet_t = canvas().getTexture();
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
scratch().clear(sf::Color(0,0,0,0));
scratch().draw(floor_s);
sf::Sprite ter_s(sheet_t, dest);
scratch().draw(ter_s, sf::BlendAlpha);
scratch().display();
sf::Sprite combined_s(scratch().getTexture());
combined_s.setPosition(dest.left, dest.top);
canvas().draw(combined_s, blend_replace);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Add Terrain Background", img);
}
public:
cCustomGraphicsDialog() : dlg(*ResMgr::dialogs.get("graphic-sheets")) {
pic_dir = tempDir/scenario_temp_dir_name/"graphics";
if(!scenario.scen_file.has_extension()) // It's an unpacked scenario
pic_dir = scenario.scen_file/"graphics";
if(!fs::exists(pic_dir)) fs::create_directories(pic_dir);
}
void run() {
for(fs::directory_iterator iter(pic_dir); iter != fs::directory_iterator(); iter++) {
std::string fname = iter->path().filename().string().c_str();
int dot = fname.find_last_of('.');
@@ -3815,405 +4221,45 @@ void edit_custom_sheets() {
set_cursor(watch_curs);
// Get image data from the sheets in memory
size_t cur = 0;
std::unordered_map<size_t, sf::Image> sheets;
for(size_t i = 0; i < spec_scen_g.numSheets; i++) {
sheets[i] = spec_scen_g.sheets[i]->copyToImage();
}
cDialog pic_dlg(*ResMgr::dialogs.get("graphic-sheets"));
auto replace_from_file = [&pic_dlg,&sheets,&cur,&all_pics,&pic_dir,&deferred_actions](std::string action_name, fs::path fpath) -> bool {
sf::Image img;
if(!img.loadFromFile(fpath.string().c_str())) {
beep();
return true;
}
if(cur >= spec_scen_g.numSheets) {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
sf::Image image_for_undo;
// TODO for reload, old image and new image will be the same! This could possibly be addressed by loading the image when edit is clicked?
// But there is no guarantee Edit is clicked first--the designer could externally edit the full-sheet image whenever they want.
// Also comparing image blobs for equality is probably expensive, but we should have a check to prevent adding undo actions if the same file was imported
// or reload is clicked when the file isn't changed.
image_for_undo.loadFromFile(toPath.string());
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], image_for_undo, img)));
img.saveToFile(toPath.string().c_str());
ResMgr::graphics.free(resName);
set_dlg_custom_sheet(pic_dlg, all_pics[cur]);
return true;
}
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], sheets[cur], img)));
sheets[cur] = img;
spec_scen_g.replace_sheet(cur, img);
set_dlg_custom_sheet(pic_dlg, all_pics[cur]);
return true;
};
auto replace_from_image = [&cur, &all_pics, pic_dir, &deferred_actions, &sheets, &pic_dlg](std::string action_name, sf::Image& image) -> bool {
if(cur >= spec_scen_g.numSheets) {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
sf::Image image_for_undo;
image_for_undo.loadFromFile(toPath.string());
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, all_pics[cur], image_for_undo, image)));
image.saveToFile(toPath.string().c_str());
ResMgr::graphics.free(resName);
set_dlg_custom_sheet(pic_dlg, all_pics[cur]);
return true;
}
deferred_actions.push_back(action_ptr(new aReplaceGraphicsSheet(action_name, cur, sheets[cur], image)));
sheets[cur] = image;
spec_scen_g.replace_sheet(cur, image);
set_dlg_custom_sheet(pic_dlg, all_pics[cur]);
return true;
};
using namespace std::placeholders;
pic_dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, _1, false));
pic_dlg["okay"].attachClickHandler(std::bind(&cDialog::toast, _1, true));
pic_dlg["copy"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir](cDialog&, std::string, eKeyMod) -> bool {
if(cur >= spec_scen_g.numSheets) {
fs::path fromPath = pic_dir/("sheet" + std::to_string(all_pics[cur]) + ".png");
sf::Image img;
img.loadFromFile(fromPath.string().c_str());
set_clipboard_img(img);
return true;
}
set_clipboard_img(sheets[cur]);
return true;
});
pic_dlg["paste"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir,&deferred_actions,replace_from_image](cDialog& me, std::string, eKeyMod) -> bool {
auto img = get_clipboard_img();
if(img == nullptr) {
beep();
return true;
}
return replace_from_image("Paste Graphics Sheet", *img);
});
pic_dlg["edit"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir](cDialog&, std::string, eKeyMod) -> bool {
fs::path image_editor = get_string_pref("ImageEditor");
if(image_editor.empty()){
showWarning("Choose an external image editor in the preferences window first.");
return true;
}
#ifdef SFML_SYSTEM_MACOS
// Find actual binary inside the .app bundle:
image_editor = image_editor / "Contents" / "MacOS" / image_editor.stem();
#endif
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path toPath = pic_dir/(resName + ".png");
std::vector<std::string> args = {toPath.string()};
try{
bp::child ch(image_editor, args, bp::std_out > stdout);
ch.detach();
}
catch(std::exception& x) {
showError(std::string{"Error running your image editor: "} + x.what());
} catch(std::string& x) {
showError("Error running your image editor: " + x);
} catch(...) {
showError("An unknown error occurred running your image editor!");
}
return true;
});
pic_dlg["reload"].attachClickHandler([replace_from_file, &all_pics, &cur, &pic_dir](cDialog&, std::string, eKeyMod) -> bool {
std::string resName = "sheet" + std::to_string(all_pics[cur]);
fs::path sheetPath = pic_dir/(resName + ".png");
return replace_from_file("Reload Graphics Sheet", sheetPath);
});
pic_dlg["open"].attachClickHandler([replace_from_file](cDialog& me, std::string, eKeyMod) -> bool {
fs::path fpath = nav_get_rsrc({"png", "bmp", "jpg", "jpeg", "gif", "psd"});
if(fpath.empty()) return true;
replace_from_file("Import Graphics Sheet", fpath);
return true;
});
pic_dlg["save"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir](cDialog&, std::string, eKeyMod) -> bool {
fs::path fpath = nav_put_rsrc({"png", "bmp", "jpg", "jpeg"});
if(fpath.empty()) return true;
if(cur >= spec_scen_g.numSheets) {
fs::path fromPath = pic_dir/("sheet" + std::to_string(all_pics[cur]) + ".png");
sf::Image img;
img.loadFromFile(fromPath.string().c_str());
img.saveToFile(fpath.string().c_str());
return true;
}
sheets[cur].saveToFile(fpath.string().c_str());
return true;
});
pic_dlg["new"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir,&deferred_actions](cDialog& me, std::string, eKeyMod) -> bool {
cChoiceDlog pickNum("add-new-sheet", {"cancel", "new"}, &me);
pickNum->getControl("num").setTextToNum(spec_scen_g.numSheets);
if(pickNum.show() == "cancel") return true;
int newSheet = pickNum->getControl("num").getTextAsNum();
fs::path sheetPath = pic_dir/("sheet" + std::to_string(newSheet) + ".png");
if(newSheet == spec_scen_g.numSheets) {
spec_scen_g.sheets.emplace_back();
spec_scen_g.init_sheet(newSheet);
spec_scen_g.sheets[newSheet]->copyToImage().saveToFile(sheetPath.string().c_str());
spec_scen_g.numSheets++;
auto iter = all_pics.insert(std::upper_bound(all_pics.begin(), all_pics.end(), newSheet), newSheet);
cur = iter - all_pics.begin();
} else {
auto iter = std::lower_bound(all_pics.begin(), all_pics.end(), newSheet);
if(*iter == newSheet) {
showError("Sorry, but that sheet already exists! Try creating a sheet with a different number.", "Sheet number: " + std::to_string(newSheet), &me);
return true;
}
iter = all_pics.insert(iter, newSheet);
cur = iter - all_pics.begin();
sf::Image img;
img.create(280, 360);
img.saveToFile(sheetPath.string().c_str());
}
me["left"].show();
me["right"].show();
set_dlg_custom_sheet(me, all_pics[cur]);
deferred_actions.push_back(action_ptr(new aCreateGraphicsSheet(newSheet)));
return true;
});
pic_dlg["del"].attachClickHandler([&sheets,&cur,&all_pics,&pic_dir,&deferred_actions](cDialog& me, std::string, eKeyMod) -> bool {
int which_pic = all_pics[cur];
fs::path fpath = pic_dir/("sheet" + std::to_string(which_pic) + ".png");
bool moved = false;
sf::Image image_for_undo;
image_for_undo.loadFromFile(fpath.string());
if(which_pic < spec_scen_g.numSheets) {
std::string choice = "del";
if(which_pic < spec_scen_g.numSheets - 1)
choice = cChoiceDlog("must-delete-in-order", {"cancel", "del", "move"}, &me).show();
if(choice == "cancel") return true;
if(choice == "move") {
moved = true;
spec_scen_g.sheets.erase(spec_scen_g.sheets.begin() + which_pic);
spec_scen_g.numSheets--;
for(; which_pic < spec_scen_g.numSheets; which_pic++) {
fs::path from = pic_dir/("sheet" + std::to_string(which_pic + 1) + ".png");
fs::path to = pic_dir/("sheet" + std::to_string(which_pic) + ".png");
if(!fs::exists(from)) continue; // Just in case
fs::remove(to);
fs::rename(from, to);
ResMgr::graphics.free("sheet" + std::to_string(which_pic));
}
auto end = std::find(all_pics.begin() + cur, all_pics.end(), which_pic - 1);
if(end != all_pics.end())
all_pics.erase(end);
else {
// This shouldn't be reached
std::cerr << "Whoops! Somehow failed to remove the index of the deleted sheet!" << std::endl;
}
} else if(choice == "del") {
all_pics.erase(all_pics.begin() + cur);
spec_scen_g.numSheets = which_pic;
spec_scen_g.sheets.resize(which_pic);
ResMgr::graphics.free("sheet" + std::to_string(which_pic));
}
}else{
all_pics.erase(all_pics.begin() + cur);
}
if(fs::exists(fpath)) fs::remove(fpath);
deferred_actions.push_back(action_ptr(new aDeleteGraphicsSheet(which_pic, moved, image_for_undo)));
dlg["cancel"].attachClickHandler(std::bind(&cDialog::toast, _1, false));
dlg["okay"].attachClickHandler(std::bind(&cDialog::toast, _1, true));
dlg["copy"].attachClickHandler(std::bind(&cCustomGraphicsDialog::copy_sheet,this));
dlg["paste"].attachClickHandler(std::bind(&cCustomGraphicsDialog::paste_sheet,this));
dlg["edit"].attachClickHandler(std::bind(&cCustomGraphicsDialog::edit_sheet_external,this));
dlg["reload"].attachClickHandler(std::bind(&cCustomGraphicsDialog::reload_sheet,this));
dlg["open"].attachClickHandler(std::bind(&cCustomGraphicsDialog::import_sheet,this));
dlg["save"].attachClickHandler(std::bind(&cCustomGraphicsDialog::export_sheet,this));
dlg["new"].attachClickHandler(std::bind(&cCustomGraphicsDialog::new_sheet,this));
dlg["del"].attachClickHandler(std::bind(&cCustomGraphicsDialog::delete_sheet,this));
if(all_pics.size() == 1) {
me["left"].hide();
me["right"].hide();
} else if(all_pics.empty()) {
cStrDlog("You've just deleted the last custom graphics sheet, so this dialog will now close. If you want to add more sheets, you can of course reopen the dialog.", "", "Last Sheet Deleted", 16, PIC_DLOG).show();
me.toast(true);
return true;
dlg["left"].hide();
dlg["right"].hide();
}
if(cur > 0) cur--;
set_dlg_custom_sheet(me, all_pics[cur]);
return true;
});
if(all_pics.size() == 1) {
pic_dlg["left"].hide();
pic_dlg["right"].hide();
}
int icon_start = 0;
int icon_end = 0;
int icon_min = 0;
int icon_max = 0;
std::vector<std::string> icon_buttons = {"icon-terr", "strip-terr", "add-terr"};
auto show_icon_selection = [&icon_min, &icon_max, &pic_dlg, icon_buttons]() {
pic_dlg["icon-min"].setTextToNum(icon_min);
pic_dlg["icon-max"].setTextToNum(icon_max);
// TODO highlight the selected icons visually
if(icon_min != 0){
for(auto name : icon_buttons){
pic_dlg[name].show();
}
pic_dlg["diff-threshold"].show();
}else{
for(auto name : icon_buttons){
pic_dlg[name].hide();
}
pic_dlg["diff-threshold"].hide();
}
};
show_icon_selection();
pic_dlg.attachClickHandlers([&sheets,&cur,&all_pics,show_icon_selection,&icon_start,&icon_end,&icon_min,&icon_max](cDialog& me, std::string dir, eKeyMod) -> bool {
size_t old_cur = cur;
if(dir == "left") {
if(cur == 0)
cur = all_pics.size() - 1;
else cur--;
} else if(dir == "right") {
cur++;
if(cur >= all_pics.size())
cur = 0;
} else return true;
if(cur != old_cur){
icon_start = icon_end = icon_min = icon_max = 0;
show_icon_selection();
}
set_dlg_custom_sheet(me, all_pics[cur]);
return true;
}, {"left", "right"});
dlg["left"].attachClickHandler(std::bind(&cCustomGraphicsDialog::arrow_button_filter, this, "left"));
dlg["right"].attachClickHandler(std::bind(&cCustomGraphicsDialog::arrow_button_filter, this, "right"));
set_dlg_custom_sheet(pic_dlg, all_pics[cur]);
set_dlg_custom_sheet(all_pics[cur]);
pic_dlg["sheet"].attachClickHandler([&all_pics, &cur, &icon_start, &icon_end, &icon_min, &icon_max, show_icon_selection](cDialog& me, std::string, eKeyMod mod) -> bool {
location where_curs = sf::Mouse::getPosition(me.getWindow());
where_curs = me.getWindow().mapPixelToCoords(where_curs);
rectangle sheet_bounds = me["sheet"].getBounds();
where_curs.x -= sheet_bounds.left;
where_curs.y -= sheet_bounds.top;
where_curs.x /= 28;
where_curs.y /= 36;
dlg["sheet"].attachClickHandler(std::bind(&cCustomGraphicsDialog::click_on_sheet, this));
int icon_hit = 1000 + 100 * all_pics[cur] + 10 * where_curs.y + where_curs.x;
if(mod_contains(mod, mod_shift)){
icon_end = icon_hit;
}else{
icon_start = icon_end = icon_hit;
}
icon_min = min(icon_start, icon_end);
icon_max = max(icon_start, icon_end);
show_icon_selection();
return true;
});
static sf::BlendMode blend_replace(sf::BlendMode::Factor::One, sf::BlendMode::Factor::Zero, sf::BlendMode::Equation::Add);
pic_dlg.attachClickHandlers([&icon_max, &icon_min, &all_pics, &cur, replace_from_image](cDialog& me, std::string item_hit, eKeyMod) -> bool {
int sheet_start = 1000 + 100 * all_pics[cur];
if(item_hit == "icon-terr"){
set_up_canvas(all_pics[cur]);
pic_num_t icon = choose_graphic(0, PIC_TER, &me);
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
cPict::drawAt(mainPtr(),canvas(),dest,icon+i,PIC_TER,false);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Import Terrain Icon", img);
}else if(item_hit == "strip-terr"){
pic_num_t icon = choose_graphic(0, PIC_TER, &me);
scratch().clear(sf::Color(0,0,0,0));
scratch().display();
sf::Image without_floor = scratch().getTexture().copyToImage();
cPict::drawAt(mainPtr(),scratch(),{0,0,36,28},icon,PIC_TER,false);
scratch().display();
sf::Image floor_to_strip = scratch().getTexture().copyToImage();
set_up_canvas(all_pics[cur]);
sf::Image canvas_image = canvas().getTexture().copyToImage();
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
sf::Image copy = without_floor;
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
for(int y = 0; y < 36; ++y){
for(int x = 0; x < 28; ++x){
sf::Color source = canvas_image.getPixel(dest.left + x, dest.top + y);
sf::Color floor_source = floor_to_strip.getPixel(x, y);
float epsilon = me["diff-threshold"].getTextAsNum();
if(color_diff(source, floor_source) > epsilon){
copy.setPixel(x, y, source);
}
}
}
sf::Texture t;
t.loadFromImage(copy);
sf::Sprite s(t);
s.setPosition(dest.left, dest.top);
canvas().draw(s, blend_replace);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Strip Terrain Background", img);
}else if(item_hit == "add-terr"){
pic_num_t icon = choose_graphic(0, PIC_TER, &me);
set_up_canvas(all_pics[cur]);
scratch().clear(sf::Color(0,0,0,0));
cPict::drawAt(mainPtr(),scratch(),{0,0,36,28},icon,PIC_TER,false);
scratch().display();
sf::Image floor_to_add = scratch().getTexture().copyToImage();
sf::Texture floor_t;
floor_t.loadFromImage(floor_to_add);
sf::Sprite floor_s(floor_t);
sf::Texture sheet_t = canvas().getTexture();
for(int i = 0; i < (icon_max - icon_min) + 1; ++i){
int sheet_icon = icon_min + i - sheet_start;
int x = sheet_icon % 10;
int y = sheet_icon / 10;
rectangle dest {y * 36, x * 28, y * 36 + 36, x * 28 + 28};
scratch().clear(sf::Color(0,0,0,0));
scratch().draw(floor_s);
sf::Sprite ter_s(sheet_t, dest);
scratch().draw(ter_s, sf::BlendAlpha);
scratch().display();
sf::Sprite combined_s(scratch().getTexture());
combined_s.setPosition(dest.left, dest.top);
canvas().draw(combined_s, blend_replace);
}
canvas().display();
sf::Image img = canvas().getTexture().copyToImage();
return replace_from_image("Add Terrain Background", img);
}
return true;
},icon_buttons);
dlg["icon-terr"].attachClickHandler(std::bind(&cCustomGraphicsDialog::import_ter_icon,this));
dlg["strip-terr"].attachClickHandler(std::bind(&cCustomGraphicsDialog::strip_ter_floor,this));
dlg["add-terr"].attachClickHandler(std::bind(&cCustomGraphicsDialog::add_ter_floor,this));
shut_down_menus(5); // So that cmd+O, cmd+N, cmd+S can work
pic_dlg.run();
dlg.run();
// Commit undo actions for everything that was done
if(pic_dlg.accepted()){
if(dlg.accepted()){
for(action_ptr action : deferred_actions){
undo_list.add(action);
}
@@ -4235,10 +4281,13 @@ void edit_custom_sheets() {
update_edit_menu();
}
};
void edit_custom_sheets() {
cCustomGraphicsDialog().run();
}
fs::path get_snd_path(size_t index) {
extern fs::path tempDir;
extern std::string scenario_temp_dir_name;
fs::path sndpath = tempDir/scenario_temp_dir_name/"sounds";
std::string sndbasenm = "SND" + std::to_string(index);
fs::path sndfile = sndpath/(sndbasenm + ".wav");