From e07c14db811f9b91188dde145c16e482c293cb97 Mon Sep 17 00:00:00 2001 From: YohananDiamond Date: Wed, 24 May 2023 12:47:20 -0300 Subject: [PATCH 01/46] Add option to prevent piano input --- src/gui/gui.cpp | 4 ++++ src/gui/gui.h | 1 + src/gui/piano.cpp | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1f2d4879..e9390dff 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5954,6 +5954,7 @@ bool FurnaceGUI::init() { pianoOptions=e->getConfBool("pianoOptions",pianoOptions); pianoSharePosition=e->getConfBool("pianoSharePosition",pianoSharePosition); pianoOptionsSet=e->getConfBool("pianoOptionsSet",pianoOptionsSet); + pianoReadonly=e->getConfBool("pianoReadonly",false); pianoOffset=e->getConfInt("pianoOffset",pianoOffset); pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit); pianoView=e->getConfInt("pianoView",pianoView); @@ -6389,6 +6390,7 @@ void FurnaceGUI::commitState() { e->setConf("pianoOptions",pianoOptions); e->setConf("pianoSharePosition",pianoSharePosition); e->setConf("pianoOptionsSet",pianoOptionsSet); + e->setConf("pianoReadonly",pianoReadonly); e->setConf("pianoOffset",pianoOffset); e->setConf("pianoOffsetEdit",pianoOffsetEdit); e->setConf("pianoView",pianoView); @@ -6835,6 +6837,7 @@ FurnaceGUI::FurnaceGUI(): pianoOptions(true), pianoSharePosition(false), pianoOptionsSet(false), + pianoReadonly(false), pianoOffset(6), pianoOffsetEdit(9), pianoView(PIANO_LAYOUT_AUTOMATIC), @@ -6844,6 +6847,7 @@ FurnaceGUI::FurnaceGUI(): pianoOctavesEdit(4), pianoOptions(false), pianoSharePosition(true), + pianoReadonly(false), pianoOffset(6), pianoOffsetEdit(6), pianoView(PIANO_LAYOUT_STANDARD), diff --git a/src/gui/gui.h b/src/gui/gui.h index 88074864..0fc45f70 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1916,6 +1916,7 @@ class FurnaceGUI { bool pianoOptions, pianoSharePosition, pianoOptionsSet; float pianoKeyHit[180]; bool pianoKeyPressed[180]; + bool pianoReadonly; int pianoOffset, pianoOffsetEdit; int pianoView, pianoInputPadMode; diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index baae316d..4842d05c 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -123,6 +123,7 @@ void FurnaceGUI::drawPiano() { pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_VISIBLE; } ImGui::Checkbox("Share play/edit offset/range",&pianoSharePosition); + ImGui::Checkbox("Read-only (can't input notes)",&pianoReadonly); ImGui::EndPopup(); } @@ -223,7 +224,7 @@ void FurnaceGUI::drawPiano() { //ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) { bool canInput=false; - if (ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) { + if (!pianoReadonly && ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) { canInput=true; ImGui::InhibitInertialScroll(); } From d6f986abb13b076d41d02efe5b6a41c71bea97a2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Jun 2023 14:33:58 -0500 Subject: [PATCH 02/46] fix the chan osc --- src/gui/chanOsc.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index d2ccfb90..9e67ef8f 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -346,14 +346,18 @@ void FurnaceGUI::drawChanOsc() { ImRect rect=ImRect(minArea,maxArea); ImRect inRect=rect; inRect.Min.x+=dpiScale; - inRect.Min.y+=dpiScale; + inRect.Min.y+=3.0*dpiScale; inRect.Max.x-=dpiScale; - inRect.Max.y-=dpiScale; + inRect.Max.y-=3.0*dpiScale; + + int precision=inRect.Max.x-inRect.Min.x; + if (precision>512) precision=512; + ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { if (!e->isRunning()) { - for (unsigned short i=0; i<512; i++) { - float x=(float)i/512.0f; + for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/65536.0f; if (minLevel>y) minLevel=y; if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/65536.0f; y-=dcOff; if (y<-0.5f) y=-0.5f; if (y>0.5f) y=0.5f; @@ -419,7 +423,7 @@ void FurnaceGUI::drawChanOsc() { color=chanOscGrad.get(xVal,1.0f-yVal); } - dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); } } } From c89b733188c69f878025187f4f2c379647aae214 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Jun 2023 15:28:01 -0500 Subject: [PATCH 03/46] fix possible division by zero --- src/gui/chanOsc.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 9e67ef8f..0813ec48 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -351,6 +351,7 @@ void FurnaceGUI::drawChanOsc() { inRect.Max.y-=3.0*dpiScale; int precision=inRect.Max.x-inRect.Min.x; + if (precision<1) precision=1; if (precision>512) precision=512; ImGui::ItemSize(size,style.FramePadding.y); From c51413fe37c3c5799986faa209295e5cc4d9925c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Jun 2023 17:23:20 -0500 Subject: [PATCH 04/46] GUI: I can just use ClipRect... --- src/gui/chanOsc.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 0813ec48..3e580524 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -346,9 +346,9 @@ void FurnaceGUI::drawChanOsc() { ImRect rect=ImRect(minArea,maxArea); ImRect inRect=rect; inRect.Min.x+=dpiScale; - inRect.Min.y+=3.0*dpiScale; + inRect.Min.y+=2.0*dpiScale; inRect.Max.x-=dpiScale; - inRect.Max.y-=3.0*dpiScale; + inRect.Max.y-=2.0*dpiScale; int precision=inRect.Max.x-inRect.Min.x; if (precision<1) precision=1; @@ -424,7 +424,9 @@ void FurnaceGUI::drawChanOsc() { color=chanOscGrad.get(xVal,1.0f-yVal); } + ImGui::PushClipRect(inRect.Min,inRect.Max,false); dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); + ImGui::PopClipRect(); } } } From 7f0dc576d871e2e2e36240c2ba9dcc06e3fbe186 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Jun 2023 18:20:07 -0500 Subject: [PATCH 05/46] GUI: possibly fix asset name carry-over issue issue #1155 --- src/gui/insEdit.cpp | 2 ++ src/gui/intro.cpp | 1 + src/gui/sampleEdit.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e02f3969..ca254d2c 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2262,9 +2262,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(2+curIns); if (ImGui::InputText("##Name",&ins->name)) { MARK_MODIFIED; } + ImGui::PopID(); ImGui::TableNextRow(); ImGui::TableNextColumn(); diff --git a/src/gui/intro.cpp b/src/gui/intro.cpp index db886316..42d36957 100644 --- a/src/gui/intro.cpp +++ b/src/gui/intro.cpp @@ -95,6 +95,7 @@ void FurnaceGUI::endIntroTune() { selEnd=SelectionPoint(); cursor=SelectionPoint(); updateWindowTitle(); + updateScroll(0); } void FurnaceGUI::drawIntro(double introTime, bool monitor) { diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 238f37e8..8f5a9843 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -158,9 +158,11 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::PushID(2+curSample); if (ImGui::InputText("##SampleName",&sample->name)) { MARK_MODIFIED; } + ImGui::PopID(); ImGui::Separator(); From c83232f8da3ae323e75299b2a2ca7efcba76edc8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 11 Jun 2023 18:57:32 -0500 Subject: [PATCH 06/46] get rid of some custom tempo legacy --- src/engine/engine.cpp | 39 ++++----------------------------------- src/engine/engine.h | 2 +- src/engine/fileOps.cpp | 26 ++++++++++---------------- src/engine/song.h | 4 ---- src/gui/speed.cpp | 2 +- 5 files changed, 16 insertions(+), 57 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9f70aef0..5afc5407 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1444,9 +1444,6 @@ void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool ol // extra attributes song.subsong[0]->hz=c.getDouble("tickRate",60.0); - if (song.subsong[0]->hz!=60.0) { - song.subsong[0]->customTempo=true; - } } void DivEngine::createNew(const char* description, String sysName, bool inBase64) { @@ -2696,16 +2693,7 @@ void DivEngine::reset() { elapsedBars=0; elapsedBeats=0; nextSpeed=speeds.val[0]; - divider=60; - if (curSubSong->customTempo) { - divider=curSubSong->hz; - } else { - if (curSubSong->pal) { - divider=60; - } else { - divider=50; - } - } + divider=curSubSong->hz; globalPitch=0; for (int i=0; ireset(); @@ -2920,14 +2908,7 @@ const DivGroovePattern& DivEngine::getSpeeds() { } float DivEngine::getHz() { - if (curSubSong->customTempo) { - return curSubSong->hz; - } else if (curSubSong->pal) { - return 60.0; - } else { - return 50.0; - } - return 60.0; + return curSubSong->hz; } float DivEngine::getCurHz() { @@ -4354,23 +4335,11 @@ void DivEngine::updateSysFlags(int system, bool restart) { BUSY_END; } -void DivEngine::setSongRate(float hz, bool pal) { +void DivEngine::setSongRate(float hz) { BUSY_BEGIN; saveLock.lock(); - curSubSong->pal=!pal; curSubSong->hz=hz; - // what? - curSubSong->customTempo=true; - divider=60; - if (curSubSong->customTempo) { - divider=curSubSong->hz; - } else { - if (curSubSong->pal) { - divider=60; - } else { - divider=50; - } - } + divider=curSubSong->hz; saveLock.unlock(); BUSY_END; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 2b9f5992..bae7daa9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -954,7 +954,7 @@ class DivEngine { void updateSysFlags(int system, bool restart); // set Hz - void setSongRate(float hz, bool pal); + void setSongRate(float hz); // set remaining loops. -1 means loop forever. void setLoops(int loops); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index d3ae5421..5a752236 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -220,20 +220,22 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.subsong[0]->hilightB=reader.readC(); } + bool customTempo=false; + ds.subsong[0]->timeBase=reader.readC(); ds.subsong[0]->speeds.len=2; ds.subsong[0]->speeds.val[0]=reader.readC(); if (ds.version>0x07) { ds.subsong[0]->speeds.val[1]=reader.readC(); - ds.subsong[0]->pal=reader.readC(); - ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50; - ds.subsong[0]->customTempo=reader.readC(); + bool pal=reader.readC(); + ds.subsong[0]->hz=pal?60:50; + customTempo=reader.readC(); } else { ds.subsong[0]->speeds.len=1; } if (ds.version>0x0a) { String hz=reader.readString(3); - if (ds.subsong[0]->customTempo) { + if (customTempo) { try { ds.subsong[0]->hz=std::stoi(hz); } catch (std::exception& e) { @@ -304,7 +306,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.subsong[0]->hz=248; break; } - ds.subsong[0]->customTempo=true; ds.subsong[0]->timeBase=0; addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } @@ -1864,8 +1865,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { subSong->speeds.val[1]=reader.readC(); subSong->arpLen=reader.readC(); subSong->hz=reader.readF(); - subSong->pal=(subSong->hz>=53); - subSong->customTempo=true; subSong->patLen=reader.readS(); subSong->ordersLen=reader.readS(); @@ -2489,8 +2488,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { subSong->speeds.val[1]=reader.readC(); subSong->arpLen=reader.readC(); subSong->hz=reader.readF(); - subSong->pal=(subSong->hz>=53); - subSong->customTempo=true; subSong->patLen=reader.readS(); subSong->ordersLen=reader.readS(); @@ -3322,9 +3319,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.subsong[0]->pat[ch].effectCols=fxCols; } - ds.subsong[0]->pal=false; ds.subsong[0]->hz=50; - ds.subsong[0]->customTempo=false; ds.systemLen=(chCount+3)/4; for(int i=0; ispeeds.val[0]=(unsigned char)reader.readC(); ds.subsong[0]->hz=((double)reader.readC())/2.5; - ds.subsong[0]->customTempo=true; unsigned char masterVol=reader.readC(); @@ -3993,8 +3987,6 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) { ds.subsong[0]->ordersLen=seqLen; ds.subsong[0]->patLen=32; ds.subsong[0]->hz=50; - ds.subsong[0]->pal=true; - ds.subsong[0]->customTempo=true; ds.subsong[0]->pat[3].effectCols=3; ds.subsong[0]->speeds.val[0]=3; ds.subsong[0]->speeds.len=1; @@ -5819,12 +5811,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeString(song.author,true); w->writeC(curSubSong->hilightA); w->writeC(curSubSong->hilightB); + + int intHz=curSubSong->hz; w->writeC(curSubSong->timeBase); w->writeC(curSubSong->speeds.val[0]); w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]); - w->writeC(curSubSong->pal); - w->writeC(curSubSong->customTempo); + w->writeC((intHz<=53)?1:0); + w->writeC((intHz!=60 && intHz!=50)); char customHz[4]; memset(customHz,0,4); snprintf(customHz,4,"%d",(int)curSubSong->hz); diff --git a/src/engine/song.h b/src/engine/song.h index fd2d1e07..0a6149f8 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -153,8 +153,6 @@ struct DivSubSong { unsigned char timeBase, arpLen; DivGroovePattern speeds; short virtualTempoN, virtualTempoD; - bool pal; - bool customTempo; float hz; int patLen, ordersLen; @@ -177,8 +175,6 @@ struct DivSubSong { arpLen(1), virtualTempoN(150), virtualTempoD(150), - pal(true), - customTempo(false), hz(60.0), patLen(64), ordersLen(1) { diff --git a/src/gui/speed.cpp b/src/gui/speed.cpp index 9cb5c72d..5557c663 100644 --- a/src/gui/speed.cpp +++ b/src/gui/speed.cpp @@ -56,7 +56,7 @@ void FurnaceGUI::drawSpeed(bool asChild) { if (tempoView) setHz/=2.5; if (setHz<1) setHz=1; if (setHz>999) setHz=999; - e->setSongRate(setHz,setHz<52); + e->setSongRate(setHz); } if (tempoView) { ImGui::SameLine(); From 6f6128cae70561757dd87ce6ed5f9f8efda71848 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 00:12:02 -0500 Subject: [PATCH 07/46] GUI: fix Wayland scaling factor detection when full-screen is on --- src/gui/gui.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dd315ddf..673db6f1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3535,7 +3535,8 @@ bool FurnaceGUI::loop() { // update config x/y/w/h values based on scrMax state if (updateWindow) { logV("updateWindow is true"); - if (!scrMax) { + if (!scrMax && !fullScreen) { + logV("updating scrConf"); scrConfX=scrX; scrConfY=scrY; scrConfW=scrW; @@ -6257,11 +6258,17 @@ bool FurnaceGUI::init() { // special consideration for Wayland if (settings.dpiScale<0.5f) { if (strcmp(videoBackend,"wayland")==0) { - if (scrW<1) { + int realW=scrW; + int realH=scrH; + + SDL_GetWindowSize(sdlWin,&realW,&realH); + + if (realW<1) { logW("screen width is zero!\n"); dpiScale=1.0; } else { - dpiScale=(double)canvasW/(double)scrW; + dpiScale=(double)canvasW/(double)realW; + logV("we're on Wayland... scaling factor: %f",dpiScale); } } } From 3410eb8b9ebadbe112ce349eb507324394b7de16 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 00:17:51 -0500 Subject: [PATCH 08/46] GUI: make Window Debug tab on by default --- src/gui/debugWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 7eb3906a..bc562a2e 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -380,7 +380,7 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } - if (ImGui::TreeNode("Window Debug")) { + if (ImGui::TreeNodeEx("Window Debug",ImGuiTreeNodeFlags_DefaultOpen)) { ImGui::Text("Screen: %dx%d+%d+%d",scrW,scrH,scrX,scrY); ImGui::Text("Screen (Conf): %dx%d+%d+%d",scrConfW,scrConfH,scrConfX,scrConfY); ImGui::Text("Canvas: %dx%d",canvasW,canvasH); From 54e7bd295d5299bb0c55a2e1620d211d02aec4ad Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 00:18:20 -0500 Subject: [PATCH 09/46] GUI: give debug menu a default key bind --- src/gui/guiConst.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 2989ae72..9fcf78a3 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -506,7 +506,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_ABOUT", "About", 0), D("WINDOW_SETTINGS", "Settings", 0), D("WINDOW_MIXER", "Mixer", 0), - D("WINDOW_DEBUG", "Debug Menu", 0), + D("WINDOW_DEBUG", "Debug Menu", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_d), D("WINDOW_OSCILLOSCOPE", "Oscilloscope (master)", 0), D("WINDOW_VOL_METER", "Volume Meter", 0), D("WINDOW_STATS", "Statistics", 0), From 16adc1fb1b96352fc7c913fa5a06793ec17b9fde Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 02:18:50 -0500 Subject: [PATCH 10/46] GUI: fix intro carry-over in specific situations issue #1149 --- src/gui/gui.cpp | 1 + src/gui/gui.h | 2 +- src/gui/intro.cpp | 10 ++++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 673db6f1..16a05582 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6953,6 +6953,7 @@ FurnaceGUI::FurnaceGUI(): mustClear(2), initialScreenWipe(1.0f), introSkipDo(false), + introStopped(false), curTutorial(-1), curTutorialStep(0) { // value keys diff --git a/src/gui/gui.h b/src/gui/gui.h index f5b58109..50e90e5e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2029,7 +2029,7 @@ class FurnaceGUI { double monitorPos; int mustClear; float initialScreenWipe; - bool introSkipDo; + bool introSkipDo, introStopped; ImVec2 introMin, introMax; // tutorial diff --git a/src/gui/intro.cpp b/src/gui/intro.cpp index 42d36957..8ebbe020 100644 --- a/src/gui/intro.cpp +++ b/src/gui/intro.cpp @@ -19,6 +19,7 @@ #define _USE_MATH_DEFINES #include "gui.h" +#include "../ta-log.h" #include "imgui_internal.h" #include @@ -73,6 +74,8 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& } void FurnaceGUI::endIntroTune() { + if (introStopped) return; + logV("ending intro"); stop(); if (curFileName.empty()) { e->createNewFromDefaults(); @@ -96,6 +99,7 @@ void FurnaceGUI::endIntroTune() { cursor=SelectionPoint(); updateWindowTitle(); updateScroll(0); + introStopped=true; } void FurnaceGUI::drawIntro(double introTime, bool monitor) { @@ -291,7 +295,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) { if (introSkipDo) { introSkip+=ImGui::GetIO().DeltaTime; if (introSkip>=0.5) { - if (e->isPlaying()) endIntroTune(); + if (!shortIntro) endIntroTune(); introPos=0.1; if (introSkip>=0.75) introPos=12.0; } @@ -318,7 +322,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) { e->setRepeatPattern(false); play(); } - if (e->isPlaying() && introPos>=10.0 && !shortIntro) endIntroTune(); + if (introPos>=10.0 && !shortIntro) endIntroTune(); introPos+=ImGui::GetIO().DeltaTime; if (introPos>=(shortIntro?1.0:11.0)) { introPos=12.0; @@ -326,5 +330,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) { commitTutorial(); } } + } else if (!shortIntro) { + endIntroTune(); } } From 91a7132e796fef7e2aed6dc34d27052242ca86b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 15:58:16 -0500 Subject: [PATCH 11/46] GUI: add a draw metric --- src/gui/debugWindow.cpp | 1 + src/gui/gui.cpp | 6 ++++++ src/gui/gui.h | 1 + 3 files changed, 8 insertions(+) diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index bc562a2e..e440b589 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -549,6 +549,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("audio: %dµs",lastProcTime); ImGui::Text("render: %.0fµs",(double)renderTimeDelta/perfFreq); + ImGui::Text("draw: %.0fµs",(double)drawTimeDelta/perfFreq); ImGui::Text("layout: %.0fµs",(double)layoutTimeDelta/perfFreq); ImGui::Text("event: %.0fµs",(double)eventTimeDelta/perfFreq); ImGui::Separator(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 16a05582..edc71376 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5810,6 +5810,7 @@ bool FurnaceGUI::loop() { renderTimeBegin=SDL_GetPerformanceCounter(); ImGui::Render(); renderTimeEnd=SDL_GetPerformanceCounter(); + drawTimeBegin=SDL_GetPerformanceCounter(); rend->renderGUI(); if (mustClear) { rend->clear(ImVec4(0,0,0,0)); @@ -5824,12 +5825,14 @@ bool FurnaceGUI::loop() { } } rend->present(); + drawTimeEnd=SDL_GetPerformanceCounter(); if (settings.renderClearPos) { rend->clear(uiColors[GUI_COLOR_BACKGROUND]); } layoutTimeDelta=layoutTimeEnd-layoutTimeBegin; renderTimeDelta=renderTimeEnd-renderTimeBegin; + drawTimeDelta=drawTimeEnd-drawTimeBegin; eventTimeDelta=eventTimeEnd-eventTimeBegin; soloTimeout-=ImGui::GetIO().DeltaTime; @@ -6832,6 +6835,9 @@ FurnaceGUI::FurnaceGUI(): renderTimeBegin(0), renderTimeEnd(0), renderTimeDelta(0), + drawTimeBegin(0), + drawTimeEnd(0), + drawTimeDelta(0), eventTimeBegin(0), eventTimeEnd(0), eventTimeDelta(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 50e90e5e..77bcab26 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1864,6 +1864,7 @@ class FurnaceGUI { int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta; int renderTimeBegin, renderTimeEnd, renderTimeDelta; + int drawTimeBegin, drawTimeEnd, drawTimeDelta; int eventTimeBegin, eventTimeEnd, eventTimeDelta; FurnaceGUIPerfMetric perfMetrics[64]; From f605ae9f6524d3179f0003d85a33e21212cb5e31 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 16:47:54 -0500 Subject: [PATCH 12/46] GUI: prepare for DirectX 11 render backend --- CMakeLists.txt | 20 ++++++- src/gui/gui.cpp | 4 +- src/gui/gui.h | 8 ++- src/gui/render.cpp | 11 ++++ src/gui/render/renderDX11.cpp | 100 ++++++++++++++++++++++++++++++++++ src/gui/render/renderDX11.h | 64 ++++++++++++++++++++++ src/gui/settings.cpp | 5 ++ 7 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 src/gui/render/renderDX11.cpp create mode 100644 src/gui/render/renderDX11.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b55f514f..da3256a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,11 @@ if (APPLE) else() set(WITH_RENDER_OPENGL_DEFAULT ON) endif() +if (WIN32) + set(WITH_RENDER_DX11_DEFAULT ON) +else() + set(WITH_RENDER_DX11_DEFAULT OFF) +endif() if (ANDROID) set(USE_GLES_DEFAULT ON) @@ -75,6 +80,7 @@ option(USE_BACKWARD "Use backward-cpp to print a backtrace on crash/abort." ${US option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT}) option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT}) option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${WITH_RENDER_OPENGL_DEFAULT}) +option(WITH_RENDER_DX11 "Whether to build with the DirectX 11 render backend." ${WITH_RENDER_DX11_DEFAULT}) option(USE_GLES "Use OpenGL ES for the OpenGL render backend." ${USE_GLES_DEFAULT}) option(SYSTEM_FFTW "Use a system-installed version of FFTW instead of the vendored one" OFF) option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF) @@ -302,7 +308,7 @@ else() endif() if (BUILD_GUI) - if (NOT WITH_RENDER_SDL AND NOT WITH_RENDER_OPENGL) + if (NOT WITH_RENDER_SDL AND NOT WITH_RENDER_OPENGL AND NOT WITH_RENDER_DX11) message(FATAL_ERROR "No render backends selected!") endif() endif() @@ -731,6 +737,18 @@ if (WITH_RENDER_OPENGL) message(STATUS "UI render backend: OpenGL") endif() +if (WITH_RENDER_DX11) + if (WIN32) + list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp) + list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp) + list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11) + list(APPEND DEPENDENCIES_LIBRARIES D3D11 d3dcompiler) + message(STATUS "UI render backend: DirectX 11") + else() + message(FATAL_ERROR "DirectX 11 render backend only for Windows!") + endif() +endif() + if (NOT WIN32 AND NOT APPLE) CHECK_INCLUDE_FILE(sys/io.h SYS_IO_FOUND) CHECK_INCLUDE_FILE(linux/input.h LINUX_INPUT_FOUND) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index edc71376..732ce551 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6148,7 +6148,7 @@ bool FurnaceGUI::init() { logV("window size: %dx%d",scrW,scrH); if (!initRender()) { - if (settings.renderBackend=="OpenGL") { + if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) { settings.renderBackend=""; e->setConf("renderBackend",""); e->saveConf(); @@ -6234,7 +6234,7 @@ bool FurnaceGUI::init() { logD("starting render backend..."); if (!rend->init(sdlWin)) { - if (settings.renderBackend=="OpenGL") { + if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) { settings.renderBackend=""; e->setConf("renderBackend",""); e->saveConf(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 77bcab26..19fdb281 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -71,16 +71,22 @@ enum FurnaceGUIRenderBackend { GUI_BACKEND_SDL=0, - GUI_BACKEND_GL + GUI_BACKEND_GL, + GUI_BACKEND_DX11 }; #ifdef HAVE_RENDER_SDL #define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL #define GUI_BACKEND_DEFAULT_NAME "SDL" #else +#ifdef HAVE_RENDER_DX11 +#define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11 +#define GUI_BACKEND_DEFAULT_NAME "DirectX 11" +#else #define GUI_BACKEND_DEFAULT GUI_BACKEND_GL #define GUI_BACKEND_DEFAULT_NAME "OpenGL" #endif +#endif // TODO: // - add colors for FM envelope and waveform diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 908fd57d..5ad753c2 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -25,12 +25,17 @@ #ifdef HAVE_RENDER_GL #include "render/renderGL.h" #endif +#ifdef HAVE_RENDER_DX11 +#include "render/renderDX11.h" +#endif bool FurnaceGUI::initRender() { if (rend!=NULL) return false; if (settings.renderBackend=="OpenGL") { renderBackend=GUI_BACKEND_GL; + } else if (settings.renderBackend=="DirectX 11") { + renderBackend=GUI_BACKEND_DX11; } else if (settings.renderBackend=="SDL") { renderBackend=GUI_BACKEND_SDL; } else { @@ -44,6 +49,12 @@ bool FurnaceGUI::initRender() { rend=new FurnaceGUIRenderGL; break; #endif +#ifdef HAVE_RENDER_DX11 + case GUI_BACKEND_DX11: + logI("render backend: DirectX 11"); + rend=new FurnaceGUIRenderDX11; + break; +#endif #ifdef HAVE_RENDER_SDL case GUI_BACKEND_SDL: logI("render backend: SDL_Renderer"); diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp new file mode 100644 index 00000000..6e7bdb6b --- /dev/null +++ b/src/gui/render/renderDX11.cpp @@ -0,0 +1,100 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "renderDX11.h" + +ImTextureID FurnaceGUIRenderDX11::getTextureID(void* which) { + return NULL; +} + +bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) { + return false; +} + +bool FurnaceGUIRenderDX11::unlockTexture(void* which) { + return false; +} + +bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) { + return false; +} + +void* FurnaceGUIRenderDX11::createTexture(bool dynamic, int width, int height) { + return NULL; +} + +bool FurnaceGUIRenderDX11::destroyTexture(void* which) { + return false; +} + +void FurnaceGUIRenderDX11::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) { +} + +void FurnaceGUIRenderDX11::clear(ImVec4 color) { +} + +bool FurnaceGUIRenderDX11::newFrame() { + return true; +} + +void FurnaceGUIRenderDX11::createFontsTexture() { +} + +void FurnaceGUIRenderDX11::destroyFontsTexture() { +} + +void FurnaceGUIRenderDX11::renderGUI() { +} + +void FurnaceGUIRenderDX11::wipe(float alpha) { +} + +void FurnaceGUIRenderDX11::present() { +} + +bool FurnaceGUIRenderDX11::getOutputSize(int& w, int& h) { + return false; +} + +int FurnaceGUIRenderDX11::getWindowFlags() { + return 0; +} + +void FurnaceGUIRenderDX11::preInit() { +} + +bool FurnaceGUIRenderDX11::init(SDL_Window* win) { + return false; +} + +void FurnaceGUIRenderDX11::initGUI(SDL_Window* win) { +} + +bool FurnaceGUIRenderDX11::quit() { + return false; +} + +void FurnaceGUIRenderDX11::quitGUI() { +} + +FurnaceGUIRenderDX11::~FurnaceGUIRenderDX11() { +} diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h new file mode 100644 index 00000000..6900848f --- /dev/null +++ b/src/gui/render/renderDX11.h @@ -0,0 +1,64 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2023 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../gui.h" + +class FurnaceGUIRenderDX11: public FurnaceGUIRender { + ID3D11Device* device; + ID3D11DeviceContext* context; + ID3D11RenderTargetView* renderTarget; + SDL_Window* sdlWin; + IDXGISwapChain* swapchain; + float quadVertex[4][3]; + unsigned int quadBuf; + + void createRenderTarget(); + + public: + ImTextureID getTextureID(void* which); + bool lockTexture(void* which, void** data, int* pitch); + bool unlockTexture(void* which); + bool updateTexture(void* which, void* data, int pitch); + void* createTexture(bool dynamic, int width, int height); + bool destroyTexture(void* which); + void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); + void setBlendMode(FurnaceGUIBlendMode mode); + void clear(ImVec4 color); + bool newFrame(); + void createFontsTexture(); + void destroyFontsTexture(); + void renderGUI(); + void wipe(float alpha); + void present(); + bool getOutputSize(int& w, int& h); + int getWindowFlags(); + void preInit(); + bool init(SDL_Window* win); + void initGUI(SDL_Window* win); + void quitGUI(); + bool quit(); + FurnaceGUIRenderDX11(): + device(NULL), + context(NULL), + renderTarget(NULL), + sdlWin(NULL), + swapchain(NULL) { + memset(quadVertex,0,4*3*sizeof(float)); + } +}; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9648de12..5e57640d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1303,6 +1303,11 @@ void FurnaceGUI::drawSettings() { settings.renderBackend="SDL"; } #endif +#ifdef HAVE_RENDER_DX11 + if (ImGui::Selectable("DirectX 11",curRenderBackend=="DirectX 11")) { + settings.renderBackend="DirectX 11"; + } +#endif #ifdef HAVE_RENDER_GL if (ImGui::Selectable("OpenGL",curRenderBackend=="OpenGL")) { settings.renderBackend="OpenGL"; From 2c912da89af3182cd52216342e96ec75504e2ed5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 19:17:46 -0500 Subject: [PATCH 13/46] GUI: DirectX 11 render backend, part 1 --- CMakeLists.txt | 2 +- src/gui/render/renderDX11.cpp | 129 ++++++++++++++++++++++++++++++++-- src/gui/render/renderDX11.h | 12 +++- 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index da3256a1..30174880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -742,7 +742,7 @@ if (WITH_RENDER_DX11) list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp) list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp) list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11) - list(APPEND DEPENDENCIES_LIBRARIES D3D11 d3dcompiler) + list(APPEND DEPENDENCIES_LIBRARIES d3d11 d3dcompiler) message(STATUS "UI render backend: DirectX 11") else() message(FATAL_ERROR "DirectX 11 render backend only for Windows!") diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 6e7bdb6b..dcfb0998 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -17,7 +17,61 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define INCLUDE_D3D11 #include "renderDX11.h" +#include +#include "backends/imgui_impl_dx11.h" +#include "../../ta-log.h" + +const D3D_FEATURE_LEVEL possibleFeatureLevels[2]={ + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_0 +}; + +bool FurnaceGUIRenderDX11::destroyRenderTarget() { + if (renderTarget!=NULL) { + renderTarget->Release(); + renderTarget=NULL; + return true; + } + return false; +} + +bool FurnaceGUIRenderDX11::createRenderTarget() { + ID3D11Texture2D* screen=NULL; + HRESULT result; + + destroyRenderTarget(); + + if (swapchain==NULL || device==NULL) { + logW("createRenderTarget: swapchain or device are NULL!"); + return false; + } + + result=swapchain->GetBuffer(0,IID_PPV_ARGS(&screen)); + if (result!=S_OK) { + logW("createRenderTarget: could not get buffer! %.8x",result); + return false; + } + if (screen==NULL) { + logW("createRenderTarget: screen is null!"); + return false; + } + + result=device->CreateRenderTargetView(screen,NULL,&renderTarget); + if (result!=S_OK) { + logW("createRenderTarget: could not create render target view! %.8x",result); + screen->Release(); + return false; + } + if (renderTarget==NULL) { + logW("createRenderTarget: what the hell the render target is null?"); + screen->Release(); + return false; + } + + return true; +} ImTextureID FurnaceGUIRenderDX11::getTextureID(void* which) { return NULL; @@ -50,25 +104,40 @@ void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) { } void FurnaceGUIRenderDX11::clear(ImVec4 color) { + float floatColor[4]={ + color.x*color.w, + color.y*color.w, + color.z*color.w, + color.w, + }; + + context->OMSetRenderTargets(1,&renderTarget,NULL); + context->ClearRenderTargetView(renderTarget,floatColor); } bool FurnaceGUIRenderDX11::newFrame() { + ImGui_ImplDX11_NewFrame(); return true; } void FurnaceGUIRenderDX11::createFontsTexture() { + ImGui_ImplDX11_CreateDeviceObjects(); } void FurnaceGUIRenderDX11::destroyFontsTexture() { + ImGui_ImplDX11_InvalidateDeviceObjects(); } void FurnaceGUIRenderDX11::renderGUI() { + ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); } void FurnaceGUIRenderDX11::wipe(float alpha) { + // TODO } void FurnaceGUIRenderDX11::present() { + swapchain->Present(1,0); } bool FurnaceGUIRenderDX11::getOutputSize(int& w, int& h) { @@ -83,18 +152,68 @@ void FurnaceGUIRenderDX11::preInit() { } bool FurnaceGUIRenderDX11::init(SDL_Window* win) { - return false; + SDL_SysWMinfo sysWindow; + D3D_FEATURE_LEVEL featureLevel; + + SDL_VERSION(&sysWindow.version); + if (SDL_GetWindowWMInfo(win,&sysWindow)==SDL_FALSE) { + logE("could not get window WM info! %s",SDL_GetError()); + return false; + } + HWND window=(HWND)sysWindow.info.win.window; + + DXGI_SWAP_CHAIN_DESC chainDesc; + memset(&chainDesc,0,sizeof(chainDesc)); + chainDesc.BufferDesc.Width=0; + chainDesc.BufferDesc.Height=0; + chainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; + chainDesc.BufferDesc.RefreshRate.Numerator=60; + chainDesc.BufferDesc.RefreshRate.Denominator=1; + chainDesc.SampleDesc.Count=1; + chainDesc.SampleDesc.Quality=0; + chainDesc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT; + chainDesc.BufferCount=2; + chainDesc.OutputWindow=window; + chainDesc.Windowed=TRUE; // TODO: what if we're in full screen mode? + chainDesc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD; + chainDesc.Flags=DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + + HRESULT result=D3D11CreateDeviceAndSwapChain(NULL,D3D_DRIVER_TYPE_HARDWARE,NULL,0,possibleFeatureLevels,2,D3D11_SDK_VERSION,&chainDesc,&swapchain,&device,&featureLevel,&context); + if (result!=S_OK) { + logE("could not create device and/or swap chain! %.8x",result); + return false; + } + + createRenderTarget(); + return true; } void FurnaceGUIRenderDX11::initGUI(SDL_Window* win) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGui_ImplSDL2_InitForD3D(win); + ImGui_ImplDX11_Init(device,context); } bool FurnaceGUIRenderDX11::quit() { - return false; + destroyRenderTarget(); + + if (swapchain!=NULL) { + swapchain->Release(); + swapchain=NULL; + } + if (context!=NULL) { + context->Release(); + context=NULL; + } + if (device!=NULL) { + device->Release(); + device=NULL; + } + return true; } void FurnaceGUIRenderDX11::quitGUI() { -} - -FurnaceGUIRenderDX11::~FurnaceGUIRenderDX11() { + ImGui_ImplDX11_Shutdown(); } diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h index 6900848f..9db29851 100644 --- a/src/gui/render/renderDX11.h +++ b/src/gui/render/renderDX11.h @@ -18,17 +18,24 @@ */ #include "../gui.h" +#ifdef INCLUDE_D3D11 +#include +#else +typedef void ID3D11DeviceContext; +typedef void ID3D11RenderTargetView; +typedef void IDXGISwapChain; +#endif class FurnaceGUIRenderDX11: public FurnaceGUIRender { ID3D11Device* device; ID3D11DeviceContext* context; ID3D11RenderTargetView* renderTarget; - SDL_Window* sdlWin; IDXGISwapChain* swapchain; float quadVertex[4][3]; unsigned int quadBuf; - void createRenderTarget(); + bool destroyRenderTarget(); + bool createRenderTarget(); public: ImTextureID getTextureID(void* which); @@ -57,7 +64,6 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { device(NULL), context(NULL), renderTarget(NULL), - sdlWin(NULL), swapchain(NULL) { memset(quadVertex,0,4*3*sizeof(float)); } From cf144f4fe9e24dbfbe89c2067ddf909968fde588 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 19:43:26 -0500 Subject: [PATCH 14/46] GUI: DirectX 11 render backend, part 2 --- src/gui/gui.cpp | 3 ++- src/gui/gui.h | 1 + src/gui/render/abstract.cpp | 5 ++++- src/gui/render/renderDX11.cpp | 22 +++++++++++++++++++++- src/gui/render/renderDX11.h | 7 +++++-- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 732ce551..6ea8a941 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3420,6 +3420,7 @@ bool FurnaceGUI::loop() { logV("portrait: %d (%dx%d)",portrait,scrW,scrH); logD("window resized to %dx%d",scrW,scrH); updateWindow=true; + rend->resized(ev); break; case SDL_WINDOWEVENT_MOVED: scrX=ev.window.data1; @@ -5824,8 +5825,8 @@ bool FurnaceGUI::loop() { } } } - rend->present(); drawTimeEnd=SDL_GetPerformanceCounter(); + rend->present(); if (settings.renderClearPos) { rend->clear(uiColors[GUI_COLOR_BACKGROUND]); } diff --git a/src/gui/gui.h b/src/gui/gui.h index 19fdb281..81266e43 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1248,6 +1248,7 @@ class FurnaceGUIRender { virtual bool destroyTexture(void* which); virtual void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); virtual void setBlendMode(FurnaceGUIBlendMode mode); + virtual void resized(const SDL_Event& ev); virtual void clear(ImVec4 color); virtual bool newFrame(); virtual void createFontsTexture(); diff --git a/src/gui/render/abstract.cpp b/src/gui/render/abstract.cpp index 996cf4e8..a45c4ecd 100644 --- a/src/gui/render/abstract.cpp +++ b/src/gui/render/abstract.cpp @@ -49,6 +49,9 @@ void FurnaceGUIRender::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode void FurnaceGUIRender::setBlendMode(FurnaceGUIBlendMode mode) { } +void FurnaceGUIRender::resized(const SDL_Event& ev) { +} + void FurnaceGUIRender::clear(ImVec4 color) { } @@ -97,4 +100,4 @@ void FurnaceGUIRender::quitGUI() { } FurnaceGUIRender::~FurnaceGUIRender() { -} \ No newline at end of file +} diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index dcfb0998..767878ce 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -48,6 +48,15 @@ bool FurnaceGUIRenderDX11::createRenderTarget() { return false; } + DXGI_SWAP_CHAIN_DESC chainDesc; + memset(&chainDesc,0,sizeof(chainDesc)); + if (swapchain->GetDesc(&chainDesc)!=S_OK) { + logW("createRenderTarget: could not get swapchain desc!"); + } else { + outW=chainDesc.BufferDesc.Width; + outH=chainDesc.BufferDesc.Height; + } + result=swapchain->GetBuffer(0,IID_PPV_ARGS(&screen)); if (result!=S_OK) { logW("createRenderTarget: could not get buffer! %.8x",result); @@ -103,6 +112,15 @@ void FurnaceGUIRenderDX11::setTextureBlendMode(void* which, FurnaceGUIBlendMode void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) { } +void FurnaceGUIRenderDX11::resized(const SDL_Event& ev) { + destroyRenderTarget(); + swapchain->ResizeBuffers(0,0,0,DXGI_FORMAT_UNKNOWN,0); + + + + createRenderTarget(); +} + void FurnaceGUIRenderDX11::clear(ImVec4 color) { float floatColor[4]={ color.x*color.w, @@ -141,7 +159,9 @@ void FurnaceGUIRenderDX11::present() { } bool FurnaceGUIRenderDX11::getOutputSize(int& w, int& h) { - return false; + w=outW; + h=outH; + return true; } int FurnaceGUIRenderDX11::getWindowFlags() { diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h index 9db29851..1a3d7dca 100644 --- a/src/gui/render/renderDX11.h +++ b/src/gui/render/renderDX11.h @@ -31,8 +31,8 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { ID3D11DeviceContext* context; ID3D11RenderTargetView* renderTarget; IDXGISwapChain* swapchain; + int outW, outH; float quadVertex[4][3]; - unsigned int quadBuf; bool destroyRenderTarget(); bool createRenderTarget(); @@ -46,6 +46,7 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { bool destroyTexture(void* which); void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode); void setBlendMode(FurnaceGUIBlendMode mode); + void resized(const SDL_Event& ev); void clear(ImVec4 color); bool newFrame(); void createFontsTexture(); @@ -64,7 +65,9 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { device(NULL), context(NULL), renderTarget(NULL), - swapchain(NULL) { + swapchain(NULL), + outW(0), + outH(0) { memset(quadVertex,0,4*3*sizeof(float)); } }; From f73d831d11abf4053c11e15f973c16f5e01e9f70 Mon Sep 17 00:00:00 2001 From: Electric Keet Date: Mon, 12 Jun 2023 19:31:24 -0700 Subject: [PATCH 15/46] Expanding interface docs. --- doc/2-interface/asset-list.md | 33 ++++++++++++++-- doc/2-interface/control-edit.png | Bin 0 -> 635 bytes doc/2-interface/control-metronome.png | Bin 0 -> 623 bytes doc/2-interface/control-play-pattern.png | Bin 0 -> 657 bytes doc/2-interface/control-play-repeat.png | Bin 0 -> 313 bytes doc/2-interface/control-play.png | Bin 0 -> 351 bytes doc/2-interface/control-repeat.png | Bin 0 -> 693 bytes doc/2-interface/control-step.png | Bin 0 -> 413 bytes doc/2-interface/control-stop.png | Bin 0 -> 460 bytes doc/2-interface/controls-classic.png | Bin 0 -> 21016 bytes doc/2-interface/controls-compact.png | Bin 0 -> 16054 bytes doc/2-interface/controls-split.png | Bin 0 -> 20672 bytes doc/2-interface/controls-vertical.png | Bin 0 -> 10175 bytes doc/2-interface/instruments-folder.png | Bin 0 -> 50247 bytes doc/2-interface/instruments.png | Bin 0 -> 52044 bytes doc/2-interface/play-edit-controls.md | 32 +++++++++++----- doc/2-interface/samples.png | Bin 0 -> 19676 bytes doc/2-interface/song-info.md | 46 ++++++++++++++++++++++- doc/2-interface/wavetables.png | Bin 0 -> 11257 bytes 19 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 doc/2-interface/control-edit.png create mode 100644 doc/2-interface/control-metronome.png create mode 100644 doc/2-interface/control-play-pattern.png create mode 100644 doc/2-interface/control-play-repeat.png create mode 100644 doc/2-interface/control-play.png create mode 100644 doc/2-interface/control-repeat.png create mode 100644 doc/2-interface/control-step.png create mode 100644 doc/2-interface/control-stop.png create mode 100644 doc/2-interface/controls-classic.png create mode 100644 doc/2-interface/controls-compact.png create mode 100644 doc/2-interface/controls-split.png create mode 100644 doc/2-interface/controls-vertical.png create mode 100644 doc/2-interface/instruments-folder.png create mode 100644 doc/2-interface/instruments.png create mode 100644 doc/2-interface/samples.png create mode 100644 doc/2-interface/wavetables.png diff --git a/doc/2-interface/asset-list.md b/doc/2-interface/asset-list.md index 7e8dc9a6..44c3fefa 100644 --- a/doc/2-interface/asset-list.md +++ b/doc/2-interface/asset-list.md @@ -1,11 +1,38 @@ # instrument list -image +![instruments window](instruments.png) + +Buttons from left to right: +- **Add:** Creates a new, default instrument. +- **Duplicate:** Duplicates the currently selected instrument. +- **Open:** Brings up a file dialog to load a file as a new instrument at the end of the list. +- **Save:** Brings up a file dialog to save the currently selected instrument. +- **Toggle folders/standard view:** Enables (and disables) folder view, explained below. +- **Move up:** Moves the currently selected instrument up in the list. Pattern data will automatically be adjusted to match. +- **Move down:** Same, but downward. +- **Delete:** Deletes the currently selected instrument. Pattern data will be adjusted to use the next available instrument in the list. + +## folder view + +![instruments window in folder view](instruments-folder.png) + +In folder view, the "Move up" and "Move down buttons disappear and a new one appears: +- **New Folder:** Creates a new folder. + +Instruments may be dragged from folder to folder and even rearranged within folders without changing their associated numbers. + +Right-clicking on a folder allows one to rename or delete it. Deleting a folder does not remove the instruments in it. # wavetable list -image +![wavetables window](wavetables.png) + +Everything from the instrument list applies here also, with one major difference: Moving waves around with the buttons will change their associated numbers in the list but _not_ in pattern or instrument data. Be careful! # sample list -image +![samples window](samples.png) + +Everything from the wavetables list applies here also, with the addition of two buttons: +- **Preview:** Plays the selected sample at its default note. +- **Stop preview:** Stops the sample playback. diff --git a/doc/2-interface/control-edit.png b/doc/2-interface/control-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..35fce78c7800c164e3e128a92fdf967a1d171cb4 GIT binary patch literal 635 zcmV->0)+jEP)H5l+GuY*WgXOt1wG7j}rHapW>$z zwx(~7$b8sQAoan6D#c>LAB7`$A`1XS{THr(?IUF`m1b+#QJ7UJop9WpztwDa9Fhfi zm}`7}H-1PNYTJsGg`Qp*|J1W-l`@Ikt?Z|T3ub;oY908GCsW&lKS$OE=Y>QB`URb? V5kE*_&oux5002ovPDHLkV1iLSL~8&5 literal 0 HcmV?d00001 diff --git a/doc/2-interface/control-metronome.png b/doc/2-interface/control-metronome.png new file mode 100644 index 0000000000000000000000000000000000000000..0c66725653361836a835c62b1bbc852cb00b9664 GIT binary patch literal 623 zcmV-#0+9WQP)lqQy}higtVl>m%gf8*;o(zLQ~Ue-X=!PGettAGG`P69qobpRg@raYHf?QfNl8he zp`o0doK{v=kdTn=?d?THMQCVft*xzJUtjh0_1D+e=;-KCQBhJ-Qpm{2*x1FLSI z$vHVWT3TB1@$r9ufB5+L@bK{K>+9kSuxJ1P0YFJaK~#8N?UcoC>p%cSeasMMX6BTc zx$ph|?{v{fi5+L`Y}!rF?q2CgGqUeUzr`${pY0`NAhVY+hj@6QUn|BSR;=|y2~kC6 zg{(w_g^~u=qMl6F7ZzxxK%oKI>UXGSp%ElRbSmpqL5&0m8pGT=<%VbhH#R72<^qQJ z-bdg%Lqz3BD4arhGnZ9Pg~BzYH*;F$S}07A-Nh#pp)kdA7yp?Gg%(fl;+B?B=+bi+ zJ-Wi(r1}9?anw)?-WijY;R{9oa_=tGR^I_ zi9GTb4%$51+qHd1`@nb0b9CR|;4((sZ`)TeIP_%zSH4e?fVkgw9>=WE-*GX15wFO+ zQ2@Y{!jWKk4#1@p5+}7({(&JTL$N literal 0 HcmV?d00001 diff --git a/doc/2-interface/control-play-pattern.png b/doc/2-interface/control-play-pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..a6eb5d01eff4e36bf500014d1424181488b7d881 GIT binary patch literal 657 zcmV;C0&e|@P)x&GpwwvNJvPfrKRiZ>tSJGYin!v_V&HKy@-g2udlCOUS67-nnXlI zXJ=>i_4SsPmihVlNl8gnRaJ+Fhw<_8-QC^1yu8xV(z?33QBhH3WMuO4@@#BuuCA_^ zmzP>vTED-)p`oF%v9a6R+wAP@_xJa~!NE{aPFMdt&d%oM=5=*-goK2Jg@v`XweIfj z@9*!z!ooW{JA8b6F)=ZTiHYFg;66S+r>|)x0003jNkl{ElQvG%>uS&aC)t)Ux`fv&FsyhWx*sBP-A4m)I7?Sj^e0tG!@>3> z^*ElzK*b5_GrxcV!a=eKhGEN>Cq_OC8)AwiZF-Z;R%{0fk+cfmOoSAz0G7T*5{(1; z+&U?5Iymno6#$=K>R_!OaNJ}L0Pmt^1Q9nlZ50@nca35RoQx%aIB3Zr15Q~5%9;$y z;8dBQupxsgICV?VcsACqgR|2Y`1d?y_yFhd$Qq|(hTncWH481oRrGX`63jVwYBu%|z|sE_Lj12tc$$7>S~V!4hAI=L8} r`4UOFik3`dF(iMtR_ppPLjL^%CrueL2XejY00000NkvXXu0mjfk*G+; literal 0 HcmV?d00001 diff --git a/doc/2-interface/control-play-repeat.png b/doc/2-interface/control-play-repeat.png new file mode 100644 index 0000000000000000000000000000000000000000..853cdcffee4396101838274feca24dce449ef147 GIT binary patch literal 313 zcmV-90mlA`P)FMcZWo0@#I%Z~OF)=at`1ndnN{EPv`}_M*QBhMq{r&6f>png{K|w)jX=!L^XhK3l^OB$k0001#NklnhWLF)$g>sgrp00000 LNkvXXu0mjf#;A@G literal 0 HcmV?d00001 diff --git a/doc/2-interface/control-play.png b/doc/2-interface/control-play.png new file mode 100644 index 0000000000000000000000000000000000000000..feafe1681d86d27ad861325375651b2612157dcf GIT binary patch literal 351 zcmV-l0igbgP)5{E0PAIdY7UfV&w$N*%WPPWR@9aDvtS5NJstDXWqTn061Iu} xTU7vC2~gG8DgbO%0&Law!B$=0Uod@b=nui93A{ue*j4}l002ovPDHLkV1iCdb#-<6`ud%nolHzj`}_Op>FJS?kz!(EHa0eGZEf}S z_5S|;adC0%?Cd!?IfjOYv9Yn*+S*1&MtOO8I5;>#K|%5H@rj9vV`F2zy}f>Ze(mk; z(b3VVsi{9dKT}gv`T6G z+1bFr!0PJiy1KgE-QDKq=682@_xJaurKOdXm2`A;QBhIE#KdJ~WsHoBuCA`XzrW|_ z=eD-Cf`WoeOG{K#RDpqkQc_aO%gaSYMOIc;T3TAPw6x63%wJz$U0q#@ii%lTS(%xc zs;a7ujg5GCc)Yy4eSLjtX=(58?{jl=L_|cUrl!=?)Q*mhN=iyiO-);9&f)+70X|7Y zK~#8N?aHS|}ZB9#!M2zv2-e%-U>9tZVJ2c00000NkvXXu0mjfIV4o_ literal 0 HcmV?d00001 diff --git a/doc/2-interface/control-step.png b/doc/2-interface/control-step.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6262da71130a3db51077004a8cae136d8aecb8 GIT binary patch literal 413 zcmV;O0b>4%P)U3Z&d!I2hyDHiXlQ8u{{CHEU3hqStgNib$;nGg zOE@?mU006~FL_t(| zUhU4;vV$-TMN!G7_LAOvz4iaUE)3w&goweshV19FY{C1s%Cb`ja87(gKRIfHAjG^d z&;|=E^16aaFM#+NyaMYkn!wP-aa;%S7}rA{{BIG_xIP2Z$mN9>8^lTwFuw}=J!63Q z#Yzw`jgsy-0DDxztAN_$0k|w%3AY5U+B;Kd5^Qq`petz4H@Go-x`z|XIvCOWt$)y7 z|Bn&fqao=1)~jf*m2^sw(mh%tm7tycL$oDyNJNoMM&5#)o1+N9%v@#w20<~lL;0LiGN(ToPfRCYNUBKSn6 z4j>RdwS6!`T@H+fEclVu0Aiu#YXDgfKkKBO=afnVn5ydI8ZgGLfW4jr-sk|U-aqdC zWIKM|yl>mFe*b7PP8vQXEl!Fp|Is||StuakC~7`d>IWq>wvA^PLM}lmqVN*)nTx^4 zN>OE(ga$o#3nVEOS{Nh1;mRVMQ9)jXf@Gi*ocaYMj}oAF2|`r>0000K literal 0 HcmV?d00001 diff --git a/doc/2-interface/controls-classic.png b/doc/2-interface/controls-classic.png new file mode 100644 index 0000000000000000000000000000000000000000..a8a093fa92fbe3d28de256c00fb0c840bf9b82ee GIT binary patch literal 21016 zcmZ_$V|XS_)HVvo&cx2dwr$&)WMWTj+qP|EV%s(+b|w?s$?p4k-{;srzWx13A4y+V zS65e8t#x8mB9s&)5#ey*KtMncrKQAFKtMnTfgd#(FyPE;Sf3*Zi1LoKn6R3=o)JtL zLUf=q6P!dpl>vy6u46wHHe`_jdUW9RwLOT}BNm)CdI+dDjs`L;nGCs%bO;$N4F(2E z;%y{lxtM%@IHj6@hh7(zaDChAv#2HxDd!w)mHde_KUXDn{UdGgX-)*i{8I3 z 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 W2smlyKMHhw1Q9IGnm3yIQvVCO_aC7E literal 0 HcmV?d00001 diff --git a/doc/2-interface/controls-compact.png b/doc/2-interface/controls-compact.png new file mode 100644 index 0000000000000000000000000000000000000000..b0d1b0d40ba37563920bb345a0f47580fa1376d7 GIT binary patch literal 16054 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 literal 0 HcmV?d00001 diff --git a/doc/2-interface/controls-split.png b/doc/2-interface/controls-split.png new file mode 100644 index 0000000000000000000000000000000000000000..235ea2ce381e9d1f25a9332c04eda7a9b68f9559 GIT binary patch literal 20672 zcmb5Vb8ux(7%h5|iEY~xO_IsPwkEc1+qP}nw#|uc+jjEK@7{OcANRg`RqxcPQ(e8k zvwQFE-MiPf*6J`>X;F9>92fuq052{kBo6?9(gOe>98jRZn(h$kQ~*FWL|lkp(N+6m zkr4y{fCDAE)NyBYh9Vc!0pV+h0-%HVh67}g0j2*Hd+QuVh{}TWutszSWBrK+s>p0Z zO`*reK%vRPgL5}<9H}zbNLsDok*QExQ$l0?7ctNml-UZU;nn{Hwl*iG8#@w4YqsXj z)=8CRZ#%}`?_!R>-g^tEmyKArNH!oNBWGo1iosWP?tXk|riX-vnp#>)oz03(0FN*V z3keOP{eKq}goK0=r;kfYN{aXu6ckiLzey=`a+74R@bILH6%-Ahm9UU8MYJO!& zZ*OcQP8hugS1MaDV4(>!<7jDWDt4RmVj$=7oyh*7YgL{nF042@N-lw7VPV0*z_?jS z5;?GEr$uvugBTbZ3R(z2(9+Q{Kzx6H&reND!zLsIo^s*TaZWuI;WqBy)ReT8)XAf} zT&`f9s-=|`yidI+4J0I_bBC9a5%_$|2aZrB@|-_To_I(|$n})qB69g>5BjcLM|U?p ze1E)r8QIy!_BLnMlo*-+`N+sfFE1x2Cz}s00RckDfOn4$T1!g{ut$?5gw>}G?X9e= zCMPG+VS+9%t!kDt#K@R&;N_#{tNi>xUU9g&xm^o7Iy&Ow;*PR$M)q%|C`fw{kuA_7 zhLAE`rVq>8%zeU%&|tE%vck+;u_bZJ@s0WZVYGU?y56{Q`JhQkN>U`4xw&COidwhe zHiS0DOd1P%XlrXD5#Yh;tE2|rkT|=#xvg$)ZmzDrI?b!v*!(gciH=4vBOuum$B!?# z)2#mdSuV|f;`f$OB+h`Y_w_Qdh224bCT!<9VhJER zfIeL=g5x*Qw1>kH;t$1pf|wEZZ%5e5IK)|c5D*reD1w*}sts$_yQGq-T46T4C-<&5 zx=r|;Y0QYB6@lX-S%>Jhaxo`TOH7!cjZIfAHf#(L5@i0>YgAGZsiNp?4*Zb%RazLr z<$v56lcn)p`C&NY*WC5Hwx#K4nvQ(1=I+bqNj(0qo>S=Y(A3&!VhvXaKa(WPvzc-o z2uLJYrAyR^kW_1y7tNJ=I@GIJGNKxYu%kpeHtBh6&t19t#}Dq^K-wHR=+>2}(QWlP zvgUm1RB5Mf_DDFi4w>oa>V4Cw{J7d6eAnn zXMAKWWvWex459KB2Z4M00|oCK&Y1qt8JCPas?2Vt`EF+Qkx2-n>&p}BQr>yJqdA#EjHgW>lFGFNd}6ZzpT0o(f)OT9=VKoBI=_w zp>3PiVPRjqb1N%%-POzIDs6UjW{VszVRm@n3azvg{#-fJ%#mdO2s_r}7;dZ2IW~po znU~B{&G?JN)PaKeAedP;%!Etp$hy+sXhACXO>p ztM%e!lmtBo{hdEP;gnGuB1rZ4!F|+SY5TXWuPh&3W#=48?So>fa*T6g+R3?0s(Ejk z%bc7p|4_t{gg{zJ6-ajle*Cr*{5uK8$lyTwgA^SZE)t#jM&Bo_EN+av_!$xo4!lpt zmJnl^cK%;v`w6{Z58v9BQ#)2(g!{Ep5k{blR@l?q8xH43csXO8aEywuu<#HZRQp`d zjh8jJ68Mj;9GBMj)J|whjMj;0X?fzj9wb@zoYxA^KOqnCuk=nR$in@j7u#oN)ZG=6 zO$W?h|~QdhiOTu^grMIN=YF^>-q2f0WgJXgUfbmI)AqMGPY3{1PL z5a{iA&24QzfzzxQ8f_c65^-6Hm6n!{c3%ctGcT8gR{B>_KU=*~{e}Oxe*N8>VRopd zqeJP+#mig5Q<@+86#iS~x5z}6w!{O^UG-r(ZCd1rflfgaQO#QC5v?c2VCn*7dbsdv zB`Ni)zL62RlR&$?+d<>AQDCc5aNZI!1Wp5I-R=CuBye@hL`b9yUmhM7!9k*?rk2=t zUW!sbTA0*b6NvF4Ug;e+PRE&5addKe41#7>#K1I#qls5Z?cO*dNP^i(XFGrCqo=3e zcn7W;QXqE<=8)H!-+x~UJCJL@2bAkQZ)aWddEqz-p#x$9B7ZhnTrxexTuZCBx3z_R z-!aI_$;nMA)P}M<>E&mfk8Q-n#6;>PaC$eT8AAr*-fsJU{)1Bng$4=MphXXj4_fyy zGc)t>&{(H!BE%kDT*?YJ6h)D=^@0bkd$&Y0PgMq`r8xPFDWQHE=8+2CfPN$fo$da9 z0c;T{?=Iuf_jixZleIM*88BQ{|EUg%AAG=Zf9DD+jq)$cI9EjbgG526V7~FMA*)dQ zMGmr70a85m@0e9er8O?d?_arJ7Z1%+b_HW39|nyTiaR?yfD|kdlcg}-UYr(|S>ZsQ zzr3K3W8^P<$x$L$7@K-j*sEuqT^EpTot=s67dJaadU^TzclMhI*KN|miQks;wKo>& zKVN2O(Ky=7^UhED%}5I#!X@Y=z#4JGv?t`wz!3YtzIa4eA0L~PS8cbAj;!3=-0BbP zEh>Z&5E1W}@d~Ax*h$`Gcq3X`SmDd{rr%pjq_LTpn|2`#g%Lz3#H%RI}GZ=1vI<(L+lTqo|Xw!ST5?K>6r?LGbCC+ z)g{K!c1d+}o{!33IHX5qo{V|AxJQCrlNa{=k8b{Kz$zZ$b-r3T;>QXFz0om&9t3M- zc66kTMN!s+WYGgrrF}({RNkYKp#6dvtKl0zY{jGh_>k^2RMq@#=e6UCA5gPSQ#o5= zc%OePMJTu-VtG!c3D)HhIW6_?Mo289_-EOGn;;?E={!tN%bL?B*YtaL2dm;R_j&u? z)Cn1QHUX`$jd*OE6|NHscOeF^+m?~q;Ox3XY#kT_pd|V`Sr7w@mnrF8Ssh z-x67~pB$2#r2*bZp#Z}=&fYGa1=fD+%x{*z4(#kOFyTpyZfk#*KhNA}_R+3(3x7`;3Je5`fZlf>iwZA)Z}Z0{4q7&;qEchZ zE<^>JEN5PQz8mrG?41`(+tsu&i=d{`mE((dC0De8^;h&KU`1rVP3I^@4Vj&A+(LFr z(z-?CN@%pp!Hx_N;geeY6O-}ZeWoO&J#+kg3LA3g?MGWiu~@7EA- zr_~-YU?UvB7Y>Le`R`i@_1`z9?f$x`ZREEzz&FRv?WXE~RQ?17`~(FC{%^7WtNMTb z|D(pVq}gg;YRSpzYk(IkSgz^;I&gb?$Y$qjc|@diwCLg#_>~98g~F zzIGsrhxii|D|pu_fzVD}OipY0@BHBR3b346Z%d3G&>MF9+V_ND; z+eOXfYV)`f)nV$Qqv!L|%l>#u>mMz%AtGXX89WjOwPLB~>)qjal7ov2>lhsn1xump z6|o<_jU{!Me&uU2XWD%&R;XY`;ruaYJN=?lZ!E3%(Ncy}c<6&9wA}9Yx>%_Tg2iNM z3qP37=`k3Io-dJ!2@fZcZCj@eK_uL&Gk|1u3-k}1-|BR8SS(j+P^{2sv9`3dG&DR| zs?u1l(g;zDU#geR=5o8<>WVX7ZLuLFyg!;e2Rk33>(L?a*?H>I*jhzlC8K}aG_4L0 zL=fplWpCP4B*@c^*KqCmI4OuxuGSng$ArP>aNHqfbtC)!dc42S;BY!irqe(m;KIVk zpPVY{gGggGlYf7@yg!^Uv1eqIiVF#Wv|0KAiEv=yeckn(`N?@?gUz4|7)BS$H<`}n z;o)(+JCO5?o-2}cND!sUrO1$!Yqne+iojH<`3lXmE&HY`Pwm#l%LxKHsO) zgCGdo#z@g*1=X_CS}a$Yn3)sl%DQ1a4oY&o-}WMYF-Z20Is8e^?aQrDso9F*{bF;u z(!3!|_zjij_;1Otm(4G^rspgDYEF&RZ^4Wtn3;CdR(V7%XyY=veKsf>zO%^HhBTB4 zsl3Tz8Pt~B{b5u+B!f%9&$-9@^MqvJ-}!RQbS2Z}_VIL14yOyk325d{$94CBzrJi+ zn%VaqWd~D1ACbfTrSsVWbf>hp$PXx{o4NrxdXBj+sWc{b)Uw}$C9%{05PoxSaN%k4 zTEEtcz>)m0$*}SO&PQ-qjFlxJkUySK*jQU&fyPsr;xp=UqoboMZ4Q*PxxfziK}N6B zoylroiOxXE3jL6;J1(bxfIc>Ds(T%U{kz!pb}tQ0wGu5%TvAfuH`M0st=R!{;S>_p z8mUGk?UCAr5=0(nUV&z@=9_ik(PYNFYLB;9@&p0+g6T|9^qaH?FvChN>|R({@JT-e z&Vr@4jZ8K>l3Yy*iEwcj-~^(N49@ZSYLiT%uYc_yY+P%yFsb4T#E8ZlWvfwPdo&mL z8->eS99Iy_A$aDoY@?B9)f|}4_bT-UU86uEygL|%YW^T+pz~j2{^7~*&$s&{Lbdw( z`j#QMeP*d`5p73g(4iF~w>ZH+$NiDOV}&zi2}FKG#CKAumM_7Cg@x^H&lZRx;<8+P zyxyz1tH2%dt<3YC*-H7b!lF^_sJh6=$f#bl_kO-GUN3pDYS^n_8_IU^Q+x)Nh7FBR ztIH~IoVEIZmzIKsV9;xdedS!zd%ZpM(abHU1?(ZZ&Am~-tE-89M;r1anu)~ww!ST0 zSfr9Dfrz+a!C&N4{7qk3()0ShoA^aWY3*n+rGR4CD2h1!t{iu^E6S)(A; z{LtMxnRIVUu936_i#n7(JFY0*=Q{Bw`-j5 zz;q6;PDUE_hC{t4G57Ce}N zZ%=Lr=-j&^9RlmDkAg}o*&HlCvDrG{PGO4DZ485CLzv*Klycb`y%Vkq5Aq% z8p~G;3l%i6lR7STGMiL@{K%m!PL~B2e(Whw{Kr2z;Xm7*FV&N^e3`w!KV1?A@7O-= z22VqK*Zh>imCaP@@KZI4Q}w}XV8X`2O3R@6?0>>wPycQeDjbimPRov~&QKwvEXWg$ zAy6l677k}yiUWt27ftLR_MKBFWkNMdg=V8fr$z_2*oOKZrsoXA<8TaJ$xaUKIrveY z5{^z&UNQ~M?siu=Cl?z1p2Bw3U@|RLO;U$xV5UedFJ6be7@?AL#7bwbtLUoh`Y$&% zXcNP^Y;LGofv0-0^#yT1sp9+mGYIq#5AF4LjZmmwczi-a&*zgy`LQ_{NOH}9SDtl? z`Qo@c-|Ke;_@WZTu4GB(zlloZ1)prO?i5idq3?{8!tn04EwD;dBl~K4v;} z4_Z88X*UioL2y^G7}dPoFMA58JfjP>olANm#R)65Zt^J97U|rbv}FhB680jP#Qw~e zn#UY3-KUTYUc3$mS{CCu+yrxsn%8=}T5c*G4_kQumpa^$ug?1t*-#EbC(>7lsLSK# zU!3(9y9pueYiJ;aNR31i%vNj6b%l*76DhyeC7Xzj#IXv12 zZmUPA>(Y^~oc-J_@2edqq|(BJYW{A>=$}np!R>oO7r;n9e zzh$bq1oC`6KBmu22Y@nsxLa2Ds8YZk#o2HxEoMA>#T9>k3cD>FbLNoN^kSPbC|5& zE?e}3mo$@#=sNzCk;idor9D=Xrec0F(=695OK6d%AR|A}RB-%!bBcz@qbGJN(BC|# zdcN8WkS++;6jBTjvc_pHXl=3Hj9)?!x1d0V@!!RT#t6{rr|utw(UkTOcTap6ndGg% z{JfYd)A-3zNr>l_Yb;^yu$S58bt)h#{kF@%jsNwX5KoId1O%71N0syVZ2tQw?qrIZ zFWmbE!;n>oIH=r5#&>`7WGjT|a8j;x>Yws*xP6W^7M7o0!VpmrKb(BaI|9=KK|?t7 zyPC7t!wnKq|02IY%NeE0W!}+<8)+8v@14QY<)@^iga++F?84bm#f|433?8C!k~RF% z&wdecUL}^$JKID2d9kwSQ<-c)H4^5K-_BAp!O7R22a9jU03~SFs=J1`Gxrmzah zsIhR@))1LiVuN(OOU(4gvG38%s!0yC1zAI8r4hTXAqOgWoYE3qhYk*Nwa+ z7R75AHgS4_H5ruwJA4<8G?^LT0MKBTW-PWu2|`tCQ7D$OV?*?dcBCu`$?N{OlMG&T z{g$7Zv;VF)>G}Thh~_FGNo?X_bsKc6@&6jPtP{bbvW{2HwQl&QORpOvZM7_y0kw@ZNs#7#LIe|YYuCR+WmTjOMWh^&hBsocj#H_e6~Wbk1CzT zk;D(DNsn8FY_cEUOioO1LT76j@C+ey&43>qPkK(dzuM?Ek0rEk7GMN@v>rz)y0Njr z7W=Lw!s-4%mF8^dRd-|jFI8_&mMtR*9x6ER#jqD^u-f0>U-f$=8lQB?zZPln*nGKq zv)#!e+Zf0TeZ(IWpL}%P8{&X3LW7Q@qNAoKSwZOS%Dok&MwHKi8PE^HSup>r$tOgv zhZV=n4LgQX0Auw-^&}-F%{pupGDoOO{CO~t-B>|tBwwcoWY&Ut6T6n6r15P!fZ1Vf zbqUcAdXui{UH&L@_VF#4@4=|!p-ED%+|NvO5>tZ)!{yq3%vn|E>#a$%s!DA8DO%Xh zx3rJ7v1~8F(E}FO?mVuR_U-9jF9{VJ5Zdvw2rOfR2~jiQ7=Di zyxO1_*qtyq$$W*P&qtI(5(>JPKi{8E>y0OI1`x~=z=QB{csyHs4+~L{;%^jo2MU4} z>{hGq{y7rc$fd9=yrF)_Z|mc(q-d4m3*LeU1qPnf`;d~5=wj88UzS@(T2Pr{;z;s8 z`wQa29|j`#@ti0W$kd#5S)S;4**Y06jN%`=$QDB42IG@_{C0ajIJut{*nNzzK`iC; z-H$d+ajSy6bl-ZcRNEwS!fMvSbZ)a}S8Rn&G%Aqo31lRGG!wzA5bj4+T7w!`n<2{p zQQrjO^1J}t){XB)9KR=9sS+qzhjJ*^qAc5w#TVZjUt;*+hqBcXmX0u&IrtLRKmvI5 zJOeGT^8WuaBqnfEPF>Adfv`$Ld7=ux9}Iu*po1BRhCQCy;YE2#E+Uk?HWkCI=)FQlQm{Y_b8i$GlFQChZKPv$YYR17Gn z&!tBVTT_riHr-DtJV@(1DDZ`cdO_-GSjZiHx7tx1S#Tv~kVU}q5E_4Mfl&72m(d+q z?ds0Bcr4HU4QM_v&D*nf`TFbX9^YM@ibaiSmY@b zW8}v}BsnS>={~-__;R~1SGU4=0v&8(9Z4y9>2T6GlLQ55@gSP$s-1q;JtF96Z{JQw z&PSH({ykA4Z{h(zr@Pw`LWeyP$~HJG20p_zy9CjAnQYa^o`3!}gNzvZ-&;%(rCV7A zV4^TwNG4h4PCevb!72yo$LQqgLkIc(yKUweC8<^JDG!-yU)%SLU_{aSiJDVvuRo2k zdA+%k(zv*|0`d2vQM(*L3u$;|*_B0_u+UX0ktkS?2kbcrE#}xbrjZ2U>?mr}spDeL zN3qajvu;8%CE@aJSnP^_s36Es=#ABL({dLcRMS;yuj8#~U*bvh(4zhceip zuIsqrU}4{VbTmPxQf^yZGh-}<5rs@(0Yt(8F=T+$ykapF06+b>O$Hgju}}gmySsEm zvYxkJz4LTK0`S$7580w!X+V2<(17F_zPUV@FG3QKD+?J%1IZOM3J1*OF$n`sWYb6h zj-^un>l|kaz_7>v!ruN1^ZGB0=fAL=|HA&CrvCqgXM6W5EJJLS?k@i`GOP==R`tcR$Y0J!g%N@ijQ7zIrNDykxdy+`qGr z7b8)mJv=%FagdQ`@9yl;a++;DJ!$I%g@g?2RKJp@bAc}j{r_wQ1);!?9MC`zNdo+c z{AV->2i9QyXFH(!ziVc9clG}MjS_+;r%JqL9A`zt#H^{QnNVqJZr0Y+6w6GLDxRB@ zx4L|O_u|RX)@8}Kg?WB{2F_U<8=H%`i16_7@$tsT#l^+d)ig4AlC$&kjP&&SRZAe< ze#Rb%f=0mwhpNq;J|KW%Mo6%`dPXm+m~laqf93=CG-)YR12 zIXG~zu&93hN>LrCURzr;x3H*Ewoq11R=xp>TlQ_mB_*>qV=?v3&Cd4r{@eZK<<#QJ zLQ(}|W8#I#OK}cRF=p(EFC8R}q)koxx1&Qr?1V< z0#Qz0kOJ4i@v)7ywSJDO*u{=XklGCx3VC~ff5elpRRNPOX#n13 zd%OC=POacPlF}N`(LfwHnw32A9RedcFoe5cU3 zk=8=Hy140L7yV$xaI|=eS&6E=6baB^zz18(5^3KO_|af*S>L)=S}tX zLHi1T4SS;HQTF}`*s}s7NKSp5tj6x3h6NKT^sp_Y8Q z>6U%WHf7y%{``&@7-)?qJiChu^v-y2V8OL6Fh`}rL}CS7FyqKjTCrp#Nd$FODpy>~ z#Ek3%tqK{KXOC}=9GlnAihtjdO%QIGI9hDi-_mM&zP%;CGMxR^kMY?q5$G$F~PN zB6;`h+mQB0NR3|}-Mu(qE#p>ia<1H&(n`zIi_56;Ub4dTfliJ=I*Kz#4p~s|5L%Ee z0h4|&(SNT~)5TvqgE9N5Z-UECU{$pD-aP{~P{LrED7DgJ;1p9mU<5c(B5XYIWV_Kf zO#|He*cZ$iA}#B5pHSlQmg^MoWAG;U4YlB6Ys{1OZJO>bXjHO}XKeC;JY?$5Jz1@*)iu6Bk`7cmBjXo+b?WW}7{N z8^*e2x1D$=M+&uJ{ELd7n%82&a4U(e7bN5#|3Y3QMds0R%}Ms85T4}_YX~JEPl4SO z+nIurvQvy*@0LBpsY{m%qOUKs-W+B~tnII2R@GpyrDBN3PCpTkg=2tDM(ORfZF0x} zr^)C`IKr4ETj|B918p@?ve8?}t@JL~+LX0_R_k6)KXiYfJ?JL1IZ{ z-l}U@8`kX~4AJcyOc4xGtpDKi0iLpDeVVnIMqt=ViM4imdX-MlI#SM2;vPGXLI3L) z@1JMGiNbufGej`(9`BLWRkf<1{nb+peHya^_*@ZIRU8%ajEsy1WN1;&8AP4Lnws9E ziqJr;azip_u_EW*&rfcqQMW?-%mNAu3eV?---#G<2vwp@6hDpi#3JpnhzC%E_p~Qg zhet+8ojiPers>*&l)1S3#sLuB`%l3-{)i(xK(8fK)$sX3Ksj3jKt<&&FROglmcT%q zvpcbbe)*USC*Ik1%M(;-RrMXA+pz>;)x}W?QPrQK%P|&a^J_*Ao9l%(V?#^|2hMKo zCIpsUoQ{qC$)c1Rr=+Chmo5kgHl&#{k<`8b0#V7bBu#k%)Q76OYM!lC&Tt1eF1J+Q z=+CCf(OM5$kdB&spjJn>XiRHkRm1D`mT`3Md9SAm;-go3^zT`E?Q3ycrpol{d&PjgZC zYnIm`RMw_QBAO4|{jF_njMM(f*T99+-aej6j%R!yHRr=5 zR@#xVDbCw@ePw6zt?tSxrVc!MpNEx0wY0Grsh&BV%~&jVlj_^TUK^FuJBTQTiKOk9 zzja#6qjPj|`A?m$uNP<@=`vga6ZXJiM?*tnKK{v!zNM2MP69dp$cU>2+xq>({S;B92}_;4CY9W)EX`33L7HuQ1tinz!XidkOt6HWCC3 zQATXzsiCUX=Y95ZgIw98^v)Y$s)4unmb1ruAd$v=D9Oe_Uim!gp=v>e8HrvVhw7uj z9x_Yz(ZF9HMB0<;V5x#<%DCKEbu5n9c8h)@CQJ6@Pg4T}!9VroWuyf}ajfPnQOqzf z5?a+>7z4{iB)LV(_jQ%)VxRme!dnAJW*Y1#qCc3mlrXq>c)ngUJb|~$F1Et;^%jSuPkt9Uu@JB0CLV6_@?7lTFcctUdL_uuR|K&iIth2Jj>u$-t*@R| zB(86l4UNPC%JtgS&w}ng-EuD?w9RE<-+}jyL3xP2!ay@6Wo-g~uy!uJZp`4)QnD*u zFU(J7W;85DS7rbwjkmY=@bGZEB4~4SGXViX?-`83kHsvI1@H^y;NW0TL5M;ZS3fxh z7?HjnpYQsHhB3|vzl(VcBtY*Legy@FkRG6$cBN33M7zV3cqLRfg}IFuie+u>4Mn*b zL)mY^4gAs*B!Y{dNSEBS#0}cT`O+EF5x*hg;yG#m9}(3D-q99&oDwo%P*GSX-U8A$ z!Uw>I20{cB+W(JVqVBvG>z4PNU*C{Bzj0!V&2ZF!cHssRfDkeuFxX_#{15ZIo2Tc6 za|dvppHZTW47|ND8&sQR1R$+(FPk^%Xl$cz)QVJwN0R_D5kD0Xv$S!8n(E}he2)(ijs=gQ zEd~&dMUeq$FhMW)QxPNpBw;`(31Ia2{+2V7P#Az@Eg!nQ3sV3E_zVwqvt)cVtXQOe z%`$FC`{2y$TSWJ)pm}LC_<~+W{7d5d{$3-F_kY~=)z#J8r+1OO_!k90NUlvYTZ#e* z?=vQiIkJG(JRsf&j<0!Bx+e?pD18dBzF@X=*0i&`J9=b)NMEjIIb>khlc%SxP5f&R zXrguHy#>C67R|{^0KHbQK&Y>l9Q1MG$gzI;#Gp!xA?UVX-2#O35CMJOJ-nY&`L!&6 z=VNWhm; z<8d(1%wg36`}KP@7;ya;l?{Vq1826T-!8kHJ8Y)yvX8mBOOD-YwwoK?+ors#-`l6# zs<(m3%NLM{$#g#}!_@J-9gf1C=6>D?6tuitYbp?p(FV$`-&W1$ket_@4JHGYKq111 zZ}hS`fNzEh-=m7hNwdH4`|I7KB~VX2z6jB}VUlI&$*N_uS$%uWW!oz&86@{K8NP(u z{$O;;DV9znIDbEeck3J|vTb?4U(RXJs8nY%o5cqh#BevSc#n~%bUw^UZhPKOdR%uw zR(RgD?ng(oD5mbL5_nwfzBDas2iX3nLU%3}Nv2Hv>d~Y-Tes=)I?i$dz7qEdy9gXJs{hLPoS^E|CtIA2ihZ?oRa7eAK$*-uWfohomIvFGeQ$=fx5P} zNx8_``!?ToSuJ?wHVJh&hVc8mribToL4nZo#_ycK;U8m)w)-itOI+r}DL;W%Xt7k;_Ii}jMD(2P zan&|L;6ba(U(2f39~eR3F2(^gEYLRU70R-1x$d{x?R(k+1;*snSy4i-O{e9@liB@5 z1?g1*fUfSXQFB(g?z!55kvE zgYbS3rnW24k>N~1wP)fhi{~J;AqI)%7)%l-4D=u1djjcb?TV){${g3r)dosqxNtaZ zCNS#WyxoD2AHHmmuBo>_Am9MPtecJrw4xZ`;bM2m^jhidFrb&=G}W!|R~-e1p`_8d z@v+I!hT2|_N{KsFEn8>2e?aJds;Cn?deL77)SlH(&;ui{nX`etReeZWu6Eu1Zmmn* zG&GYZDC{4BL1#`2;A;gwkn{VQq;dw<@V3Lx3XPW7A#0H$`dRJ-;@uPU9Xo)tQ`?)J zJ`tm#%h~~iGC8;r{t6J6?nElF#P{nS!fpuGc}7}PM?-stAB|)w>W3$V_^Q85%&{2t z!5)~xj95-HfmRJMoiF)9fL8J^o#=E)x_Ih9f>0PXxC|hVEW`WhxEDqVq*A&6WP87z z4&S`&$3PM6GbUd#ovp>{$1z}RJ$1$ z&V1GsM#mciGNLnm7;?@3<1nVl}5`{ z(X2(0L!s&OhC|Gc@b1w@1oi_YIO2soS+Rn>3q#?%cpvnOvx=H0YoLOJ^G@h?8-kG7 z7aoA70^yWaS28ZKX+J?1Lg?)}oENM!#l!2j1DxKEBZXWAt)%Y?d?wLO%4B7}+-SP+E7HEf!GEi_rEOi?;2jdfaNYO0Z< z%VUR=vHH=_p-Yku@D=cfCPJstTfrvBnVl6Gvv)^)_>K9MoA7*p+$%Fx4eEv9V;B)P z902`Ui4aLdvA%nR=mE}*3nE^^2k1hgzj{9G>#|5lm5qn9pXwq7FM@HcWxtSdD6ZWO zlT^wZ*5t18NhA;4UQU|TO683{W{JJ0&FA#W<;-N%m6@B8nHlJ0xNe063f%6G%*2&R zO>x<5iRj|Nho*C>;=UcFPB-NWM2KzjQ7>RY9UjjX?X7=EQkinX6jiSSFK`-s5>0BY z0zw^3T!_#*DiKq744jY{^==#b7ux4E1Tt<2v^%5>@3|#r1XwQ)>di zga$;@t^@1CnCe4_HQp~PW(Ot+RxY>%C%Lhu^x%Qc+g{K2Q=Et+!biFSiZT>PqABmA z?8lh~)fp)irE1!Sy-mJ;z&DGY=8%3g%Zw~}C72E4rcwzLz>%lGld&T`NbvFaoUCAJ zHZO!=TzC&<@D$BXhUc{q#dw`V2zsrD_(nkVJoXxc6ErniSFquyX ztv#S1n!~(h(>Y>`z;y@2ut4btfhYA7t#Xx0jdtyb!>=qBw>v{N5Q{#nZZf4r;mp#+ zQtWNu>ZGqmwEK@N0cck%_os(Pqx>P}c(PoUU-|V4xk_*_U*-Mq4;DPxRFQw@F!Pjo z$NVOjvEK*at-3p)DD?ln>Kkc=&kPe}-aC6;c}5xS#n z$25lZ5!-a{)eniuG}~32N}pd|KAFvpON2~RiUmFXk4~xRp&A^?JtQ=HZ4xRIDFEov zvd5(IXm~v>$(aJoyh4fK)$G9GST(T?ma1|WNLp5FmIs6Vj+C$sRzMkRB33N%JMtIS zT7jaG47mf~zbT|ChcH1T$;rzDe!$J#$q6X83RkEu6ERpZ28Pc4$pIB(g6n|(R)h&% zc|H@dwT{pB{(K8lxTT+=qDp(-_W4e?sP=1;s|ZG#nL;dNY;O7SV?S;iARtyr*!7b^ zMR@D!;s*+iin;Ku1t!dfecMW&pFQPFlzW3laHs@IwGkPJtG;PxG$R$5rpILy0#QEf zA7clvV3NGRm2p2?L3J1c5+0^+Mg2kwN6r(oC}cL>F+*YR3CANKXrOSDQ1}aKF>O{B zy{BatW&1-iBi;zx4rp}Lpwc<2py>ynJtha8sZjQ4x?_0nhkSoT>E-i?~GP5ujC@|iQ%{4hBwA$?tD#}xr#SokG zS-%JHwyL+8-H7Z&@~cXKGh z$0?o)WDu(t!7|1ERKr3k zhhRHJuYrWIDEy>0Aii^{T{MQju@v}AN%G@d9FUCR{#m?q=O^-~&udmO23*3@xArom zGa>hJl_*%&rDl*%KSEJ~n3c^=_wA|Ptkl_-hKihlXv!(J|8L5GDO zSE@ISi-CO=n_ke@%m)VZ=-(!D2<3@}{9{_q*C1H^g%I^(VM=H4@_L-(2H?T>j)1yV zuMON9>6e+-C`%s%<+A1EYg|Df^CjGywG$7%N|uD>M|9r9)cb+++JHv)x~p4`BO~Wv z!CTZ(3d3Cf8zXouEw@Sfg19dfD-4T9G#|b?`Mx}sFPM0|cO4YkpYdMG9)?At)hH;_ zZWvqMYyl4k9?mvGn32`Hm!pnQx!>@!mqoTPaKHpQxvtk8N#iw96vU6jEV=F6erRE& z#24w-f6GxGeXUPme?^Ft$EU) z#K>@0*qpB+*TOQNnK2$QO2^rP1p)1rMx@$LME1LRhGChv2 zL5E%$z5tl)B5Hy#ciEE}vrVRqP(o45*}}%14UC!oc8?9q`sHS2)3m4&m3g6`yBh4i z2D;PB1DQ%idShZAx=>ZR_M=o@8fZan=*UCHDHoZ^iI(x6K=Ek3py={k?2cL#<2?c@ z3vTe57;S2+EN5y)JkcE*hM__(6s%4-_J9h@&`07>Bf8Bgx;SnW{?I~2km!b~q$ydw zwYyg-HFGRMlKKIw)fG~kmQ08$jyF7&6?VL;YIc!|A+a8^!+B*(;m1ELT(}q;Rb2OVLyziErJs?1F0mjX*qylabgK z4keN8s!tnu*A#a1A&ZJeNM`rp;3gVo66Ci~qY-?a7a=WH=zlVw&$)QOfXegeg7Mgq5Zu>r+H zivKPFUmSU(r4O<=4FnP%gi39Rt#GRF`Bngcz3t9$8qrLHilKouuTX%-5)+ycDx@%% zm|wzCixmcCj%R(qjZoGW6V4y}kn&ZUXE7h`s)8d8HS_OOw zP4lQ@0t;q_l7+s0@QM6Sy%5`_8z3FIhQ)ga;+JxxI7OcbPK-#HTXm-IMuQq2i z3cfaqQS9xox&vo&6>PCs^7}DOE%S13y@rp3r}c<^<)K*B--#Zf&oV2QDh6hPX#52E zN9s9rz<-J|B0Dx1;*{$%k@<7{#(Tl0i=C2PAV{8kvWK1XXWbuzJ>1t!`f2K=%z9;1 zpj^JG5OppZ=TV?L<17U9va(?+AOFyyP0Z|XdNKj5&_#G^ zxaE)0Aztfd4KX1?>Oav+W0V|Vl8$){d?7qE?~oxps&sGtW^F2#M&t!i!-KOy^wrtr z4B$A-@%6HMW!-~5u65&`6cig(L0dSuM=Lzx`iRcRe(;gl`bZERYOUymF2x-IOL17p zI4k#n^2KCN8x(v}M>@K?utn(GCf)rawcDmZKMo=?)H^Rwn|K2z_0eN~6xp-wiw(wb z96c2xg(qJtOi=iGgOU^ zY;UEQt}ZV(pKtZuOM09w;fTC`ymGD*(m?$(MvMjTzL>=4d|0GyscwgYNU>>e9Jm(R}gJIRdk0`P{jK8xP_6o%_`Xz<}PQUNwK( zs9ghOn-1;6_|`00M)vErudkoqUy59h?b|vxt#6M+3gQ&O0SdE*Oli}{jBk-6``az8 z)5Z<=uUuGg;VK+i(&@!r+P}(>0LA$^7n>lFS_%XZHmlKer*jk6dtp%DK5;nbU;%~V3*5{+1}Fv{Pj%FbmKIrg@k0 z@5?$qUi(q3-j9na%`u!;tw17xcMTE_tJeNd0yn#*ct zct3BtOmm!99_RRMX?P+5(*~Cd7W0Kt9iLC9jaIuA;O2`8ZO_{w4InS&2T3prm@y#P zp8=MzWJZ9Qe*1?f(0bjt<$gZ&lmJX=fb`ee$uf|HTerrIs_A-K+Z&4b4RG`_r1FO} z0b-Y4zaKH|M_5j|dm6wNz16#JqdBj7U7vt77m$zOdRj5S;eFW&aCMpg%jyN>8GyTL zD&h+}nO$#ofb5mo)5WS|4`sINUXqG7(9QA@ODyQTVK*X4-!?b}%s%gcX>u!&uOoDv z;eWqqm^R7rO2$&XhdlTK`V4!mBsmX8*~r#{MPOP8652bufH_?iGwLRgj^n(j6|`J; z{r-Hm`o98C4zTh3?T<(WV2^eNfUb`$(0N*(efC)!4?g&yS`pjA`s=S>+UM0*Uv8|D{q~3)^Yb=eDTGPJo1R0 zaVE@(S=?^B@4h>>~y?|6FV&U|>tlMTT-@C^EbP52Q1gf%h0jef;sqY}^}dv{7pg2S5G$ z_y6UWU#`9OT694R362Ha-4ATkgo-;OPtQH~Tx|dL+izE0bro#u)2Gj#d+yoHPy|Yy z>d>J>VN#U4>PC)?LjwD6s2{5M@ZrOQOCjI?ti*~HD;nzXqmMqKPk1DYjmwrTd+MpD zy!Jc*Ao!~(TzTb{woB-%ee&eVP3`{8Q(OUwi-g(8wzM)HIadxK5!TjSciq~@QrM^p zixw^7uj9ds;9U6f%P-j+N>EhNwLbmyQ?r*O_YftTHm<(C=v_9{mYNxgpfYvdYioASL6tHrRSV2G z)ZrgTS73H!`6;y<{B^`X1FYYnac45b+AbMoi<*IvsFoDbSle40xW4)3o7b3(Z@wcD zMaen%P1WU_wuOCS+iR)4)HBt6*Ijp|e75v`qi5j+s7KnPw^D~&Zn>qJoSn06p#$Re z*I!o(LXW0Sol0mPJ9aGFLfu0=B-M8QoZ1IsMP7UN?wvDuj*LTfH&pnsH zb??3R1`L!8avXJ?6{v0f!;k2YwxL5LOoxiT(ILHr;+JM?uqn|qzWeUGI5D3KDY>7* zQek%d(MKPhdN$l-thJHi@WT&hfJz#;&r%(LF%DOUrqfKDHqBcp#gpBrm-tPzeDcXB zW5$e;qvB8MrJ-~3TM2*-v^aimy6L89|LY%taUOQqVI<0E+-dgWULVSr!Kn*l4msqIL`{6D zQ%^mW8C|9G&=@@Lq3^=X@%R|Yd(()Jr~ypf#1B0)9g$5AzG*s$-v z|K9#;b&EZYdTlSU}msNkyX{?XGpx+lU*bNg_uU7zxVN8e^svMz-~o2GH_O-M~}`>BpSX8iymce1$3AOit@*>MHsC>9Yvtxo_u$R5$m?Q3W-kbE==`V zMafa-g8vR6RG&;mNl8r(_*CCpiN;pig;EE)eaDpf&C(xtpZ#$tN z9n5Q}aJA#U4OMp8Wfv#{Z%|SE zx40CLp^$L_CX1ef5T>hvEO|2O(uxA#u0$WJ!hPwv zbr@Y-br-l%X*&nIW69A+(C;dHE_GMRs7}TbvF*0$UN}@lBe$#E)Fph#vL+>eU9rrL zix4DYO_LOIr87YqJcA8AR-R3V{QmpzIxKOQ=un^qT6F922NKYTjt8x&b5c3BWUL9l zH{5VTB9&B5&H^5F2bxuHt^8n&i>bKJXU&=g8;WHo17By0W(8Xczg!zaj&eAv#EDQu zZr}uEUFhNi1*KGgT6v{jc;SVxeyD7PhS}W%F#q47h!}d$J@?==IKH}EuwBHK_Nw$( zw2c83h;_jQ7eJ8_BS!eSK~dcOj-@hNvf;!NPmHP^T@^n~IFva1+_`flO&1BlB03v` z;O~u|a}K3cS+X=P>nbQBotu}I8mJcJ8iI++aLS?0zdnToDr?9cHPEOtaR`3<8Ec5! zP6jyf5fs}IBPRn?sA^QHhDaDy1ESy`CHzxji4N&b(Wt%xijW>H^g!~y5iQX2)XNVP z5ku0L)Ioo%X7JV^lk@_LlDAe~f^+Aba}J-Ii_#&}`ygd#YjR!nU7ynaF$9*`GjOOg zpeV2;QVE)3B#Q;NG>nBk!ak%@{+ZNn35JtTJ{gR8OWu*2{xtP~pWFTj)Cx)k-Okar z<(f+Kf+JUUNrY~m8r%utfRD!R5?DUzNg8bljp#)U;ZWx|7to_p3&zl%%5iHtRE&{r zsVNAG@hc^twJc_XKno-Z6w&nb_UNOJwrvyw*c=ET;~cDY!XT%G#fh^HZE~3izwHMk znCIeF+oMLF0~Wxc&Ng#X+o)=FcWcSp4SsO>){yab&eY2R>5YFgBzGnnd2JpX!hj`f ztD5ql7NrX%cWz@Tn=IC7#vyNz-*E*!7t6+kxKf9TNs@pksch+!3Q*4iGvYr0B!`0P zlzLUQpMdA|gQNh=)pp&wh|ZH?uQ2JJWH$;GTC>6V zGNOTOO)hBZX3b$b$mN$`&Y#i)(5RDfNd7gm$5>7FM{dmbG|Qr;olykSA<#AX+PohV zKjl_+8tIr7FtCBm(i~Xb#(fJ{VgfJYbxb8RnZ|M|W>(ntY{G!eHrvd#)1d=(=mhxX z_`G#k%HyiV&eP*uG~LnhXm>Cpm{aS86V!_9!Mr8%pyx}Y@hekC^a*2-Eo)t}!`N1w zX5@o;d;u)DX{}4(r&diNWasnDhMQclR{%EVi%F9v>D-t&7N2GMHAmI2^*fkj@!)YKh6PltYIwlEivx)`qqr)Wvn)gKfWMyf? zXXmJ~gqPczMXyO-KYT^YTumz z2CC%4y;pmk9%vB@|48DoPEQ)`$=$`d=cp3nvUFU^j}BKji4O599V*YHm4skbbXW>- z1@p`sgBhjegN@thX%rF4UfrdPoUpISYX?o^e{WhMLFcBI7W4kuXP@nVUf@i!q(Q9$ zkDCMt#l$A~5hZ|_m*Q*c2NF0cpw3D94>+!SSa}^{hmdW62U-TygA+qg=MUlYCo7>bI^pV+>FhGgEfqvgg5(t3+ zKg!FSk{~lh?GCk5^iggEX6hOpO85QL+Xwz_hPyqiQ4gYRi|L7%qu9}y`ufYa;tsMj znN!FgTL4oGS9FMR9J`vW)uFCa1Zwo&4zaFl3_cvkQ70+l;aL5jP|tKI`2iiMQF$w3 zqT$!3)H*MhMTas|1dz45aNqaR6CKKVKnH*hWu^!K9m-4*06HW^09?LztiebZoieEyMIVgqeGY96#+o(gpFPAjSgL&A^?cfI^s;G z+M$FL0l+j(aq3cBB%}xcKA%cROc4M&l$jy`bSN`L0O-)IDFPmkuebo_d9EGGNf7`# zv@A;ri75g=hq_J?06KKj4!UjI%6|pGFbw53Kkd~q0RR9D0-)Ky^kR4jB}f$jTowVK lA^=bk04f3i6#<|k0C-EjZ!+<}0B!&P002ovPDHLkV1oSehEf0k literal 0 HcmV?d00001 diff --git a/doc/2-interface/controls-vertical.png b/doc/2-interface/controls-vertical.png new file mode 100644 index 0000000000000000000000000000000000000000..98a89cc4ec81a52003ac00f7cef4160030b1d817 GIT binary patch literal 10175 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 literal 0 HcmV?d00001 diff --git a/doc/2-interface/instruments-folder.png b/doc/2-interface/instruments-folder.png new file mode 100644 index 0000000000000000000000000000000000000000..18e4ae630f1f07fc5523f83c3493540d552363a3 GIT binary patch literal 50247 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 z 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≪HimYZKjU?r;tR4^r@fO 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 Appearance to one of these: +The layout can be changed in Settings > Appearance to one of these: ### classic -image +![classic play/edit controls](controls-classic.png) ### compact -image +![compact play/edit controls](controls-compact.png) ### compact (vertical) -image +![compact vertical play/edit controls](controls-vertical.png) + +### split + +![split play and edit controls](controls-split.png) diff --git a/doc/2-interface/samples.png b/doc/2-interface/samples.png new file mode 100644 index 0000000000000000000000000000000000000000..816512fbb1261d26a014d000adb5d0034249ce6f GIT binary patch literal 19676 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

4mcKoxy~ss!rlEz8;@Fm!62>4};rB_HC-Lx+F0 z`hmEs<*15{7HuVhxhhzejwTKn)s( zp!jp?(OWMt3kpFyv6so6@*&vAcN}fXUn;@V7QWt;(%%{G&9J0}pd4q{A}@7R+(J0C z8Ku>lIczlOvjWb33!kQ0dzCI>A|=bKOVb zgCRxb0NwfKw)mheR;QTR;F1y|DnK3{lXtxcTqEhYYWew{MWHeqoAq@49FVs{j*h8W zIz3AQ+{=tiX64f%| zd5>cUY48u%GCm4?HRb3Zs(PB(+!dWJZxC*E%3!A_g9OwTwdM}|xqz9D(M?fPJGhVb zdgIFACSP?h_T=i)4YR2rF^*Vr#bEX>8q+4MKG)4;2H>eeE+#?rH?_Nlg|lrV81-o6 z_?DOuTxJ~*Fi~|)s3JKA-#g^>UaYYZ|1P}1*=DAnJ z&MmYhT(-35<`$@|t{)z|l5uxYN+=D+>Dr3B0XA?lJI(t%&c)NUvB=0GqD4Fb>@eM@ zeOq(P$)*-t@yuXT?w#D~9FDiMkU`4~b)eo|VlC-$EI5y-AR#WkQxRiFq-PIkYCfWq z)c|I_@f1y)mhQ0&pu%uy47R?q;LMCI6vr24p0qSFL#y<;?%yH*$(IZ0(d&j(wiqV3 zqDo1uBM{;^fHmN_TaSps|Xw{5R(JS)sJGnnr8H zIP81`=-_E9_!Ajwxozfv%5abK&V_K#e7_8|57tiHuiKDlA~7*s1Zp>O#*pvdf%Ud6 ze~7lFLIs>bS!-?kkO*AWQLV|h7QT@ms&KY9&zQ5>n7Rszz{|gx=hOD(YJP#IZx%Fx z#{B$E8F8Vx5Y2Cw3_Y0a&E#Hx{b^b@2Q<5|a|x{wP6YP?;rI|{7?)HKdCJ(~;ks=g zTxjoHgb3d%ab>4F64el@{f6}R(A#i2*NR7{EjTYuP_zbfPa1;X1~gan4rp?X=bGn# z`9&B7*6wd!MkrxM_tY6Fp%q-0-2H3|29l_`UXp+M#V3zChY`I`*C)l5<~SAU_ymjb z?6X-I#?bq2f?G7RPaA(nSqg4UMhZ!zyNE-SRM;^{xha^>>XWqM^MGimZ0ab>3v7t>q9{8%)kul3pLglR~1uZreO>dT;oohLjmOD#&b#GLnN%A;6#MgiY zbn-;ZlN<3eXP?9r_jC;J J)N0vB{SR{RO|Sp} literal 0 HcmV?d00001 diff --git a/doc/2-interface/song-info.md b/doc/2-interface/song-info.md index e2aa806f..1a4cd03e 100644 --- a/doc/2-interface/song-info.md +++ b/doc/2-interface/song-info.md @@ -1,5 +1,49 @@ # song info -# sub-songs +- "Name" is for the track's title. +- "Author" is used to list the contributors to a song. If the song is a cover of someone else's track, it's customary to list their name first, followed by `[cv. YourName]`. +- "Album" can be used to store the associated album name, the name of the game the song is from, or whatever. +- "System" is for the game or computer the track is designed for. This is automatically set when creating a new tune, but it can be changed to anything one wants. The "Auto" button will provide a guess based on the chips in use. + +All of this metadata will be included in a VGM export. This isn't the case for a WAV export, however. + +"Tuning (A-4)" allows one to set tuning based on the note A-4, which should be 440 in most cases. Opening an Amiga MOD will set it to 436 for hardware compatibility. + +# subsongs + +This window allows one to create **subsongs** – multiple individual songs within a single file. Each song has its own order list and patterns, but all songs within a file share the same chips, samples, and so forth. + +- The drop-down box selects the current subsong. +- The `+` button adds a new subsong. +- The `−` button permanently deletes the current subsong (unless it's the only one). +- "Name" sets the title of the current subsong. +- The box at the bottom can store any arbitrary text, like a separate "Comments" box for the current subsong. # speed + +There are multiple ways to set the tempo of a song. + +**Tick Rate** sets the frequency of ticks per second, the rate at which notes and effects are processed. +- All values are allowed for all chips, but most chips have hardware limitations that mean they should stay at either 60 (approximately NTSC) or 50 (exactly PAL). +- Clicking the Tick Rate button switches to a more traditional **Base Tempo** BPM setting. + +**Speed** sets the number of ticks per row. +- Clicking the "Speed" button changes to more complex modes covered in the [grooves] page. + +**Virtual Tempo** simulates any arbitrary tempo without altering the tick rate. It does this by adding or skipping ticks to approximate the tempo. The two numbers represent a ratio applied to the actual tick rate. Example: +- Set tick rate to 150 BPM (60 Hz) and speed to 6. +- Set the first virtual tempo number (numerator) to 200. +- Set the second virtual tempo number (denominator) to 150. +- The track will play at 200 BPM. +- The ratio doesn't have to match BPM numbers. Set the numerator to 4 and the denominator to 5, and the virtual BPM becomes 150 × 4/5 = 120. + +**Divider** changes the effective tick rate. A tick rate of 60Hz and a divisor of 6 will result in ticks lasting a tenth of a second each! + +**Highlight** sets the pattern row highlights: +- The first value represents the number of rows per beat. +- The second value represents the number of rows per measure. +- These don't have to line up with the music's actual beats and measures. Set them as preferred for tracking. _Note:_ These values are used for the metronome and calculating BPM. + +**Pattern Length** is the length of each pattern in rows. This affects all patterns in the song, and every pattern must be the same length. (Individual patterns can be cut short by `0Bxx`, `0Dxx`, and `FFxx` commands.) + +**Song Length** shows how many orders are in the order list. Decreasing it will hide the orders at the bottom. Increasing it will restore those orders; increasing it further will add new orders of all `00` patterns. diff --git a/doc/2-interface/wavetables.png b/doc/2-interface/wavetables.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7ea88082aead6b6f72340d2944fb8131c9817f GIT binary patch literal 11257 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 ou*}%M Date: Mon, 12 Jun 2023 22:36:27 -0500 Subject: [PATCH 16/46] add demos/msx/OPLL_High_and_Rising.fur see ticket #1084 --- demos/msx/OPLL_High_and_Rising.fur | Bin 0 -> 6187 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/msx/OPLL_High_and_Rising.fur diff --git a/demos/msx/OPLL_High_and_Rising.fur b/demos/msx/OPLL_High_and_Rising.fur new file mode 100644 index 0000000000000000000000000000000000000000..2ecab85bb1de2b9cb2b1a402f457701802cf269b GIT binary patch literal 6187 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 literal 0 HcmV?d00001 From 852f61b7fcacc64516935ee7c34ae250803ba215 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 12 Jun 2023 23:43:15 -0500 Subject: [PATCH 17/46] GUI: DirectX 11 render backend, part 3 --- src/gui/render/renderDX11.cpp | 112 +++++++++++++++++++++++++++++++--- src/gui/render/renderDX11.h | 4 ++ 2 files changed, 107 insertions(+), 9 deletions(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 767878ce..1133f842 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -28,6 +28,19 @@ const D3D_FEATURE_LEVEL possibleFeatureLevels[2]={ D3D_FEATURE_LEVEL_10_0 }; +struct FurnaceDXTexture { + ID3D11Texture2D* tex; + ID3D11ShaderResourceView* view; + int width, height; + unsigned char* lockedData; + FurnaceDXTexture(): + tex(NULL), + view(NULL), + width(0), + height(0), + lockedData(NULL) {} +}; + bool FurnaceGUIRenderDX11::destroyRenderTarget() { if (renderTarget!=NULL) { renderTarget->Release(); @@ -83,27 +96,104 @@ bool FurnaceGUIRenderDX11::createRenderTarget() { } ImTextureID FurnaceGUIRenderDX11::getTextureID(void* which) { - return NULL; + FurnaceDXTexture* t=(FurnaceDXTexture*)which; + return (ImTextureID)t->view; } bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) { - return false; + FurnaceDXTexture* t=(FurnaceDXTexture*)which; + if (t->lockedData!=NULL) return false; + + D3D11_MAPPED_SUBRESOURCE mappedRes; + memset(&mappedRes,0,sizeof(mappedRes)); + + HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE,0,&mappedRes); + if (result!=S_OK) { + logW("could not map texture!"); + return false; + } + t->lockedData=(unsigned char*)mappedRes.pData; + *data=mappedRes.pData; + *pitch=mappedRes.RowPitch; + return true; } bool FurnaceGUIRenderDX11::unlockTexture(void* which) { - return false; + FurnaceDXTexture* t=(FurnaceDXTexture*)which; + if (t->lockedData==NULL) return false; + context->Unmap(t->tex,D3D11CalcSubresource(0,0,1)); + t->lockedData=NULL; + return true; } bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) { - return false; + FurnaceDXTexture* t=(FurnaceDXTexture*)which; + context->UpdateSubresource(t->tex,D3D11CalcSubresource(0,0,1),NULL,data,pitch,pitch*t->height); + return true; } void* FurnaceGUIRenderDX11::createTexture(bool dynamic, int width, int height) { - return NULL; + D3D11_TEXTURE2D_DESC texDesc; + D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc; + ID3D11Texture2D* tex=NULL; + ID3D11ShaderResourceView* view=NULL; + HRESULT result; + + memset(&texDesc,0,sizeof(texDesc)); + memset(&viewDesc,0,sizeof(viewDesc)); + + texDesc.Width=width; + texDesc.Height=height; + texDesc.MipLevels=1; + texDesc.ArraySize=1; + texDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; // ??? + texDesc.SampleDesc.Count=1; + texDesc.SampleDesc.Quality=0; + texDesc.Usage=dynamic?D3D11_USAGE_DYNAMIC:D3D11_USAGE_DEFAULT; + texDesc.BindFlags=D3D11_BIND_SHADER_RESOURCE; + texDesc.CPUAccessFlags=dynamic?D3D11_CPU_ACCESS_WRITE:0; + texDesc.MiscFlags=0; + + result=device->CreateTexture2D(&texDesc,NULL,&tex); + if (result!=S_OK) { + logW("could not create texture! %.8x",result); + return NULL; + } + + viewDesc.Format=texDesc.Format=texDesc.Format; + viewDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURE2D; + viewDesc.Texture2D.MostDetailedMip=0; + viewDesc.Texture2D.MipLevels=texDesc.MipLevels; + + result=device->CreateShaderResourceView(tex,&viewDesc,&view); + if (result!=S_OK) { + logW("could not create texture view! %.8x",result); + tex->Release(); + return NULL; + } + + FurnaceDXTexture* ret=new FurnaceDXTexture; + ret->width=width; + ret->height=height; + ret->tex=tex; + ret->view=view; + textures.push_back(ret); + return ret; } bool FurnaceGUIRenderDX11::destroyTexture(void* which) { - return false; + FurnaceDXTexture* t=(FurnaceDXTexture*)which; + t->view->Release(); + t->tex->Release(); + delete t; + + for (size_t i=0; iResizeBuffers(0,0,0,DXGI_FORMAT_UNKNOWN,0); - - - createRenderTarget(); } @@ -219,6 +306,13 @@ void FurnaceGUIRenderDX11::initGUI(SDL_Window* win) { bool FurnaceGUIRenderDX11::quit() { destroyRenderTarget(); + for (FurnaceDXTexture* i: textures) { + i->view->Release(); + i->tex->Release(); + delete i; + } + textures.clear(); + if (swapchain!=NULL) { swapchain->Release(); swapchain=NULL; diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h index 1a3d7dca..19cfb6f0 100644 --- a/src/gui/render/renderDX11.h +++ b/src/gui/render/renderDX11.h @@ -26,6 +26,8 @@ typedef void ID3D11RenderTargetView; typedef void IDXGISwapChain; #endif +struct FurnaceDXTexture; + class FurnaceGUIRenderDX11: public FurnaceGUIRender { ID3D11Device* device; ID3D11DeviceContext* context; @@ -37,6 +39,8 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { bool destroyRenderTarget(); bool createRenderTarget(); + std::vector textures; + public: ImTextureID getTextureID(void* which); bool lockTexture(void* which, void** data, int* pitch); From ad8082a9210c7f7d275dcf2468febe03ddaa605a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 03:12:12 -0500 Subject: [PATCH 18/46] GUI: DirectX 11 render backend, part 4 ready to merge? --- CMakeLists.txt | 14 +- .../backends/imgui_impl_dx11.cpp | 27 ++- src/gui/gui.cpp | 6 +- src/gui/render/renderDX11.cpp | 210 +++++++++++++++++- src/gui/render/renderDX11.h | 32 ++- 5 files changed, 269 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 30174880..a5e91fb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -739,11 +739,15 @@ endif() if (WITH_RENDER_DX11) if (WIN32) - list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp) - list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp) - list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11) - list(APPEND DEPENDENCIES_LIBRARIES d3d11 d3dcompiler) - message(STATUS "UI render backend: DirectX 11") + if (SUPPORT_XP) + message(FATAL_ERROR "SUPPORT_XP is on. cannot enable DirectX 11 backend.") + else() + list(APPEND GUI_SOURCES src/gui/render/renderDX11.cpp) + list(APPEND GUI_SOURCES extern/imgui_patched/backends/imgui_impl_dx11.cpp) + list(APPEND DEPENDENCIES_DEFINES HAVE_RENDER_DX11) + list(APPEND DEPENDENCIES_LIBRARIES d3d11) + message(STATUS "UI render backend: DirectX 11") + endif() else() message(FATAL_ERROR "DirectX 11 render backend only for Windows!") endif() diff --git a/extern/imgui_patched/backends/imgui_impl_dx11.cpp b/extern/imgui_patched/backends/imgui_impl_dx11.cpp index fa60d9eb..86276968 100644 --- a/extern/imgui_patched/backends/imgui_impl_dx11.cpp +++ b/extern/imgui_patched/backends/imgui_impl_dx11.cpp @@ -32,16 +32,16 @@ // 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. // 2016-05-07: DirectX11: Disabling depth-write. +// DISCLAIMER: modified with d3dcompiler patch (see https://github.com/ocornut/imgui/pull/638). + #include "imgui.h" #include "imgui_impl_dx11.h" // DirectX #include #include -#include -#ifdef _MSC_VER -#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. -#endif + +typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID, SIZE_T, LPCSTR, D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR, LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob*); // DirectX11 data struct ImGui_ImplDX11_Data @@ -380,11 +380,22 @@ bool ImGui_ImplDX11_CreateDeviceObjects() if (bd->pFontSampler) ImGui_ImplDX11_InvalidateDeviceObjects(); - // By using D3DCompile() from / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) - // If you would like to use this DX11 sample code but remove this dependency you can: - // 1) compile once, save the compiled shader blobs into a file or source code and pass them to CreateVertexShader()/CreatePixelShader() [preferred solution] - // 2) use code to detect any version of the DLL and grab a pointer to D3DCompile from the DLL. // See https://github.com/ocornut/imgui/pull/638 for sources and details. + // Detect which d3dcompiler_XX.dll is present in the system and grab a pointer to D3DCompile. + // Without this, you must link d3dcompiler.lib with the project. + D3DCompile_t D3DCompile = NULL; + { + char dllBuffer[20]; + for (int i = 47; i > 30 && !D3DCompile; i--) + { + sprintf(dllBuffer, "d3dcompiler_%d.dll", i); + HMODULE hDll = LoadLibraryA(dllBuffer); + if (hDll) + D3DCompile = (D3DCompile_t)GetProcAddress(hDll, "D3DCompile"); + } + if (!D3DCompile) + return false; + } // Create the vertex shader { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6ea8a941..620ad36a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6237,9 +6237,9 @@ bool FurnaceGUI::init() { if (!rend->init(sdlWin)) { if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) { settings.renderBackend=""; - e->setConf("renderBackend",""); - e->saveConf(); - lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); + //e->setConf("renderBackend",""); + //e->saveConf(); + //lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace."); } else { lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); if (!settings.renderDriver.empty()) { diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 1133f842..1ed6c9cc 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -23,6 +23,47 @@ #include "backends/imgui_impl_dx11.h" #include "../../ta-log.h" +typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID,SIZE_T,LPCSTR,D3D_SHADER_MACRO*,ID3DInclude*,LPCSTR,LPCSTR,UINT,UINT,ID3DBlob**,ID3DBlob*); + +const char* shD3D11_wipe_srcV= + "cbuffer WipeUniform: register(b0) {\n" + " float alpha;\n" + " float padding1;\n" + " float padding2;\n" + " float padding3;\n" + " float4 padding4;\n" + "};\n" + "\n" + "struct vsInput {\n" + " float4 pos: POSITION;\n" + "};\n" + "\n" + "struct fsInput {\n" + " float4 pos: SV_POSITION;\n" + " float4 color: COLOR0;\n" + "};\n" + "\n" + "fsInput main(vsInput input) {\n" + " fsInput output;\n" + " output.pos=input.pos;\n" + " output.color=float4(0.0f,0.0f,0.0f,alpha);\n" + " return output;\n" + "}"; + +const char* shD3D11_wipe_srcF= + "struct fsInput {\n" + " float4 pos: SV_POSITION;\n" + " float4 color: COLOR0;\n" + "};\n" + "\n" + "float4 main(fsInput input): SV_Target {\n" + " return input.color;\n" + "}"; + +const D3D11_INPUT_ELEMENT_DESC shD3D11_wipe_inputLayout={ + "POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 +}; + const D3D_FEATURE_LEVEL possibleFeatureLevels[2]={ D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_0 @@ -237,8 +278,43 @@ void FurnaceGUIRenderDX11::renderGUI() { ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); } +const float blendFactor[4]={ + 1.0f, 1.0f, 1.0f, 1.0f +}; + void FurnaceGUIRenderDX11::wipe(float alpha) { - // TODO + D3D11_VIEWPORT viewPort; + unsigned int strides=4*sizeof(float); + unsigned int offsets=0; + + memset(&viewPort,0,sizeof(viewPort)); + viewPort.TopLeftX=0.0f; + viewPort.TopLeftY=0.0f; + viewPort.Width=outW; + viewPort.Height=outH; + viewPort.MinDepth=0.0f; + viewPort.MaxDepth=1.0f; + + D3D11_MAPPED_SUBRESOURCE mappedUniform; + if (context->Map(sh_wipe_uniform,0,D3D11_MAP_WRITE_DISCARD,0,&mappedUniform)!=S_OK) { + logW("could not map constant"); + } + WipeUniform* sh_wipe_uniformState=(WipeUniform*)mappedUniform.pData; + sh_wipe_uniformState->alpha=alpha; + context->Unmap(sh_wipe_uniform,0); + + context->RSSetViewports(1,&viewPort); + context->RSSetState(rsState); + + context->OMSetBlendState(omBlendState,blendFactor,0xffffffff); + context->IASetInputLayout(sh_wipe_inputLayout); + context->IASetVertexBuffers(0,1,&quadVertex,&strides,&offsets); + context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + context->VSSetShader(sh_wipe_vertex,NULL,0); + context->VSSetConstantBuffers(0,1,&sh_wipe_uniform); + context->PSSetShader(sh_wipe_fragment,NULL,0); + + context->Draw(4,0); } void FurnaceGUIRenderDX11::present() { @@ -258,6 +334,13 @@ int FurnaceGUIRenderDX11::getWindowFlags() { void FurnaceGUIRenderDX11::preInit() { } +const float wipeVertices[4][4]={ + -1.0, -1.0, 0.0, 1.0, + 1.0, -1.0, 0.0, 1.0, + -1.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 0.0, 1.0 +}; + bool FurnaceGUIRenderDX11::init(SDL_Window* win) { SDL_SysWMinfo sysWindow; D3D_FEATURE_LEVEL featureLevel; @@ -291,6 +374,131 @@ bool FurnaceGUIRenderDX11::init(SDL_Window* win) { return false; } + // https://github.com/ocornut/imgui/pull/638 + D3DCompile_t D3DCompile=NULL; + char dllBuffer[20]; + for (int i=47; (i>30 && !D3DCompile); i--) { + snprintf(dllBuffer,20,"d3dcompiler_%d.dll",i); + HMODULE hDll=LoadLibraryA(dllBuffer); + if (hDll) { + D3DCompile=(D3DCompile_t)GetProcAddress(hDll,"D3DCompile"); + } + } + if (!D3DCompile) { + logE("could not find D3DCompile!"); + return false; + } + + // create wipe shader + ID3DBlob* wipeBlobV=NULL; + ID3DBlob* wipeBlobF=NULL; + D3D11_BUFFER_DESC wipeConstantDesc; + + result=D3DCompile(shD3D11_wipe_srcV,strlen(shD3D11_wipe_srcV),NULL,NULL,NULL,"main","vs_4_0",0,0,&wipeBlobV,NULL); + if (result!=S_OK) { + logE("could not compile vertex shader! %.8x",result); + return false; + } + result=D3DCompile(shD3D11_wipe_srcF,strlen(shD3D11_wipe_srcF),NULL,NULL,NULL,"main","ps_4_0",0,0,&wipeBlobF,NULL); + if (result!=S_OK) { + logE("could not compile pixel shader! %.8x",result); + return false; + } + + result=device->CreateVertexShader(wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),NULL,&sh_wipe_vertex); + if (result!=S_OK) { + logE("could not create vertex shader! %.8x",result); + return false; + } + result=device->CreatePixelShader(wipeBlobF->GetBufferPointer(),wipeBlobF->GetBufferSize(),NULL,&sh_wipe_fragment); + if (result!=S_OK) { + logE("could not create pixel shader! %.8x",result); + return false; + } + + result=device->CreateInputLayout(&shD3D11_wipe_inputLayout,1,wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),&sh_wipe_inputLayout); + if (result!=S_OK) { + logE("could not create input layout! %.8x",result); + return false; + } + + memset(&wipeConstantDesc,0,sizeof(wipeConstantDesc)); + wipeConstantDesc.ByteWidth=sizeof(WipeUniform); + wipeConstantDesc.Usage=D3D11_USAGE_DYNAMIC; + wipeConstantDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER; + wipeConstantDesc.CPUAccessFlags=D3D11_CPU_ACCESS_WRITE; + wipeConstantDesc.MiscFlags=0; + wipeConstantDesc.StructureByteStride=0; + + result=device->CreateBuffer(&wipeConstantDesc,NULL,&sh_wipe_uniform); + if (result!=S_OK) { + logE("could not create constant buffer! %.8x",result); + return false; + } + + // create wipe vertices + D3D11_BUFFER_DESC vertexDesc; + D3D11_SUBRESOURCE_DATA vertexRes; + + memset(&vertexDesc,0,sizeof(vertexDesc)); + memset(&vertexRes,0,sizeof(vertexRes)); + + vertexDesc.ByteWidth=4*4*sizeof(float); + vertexDesc.Usage=D3D11_USAGE_DEFAULT; + vertexDesc.BindFlags=D3D11_BIND_VERTEX_BUFFER; + vertexDesc.CPUAccessFlags=0; + vertexDesc.MiscFlags=0; + vertexDesc.StructureByteStride=0; + + vertexRes.pSysMem=wipeVertices; + vertexRes.SysMemPitch=0; + vertexRes.SysMemSlicePitch=0; + + result=device->CreateBuffer(&vertexDesc,&vertexRes,&quadVertex); + if (result!=S_OK) { + logE("could not create vertex buffer! %.8x",result); + return false; + } + + // initialize the rest + D3D11_RASTERIZER_DESC rasterDesc; + D3D11_BLEND_DESC blendDesc; + + memset(&rasterDesc,0,sizeof(rasterDesc)); + memset(&blendDesc,0,sizeof(blendDesc)); + + rasterDesc.FillMode=D3D11_FILL_SOLID; + rasterDesc.CullMode=D3D11_CULL_NONE; + rasterDesc.FrontCounterClockwise=false; + rasterDesc.DepthBias=0; + rasterDesc.DepthBiasClamp=0.0f; + rasterDesc.SlopeScaledDepthBias=0.0f; + rasterDesc.DepthClipEnable=false; + rasterDesc.ScissorEnable=false; + rasterDesc.MultisampleEnable=false; + rasterDesc.AntialiasedLineEnable=false; + result=device->CreateRasterizerState(&rasterDesc,&rsState); + if (result!=S_OK) { + logE("could not create rasterizer state! %.8x",result); + return false; + } + + blendDesc.AlphaToCoverageEnable=false; + blendDesc.IndependentBlendEnable=false; + blendDesc.RenderTarget[0].BlendEnable=true; + blendDesc.RenderTarget[0].SrcBlend=D3D11_BLEND_SRC_ALPHA; + blendDesc.RenderTarget[0].DestBlend=D3D11_BLEND_INV_SRC_ALPHA; + blendDesc.RenderTarget[0].BlendOp=D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].SrcBlendAlpha=D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlendAlpha=D3D11_BLEND_INV_SRC_ALPHA; + blendDesc.RenderTarget[0].BlendOpAlpha=D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].RenderTargetWriteMask=D3D11_COLOR_WRITE_ENABLE_ALL; + result=device->CreateBlendState(&blendDesc,&omBlendState); + if (result!=S_OK) { + logE("could not create blend state! %.8x",result); + return false; + } + createRenderTarget(); return true; } diff --git a/src/gui/render/renderDX11.h b/src/gui/render/renderDX11.h index 19cfb6f0..18d8673d 100644 --- a/src/gui/render/renderDX11.h +++ b/src/gui/render/renderDX11.h @@ -23,6 +23,12 @@ #else typedef void ID3D11DeviceContext; typedef void ID3D11RenderTargetView; +typedef void ID3D11Buffer; +typedef void ID3D11RasterizerState; +typedef void ID3D11BlendState; +typedef void ID3D11VertexShader; +typedef void ID3D11PixelShader; +typedef void ID3D11InputLayout; typedef void IDXGISwapChain; #endif @@ -33,8 +39,22 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { ID3D11DeviceContext* context; ID3D11RenderTargetView* renderTarget; IDXGISwapChain* swapchain; + ID3D11RasterizerState* rsState; + ID3D11BlendState* omBlendState; + + ID3D11Buffer* quadVertex; int outW, outH; - float quadVertex[4][3]; + + // SHADERS // + // -> wipe + ID3D11VertexShader* sh_wipe_vertex; + ID3D11PixelShader* sh_wipe_fragment; + ID3D11InputLayout* sh_wipe_inputLayout; + ID3D11Buffer* sh_wipe_uniform; + struct WipeUniform { + float alpha; + float padding[7]; + }; bool destroyRenderTarget(); bool createRenderTarget(); @@ -70,8 +90,14 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender { context(NULL), renderTarget(NULL), swapchain(NULL), + rsState(NULL), + omBlendState(NULL), + quadVertex(NULL), outW(0), - outH(0) { - memset(quadVertex,0,4*3*sizeof(float)); + outH(0), + sh_wipe_vertex(NULL), + sh_wipe_fragment(NULL), + sh_wipe_inputLayout(NULL), + sh_wipe_uniform(NULL) { } }; From 4f39e6ee31830f5346e577aed4b9525b9b0ee96e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 03:54:55 -0500 Subject: [PATCH 19/46] GUI: DirectX 11 render backend, part 5 nope, not yet --- src/gui/render/renderDX11.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 1ed6c9cc..5b2cbb36 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -245,7 +245,7 @@ void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) { void FurnaceGUIRenderDX11::resized(const SDL_Event& ev) { destroyRenderTarget(); - swapchain->ResizeBuffers(0,0,0,DXGI_FORMAT_UNKNOWN,0); + swapchain->ResizeBuffers(0,(unsigned int)ev.window.data1,(unsigned int)ev.window.data2,DXGI_FORMAT_UNKNOWN,DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); createRenderTarget(); } From d85dd7071a5472410a636b190ee7f60411f3d9ff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 04:06:03 -0500 Subject: [PATCH 20/46] GUI: DirectX 11 render backend, part 6 nice I probably found a new DXVK bug --- src/gui/render/renderDX11.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 5b2cbb36..5f7e70d6 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -109,6 +109,7 @@ bool FurnaceGUIRenderDX11::createRenderTarget() { } else { outW=chainDesc.BufferDesc.Width; outH=chainDesc.BufferDesc.Height; + logI("DX11: buffer desc sizes: %d, %d",chainDesc.BufferDesc.Width,chainDesc.BufferDesc.Height); } result=swapchain->GetBuffer(0,IID_PPV_ARGS(&screen)); @@ -133,6 +134,7 @@ bool FurnaceGUIRenderDX11::createRenderTarget() { return false; } + screen->Release(); return true; } @@ -245,7 +247,11 @@ void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) { void FurnaceGUIRenderDX11::resized(const SDL_Event& ev) { destroyRenderTarget(); - swapchain->ResizeBuffers(0,(unsigned int)ev.window.data1,(unsigned int)ev.window.data2,DXGI_FORMAT_UNKNOWN,DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); + logI("DX11: resizing buffers"); + HRESULT result=swapchain->ResizeBuffers(0,(unsigned int)ev.window.data1,(unsigned int)ev.window.data2,DXGI_FORMAT_UNKNOWN,DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); + if (result!=S_OK) { + logW("error while resizing swapchain buffers! %.8x",result); + } createRenderTarget(); } From db14ce602d1901642b940730727edce62ae87306 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 04:16:19 -0500 Subject: [PATCH 21/46] why are you not showing me the return value I want to see what's going on --- src/gui/render/renderDX11.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 5f7e70d6..452b8180 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -152,7 +152,7 @@ bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) { HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE,0,&mappedRes); if (result!=S_OK) { - logW("could not map texture!"); + logW("could not map texture! %.8x",result); return false; } t->lockedData=(unsigned char*)mappedRes.pData; From 8ee4734eabaef61223f434a02919d31351893d82 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 04:24:34 -0500 Subject: [PATCH 22/46] DISCARD --- src/gui/render/renderDX11.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index 452b8180..ea6aac78 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -150,7 +150,7 @@ bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) { D3D11_MAPPED_SUBRESOURCE mappedRes; memset(&mappedRes,0,sizeof(mappedRes)); - HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE,0,&mappedRes); + HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE_DISCARD,0,&mappedRes); if (result!=S_OK) { logW("could not map texture! %.8x",result); return false; From 1ffa80e84594092540c02563cfa5f74a070d6f6d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 04:38:13 -0500 Subject: [PATCH 23/46] and one more piece of code --- src/gui/render/renderDX11.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index ea6aac78..e7ea0910 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -158,6 +158,8 @@ bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) { t->lockedData=(unsigned char*)mappedRes.pData; *data=mappedRes.pData; *pitch=mappedRes.RowPitch; + + logV("texture locked... pitch: %d",mappedRes.RowPitch); return true; } From 6933446d8a5d69d6f2470b10798c71c568ca512f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 13 Jun 2023 05:45:36 -0500 Subject: [PATCH 24/46] GUI: DirectX 11 render backend, part 7 now with texture workaround --- src/gui/sampleEdit.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 8f5a9843..5d2e1d9f 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -1231,7 +1231,17 @@ void FurnaceGUI::drawSampleEdit() { } } - memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); + if ((pitch>>2)==sampleTexW) { + memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int)); + } else { + int srcY=0; + int destY=0; + for (int i=0; i>2; + } + } rend->unlockTexture(sampleTex); delete[] data; } From 83ad6e08941b556410d590ab06029ddf16fd3e62 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 14 Jun 2023 14:50:39 -0500 Subject: [PATCH 25/46] GUI: DirectX 11 render backend, part 8 updateTexture for dynamic textures --- src/gui/render/renderDX11.cpp | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index e7ea0910..b860890f 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -74,12 +74,14 @@ struct FurnaceDXTexture { ID3D11ShaderResourceView* view; int width, height; unsigned char* lockedData; + bool dynamic; FurnaceDXTexture(): tex(NULL), view(NULL), width(0), height(0), - lockedData(NULL) {} + lockedData(NULL), + dynamic(false) {} }; bool FurnaceGUIRenderDX11::destroyRenderTarget() { @@ -173,7 +175,26 @@ bool FurnaceGUIRenderDX11::unlockTexture(void* which) { bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) { FurnaceDXTexture* t=(FurnaceDXTexture*)which; - context->UpdateSubresource(t->tex,D3D11CalcSubresource(0,0,1),NULL,data,pitch,pitch*t->height); + if (t->dynamic) { + unsigned char* d=NULL; + int p=0; + if (!lockTexture(t,&d,&p)) return false; + if (p==pitch) { + memcpy(d,data,p*t->height); + } else { + unsigned char* ucData=(unsigned char*)data; + int srcPos=0; + int destPos=0; + for (int i=0; iheight; i++) { + memcpy(&d[destPos],&ucData[srcPos],pitch); + srcPos+=pitch; + destPos+=p; + } + } + unlockTexture(t); + } else { + context->UpdateSubresource(t->tex,D3D11CalcSubresource(0,0,1),NULL,data,pitch,pitch*t->height); + } return true; } @@ -222,6 +243,7 @@ void* FurnaceGUIRenderDX11::createTexture(bool dynamic, int width, int height) { ret->height=height; ret->tex=tex; ret->view=view; + ret->dynamic=dynamic; textures.push_back(ret); return ret; } From cca84dea00aea754d615cc026d8eee1f3ae4e884 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 14 Jun 2023 17:21:02 -0500 Subject: [PATCH 26/46] fix --- src/gui/render/renderDX11.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/render/renderDX11.cpp b/src/gui/render/renderDX11.cpp index b860890f..18cbcbae 100644 --- a/src/gui/render/renderDX11.cpp +++ b/src/gui/render/renderDX11.cpp @@ -178,7 +178,7 @@ bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) { if (t->dynamic) { unsigned char* d=NULL; int p=0; - if (!lockTexture(t,&d,&p)) return false; + if (!lockTexture(t,(void**)&d,&p)) return false; if (p==pitch) { memcpy(d,data,p*t->height); } else { From 89f8c8fa19a274fcd1540a50b474c13128f12615 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 14 Jun 2023 17:35:17 -0500 Subject: [PATCH 27/46] OPLL: try to fix vol macro for drums --- src/engine/platform/opll.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 8f30a9dd..d0effd15 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -101,8 +101,17 @@ void DivPlatformOPLL::tick(bool sysTick) { if (chan[i].std.vol.had) { chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].vol,MIN(15,chan[i].std.vol.val),15); - if (i<9) { - rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); + + if (i>=6 && properDrums) { + drumVol[i-6]=15-chan[i].outVol; + rWrite(0x36,drumVol[0]); + rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + break; + } else if (i<6 || !drums) { + if (i<9) { + rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); + } } } From 65a81d67b94989b7b143a02ac053ab1142364dda Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Tue, 13 Jun 2023 23:57:42 +0200 Subject: [PATCH 28/46] Update sample doc --- doc/6-sample/README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/6-sample/README.md b/doc/6-sample/README.md index 05891a49..2f4fe3cd 100644 --- a/doc/6-sample/README.md +++ b/doc/6-sample/README.md @@ -1,6 +1,6 @@ # samples -In the context of Furnace, a sound sample (usually just referred to as a sample) is a string of numbers that hold sampled PCM audio. +In the context of Furnace, a sound sample (usually just referred to as a sample) is a string of numbers that represent sampled PCM audio. In Furnace, these samples can be generated by importing a .wav (think of it as an higher quality MP3) file. @@ -13,6 +13,7 @@ as of Furnace 0.6, the following sound chips have sample support: - PC Engine/TurboGrafx-16/HuC6280 - Amiga/Paula - SegaPCM +- NEC PC-9801/YM2608 (ADPCM channel only) - Neo Geo/Neo Geo CD/YM2610 (ADPCM channels only) - Seta/Allumer X1-010 - Atari Lynx @@ -21,12 +22,16 @@ as of Furnace 0.6, the following sound chips have sample support: - QSound - ZX Spectrum 48k (1-bit) - RF5C68 -- WonderSwan +- SNES/S-DSP +- WonderSwan (second channel only) - tildearrow Sound Unit - VERA (last channel only) - Y8950 (last channel only) - Konami K007232 -- a few more that I've forgotten to mention +- Irem GA20 +- Ensoniq OTTO/ES5506 +- Yamaha PCMD8/YMZ280B +- MMC5 (last channel only) ## compatible sample mode @@ -45,9 +50,9 @@ due to limitations in some of those sound chips, some restrictions exist: - NES: if on DPCM mode, only a limited selection of frequencies is available, and loop position isn't supported (only entire sample). - SegaPCM: your sample can't be longer than 65535, and the maximum frequency is 31.25KHz. - QSound: your sample can't be longer than 65535, and the loop length shall not be greater than 32767. -- Neo Geo (ADPCM-A): no looping supported. your samples will play at ~18.5KHz. -- Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is ~55KHz. -- YM2608: the maximum frequency is ~55KHz. +- Neo Geo (ADPCM-A): no looping supported. your samples will play at 18.518KHz. +- Neo Geo (ADPCM-B): no loop position supported (only entire sample), and the maximum frequency is 55.555KHz. +- YM2608: the maximum frequency is 55.555KHz. - MSM6258/MSM6295: no arbitrary frequency. - ZX Spectrum Beeper: your sample can't be longer than 2048, and it always plays at ~55KHz. - Seta/Allumer X1-010: frequency resolution is terrible in the lower end. your sample can't be longer than 131072. From baf2964faa4fd700ef46113040b42640e22e5d83 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:05:54 +0200 Subject: [PATCH 29/46] Update README.md wavetable --- doc/5-wave/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/5-wave/README.md b/doc/5-wave/README.md index 19a07b10..a42ffd81 100644 --- a/doc/5-wave/README.md +++ b/doc/5-wave/README.md @@ -6,7 +6,11 @@ Furnace's wavetable editor is rather simple, you can draw the waveform using mou Furnace's wavetable editor features multiple ways of creating desired waveform shape: -- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders TODO: what the last two are doing? What is amplitude/phase for?) +- Shape tab allows you to select a few predefined basic shapes and indirectly edit it via "Duty", "Exponent" and "XOR Point" sliders: + - `Duty` slider affects mainly pulse waves, determining its wisth, like on C64/VRC6 + - `Exponent` powers the waveform in the mathematical sense of the word (^2, ^3 and so on) + - `XOR Point` determines the point where the waveform gets negated. + - TODO: amplitude/phase part - FM is for creating the waveform with frequency modulation synthesis principles: One can set carrier/modulation levels, frquency multiplier, connection between operators and FM waveforms of these operators. - WaveTools allows user to fine-tune the waveform: scale said waveform in both X and Y axes, smoothen, amplify, normalize, convert to signed/unisgned, invert or even randomize the wavetable. From f8346f55885cb51fd3119a6690bcc7f7591c70c7 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:16:02 +0200 Subject: [PATCH 30/46] Update opll.md drum mode internals --- doc/7-systems/opll.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/7-systems/opll.md b/doc/7-systems/opll.md index 8b16e888..115e4bd7 100644 --- a/doc/7-systems/opll.md +++ b/doc/7-systems/opll.md @@ -12,7 +12,11 @@ OPLL also spawned a few derivative chips, the best known of these is: the YM2413 is equipped with the following features: - 9 channels of 2 operator FM synthesis -- A drum/percussion mode, replacing the last 3 voices with 5 rhythm channels +- A drum/percussion mode, replacing the last 3 voices with 5 rhythm channels, with drum mode tones hard-defined in the chip itself, like FM instruments. Only pitch might be altered. + + - Drum mode works like following: FM channel 7 is for Kick Drum, which is a normal FM channel but routed through mxier twice for 2x volume, like all drum sounds. FM channel 8 splits to Snare Drum and Hi-Hat. Snare Drum is the carrier and it works with a special 1 bit noise generator combined with a square wave, all possible by overriding phase-generator with some different synthesis method. Hi-Hat is the modulator and it works with the noise generator and also the special synthesis. CH9 splits to Top-Cymbal and Tom-Tom, Top-Cymbal is the carrier and only has the special synthesis, while Tom-Tom is basically a 1op wave. + - Special syntheis mentioned already is: 5 square waves are gathered from 4x, 64x and 128x the pitch of channel 8 and 16x and 64x the pitch of channel 9 and they go through a process where 2 HH bits OR'd together, then 1 HH and 1 TC bit OR'd, then the two TC bits OR'd together, and those 3 results get XOR'd. + - 1 user-definable patch (this patch can be changed throughout the course of the song) - 15 pre-defined patches which can all be used at the same time - Support for ADSR on both the modulator and the carrier @@ -60,4 +64,4 @@ the YM2413 is equipped with the following features: - `58xx`: set DR of operator 2. - `5Bxy`: set KSR of operator. - `x` is the operator (1-2). a value of 0 means "all operators". - - `y` determines whether KSR is on. \ No newline at end of file + - `y` determines whether KSR is on. From 72b654f708b507a2d4d49f13bddad07d6860222f Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:17:42 +0200 Subject: [PATCH 31/46] Update opl.md ditto --- doc/7-systems/opl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/7-systems/opl.md b/doc/7-systems/opl.md index b4a86e6d..80c00299 100644 --- a/doc/7-systems/opl.md +++ b/doc/7-systems/opl.md @@ -3,7 +3,7 @@ a series of FM sound chips which were very popular in DOS land. it was so popular that even Yamaha made a logo for it! essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel. -however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. +however, it also had a [drums mode](opll.md), and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. the original OPL (Yamaha YM3526) was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 two-operator channels and drums mode. @@ -77,4 +77,4 @@ afterwards everyone moved to Windows and software mixed PCM streaming... - only in 4-op mode (OPL3). - `5Bxy`: set KSR of operator. - `x` is the operator (1-4; last 2 operators only in 4-op mode). a value of 0 means "all operators". - - `y` determines whether KSR is on. \ No newline at end of file + - `y` determines whether KSR is on. From 3e7710651a3cb71d54819c440dc387df61ecf4fb Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:23:08 +0200 Subject: [PATCH 32/46] Update ym2612.md src: genny vst and sonic.exe vs fnf --- doc/7-systems/ym2612.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/7-systems/ym2612.md b/doc/7-systems/ym2612.md index 1eeb148e..cbb38e33 100644 --- a/doc/7-systems/ym2612.md +++ b/doc/7-systems/ym2612.md @@ -1,6 +1,6 @@ # Yamaha YM2612 -one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player. +one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player, that via software mixing, thanks to Z80 sound CPU, can play more than one channel of straight-shot samples at once. As of Furnace 0.6pre5, Furnace offers DualPCM, which allows 2 channels ofsoftware-mixed 8-bit PCM samples at 13750 Hz. # effects From 2658f4cfdcdaed60347cfef81dfacdd9ba33d628 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:23:26 +0200 Subject: [PATCH 33/46] typo --- doc/7-systems/ym2612.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/7-systems/ym2612.md b/doc/7-systems/ym2612.md index cbb38e33..dda45266 100644 --- a/doc/7-systems/ym2612.md +++ b/doc/7-systems/ym2612.md @@ -1,6 +1,6 @@ # Yamaha YM2612 -one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player, that via software mixing, thanks to Z80 sound CPU, can play more than one channel of straight-shot samples at once. As of Furnace 0.6pre5, Furnace offers DualPCM, which allows 2 channels ofsoftware-mixed 8-bit PCM samples at 13750 Hz. +one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player, that via software mixing, thanks to Z80 sound CPU, can play more than one channel of straight-shot samples at once. As of Furnace 0.6pre5, Furnace offers DualPCM, which allows 2 channels of software-mixed 8-bit PCM samples at 13750 Hz. # effects From e089c3940dfaa0ce14042bd1c1012e8320ab29a7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 15 Jun 2023 15:45:53 -0500 Subject: [PATCH 34/46] fix atari breakbeat issue #1157 --- demos/a2600/atari breakbeat.fur | Bin 1101 -> 1096 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/a2600/atari breakbeat.fur b/demos/a2600/atari breakbeat.fur index dd4183befab50e4c8cde3799e4e2c3df042577a6..7dfbb0e35700a659689831e1cbfcb672b52c472e 100644 GIT binary patch literal 1096 zcmV-O1h@Nmob6XlZyQAzerIN9?IcY$AP`7CIDANzkfK^W5TpXNX$gu_FBa_|*tqMi zvGY;e5sic(kpOYy#x1JE58&7{9JuoX5ZXUbi33N_^33ecdL7%f7pq99o%orZdEe*# z*mqv9H=EnXy#uf5Z|oo5J?{FO4~b}lhGWOw{`e8cLP|z-^ua0FWUUvpZW;KBo;<2E z#td`%ozG%C-Qf+qUVCG&=X;$!-y6`o2c7pJ{_O^F&J=+$LuKrmAbJE`w}^fLwj|LH zz`9NJ9q=O%7K#1_R#u3dRieE$qCXsPUO?ergcsbfurM>sZ0Ma%uak+VK9&j1)t0`; zUp#6$THBA?!oghp-P}AHqI_eF*yy z_95(*Z8Er&_Q;@B+|L`p6=3^T?W4(=+;dnv>_YvC-$MMLZ79fC3V`(PL810 zR8Scy=gh%CBp%VvXgPdg(E?Q*eHF)E6+9*tZHrNaeq&-hkuTCjZ0a9)JwHb#TPl;{ zFcaj0WGtNwN>P#+qUR^4fwTH$b$!=S4krCx#1Js#H+JXBh@rg4~^8r1=)~yVpd5a<8Jt< z8$RlWkGkQyV_r55D4J-e7T^N`X{D{(32j>VRyc(tAq!|LeKq^M@CbSVNS`_{p8xx}`FO&75~eQcH@c*8EybZUGuQJ-l#Im4^YVMr%v{f-{N9}Wq}mhs z_obP+o=5%PUzk6TX6AYxxqTP|3rX-{- zRZ;{D{HVlVz`#Gizrex<12Y>F3lkF?V#K_&9p@V7lDjI!zSakJp5L9n@8jpIUoM`U ze$rg`D%IlJQwgl?Jo{r^632 zXvFR|Z_{hmi%ZR_*I250n{Z{laSh$yZvv$O8i@f3p%mZ+ZhvumV*&iZ&9ec%;x@+s z9*koO4*_f)0eE{1;Qc9p4`%_soCElF5#Z-#RKasC8}|hFq@xb6uR}W?|8KWLQ+^2V z@L$-<0rBG9a(FmBQ@(Q)KP#(`D~*Nf>eZ>SwfeLA`oqQN8&y2OJG^kdg#)(0g}L%L zta!`VTlAV6Fg<%3eq-0vtK-K@1^MS}`3#;66TS}?QWb23>NG_4k&%KmGUm_R!nspq z^XwD3J;y^ok3%zP)GG~*pci;h=5ujUz=pY_K70;RSVD!8MZ-?lu?hH?E@CACUd8|p zwAQ_5RfFIXCvZCi$bgq?#}IhWOHQ;|b{ocVlN66)iV>*QCu=y>)GTo%~v zl|#2*Ha=@Qrt>>Pikp^>>HKU+ansTzMvPHJ#t|2de4( zraVxy8kfQZXsr-P^cxxfy@~_^uuUNJCk42lO8gci$A~)Jwyw?~{hWVq^2_|D@SC3h zoIjiVmV!NqUnmGvC4!iGQazL{(>yzUlmqgumNQCH|fAtLmi1Kd2xK zi=RpOyZuz+-)a6;b@n>HE8*| Date: Fri, 16 Jun 2023 13:34:03 -0500 Subject: [PATCH 35/46] YM2612: fix DualPCM chan osc sustain issue #1162 --- src/engine/platform/genesis.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 9f905105..8e92c40f 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -188,9 +188,11 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7; } else { oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<7; + oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); + oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); @@ -251,9 +253,11 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7; } else { oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<7; + oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=chOut; + oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { oscBuf[i]->data[oscBuf[i]->needle++]=chOut; From 7b30cfb1b75ad4569b86134fc4a0235e52f7c019 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 16 Jun 2023 15:43:41 -0500 Subject: [PATCH 36/46] IGFD: some changes --- extern/igfd/ImGuiFileDialog.cpp | 13 +++++++------ extern/igfd/ImGuiFileDialog.h | 2 ++ src/gui/fileDialog.cpp | 3 +++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 4af6ca44..4ad01ba1 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -133,6 +133,9 @@ namespace IGFD #ifndef resetButtonString #define resetButtonString ICON_FA_REPEAT #endif // resetButtonString +#ifndef homeButtonString +#define homeButtonString ICON_FA_HOME +#endif // bomeButtonString #ifndef drivesButtonString #define drivesButtonString ICON_FA_HDD_O #endif // drivesButtonString @@ -170,7 +173,7 @@ namespace IGFD #define buttonEditPathString "Edit path\nYou can also right click on path buttons" #endif // buttonEditPathString #ifndef buttonResetPathString -#define buttonResetPathString "Reset to current directory" +#define buttonResetPathString "Go to home directory" #endif // buttonResetPathString #ifndef buttonParentDirString #define buttonParentDirString "Go to parent directory" @@ -700,10 +703,8 @@ namespace IGFD if (ImGui::IsItemHovered()) ImGui::SetTooltip(buttonResetSearchString); ImGui::SameLine(); - ImGui::Text(searchString); - ImGui::SameLine(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - bool edited = ImGui::InputText("##InputImGuiFileDialogSearchField", puSearchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + bool edited = ImGui::InputTextWithHint("##InputImGuiFileDialogSearchField", searchString, puSearchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); if (ImGui::GetItemID() == ImGui::GetActiveID()) puSearchInputIsActive = true; ImGui::PopItemWidth(); @@ -2245,9 +2246,9 @@ namespace IGFD void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) { - if (IMGUI_BUTTON(resetButtonString)) + if (IMGUI_BUTTON(homeButtonString)) { - SetCurrentPath("."); + SetCurrentPath(FileDialog::Instance()->homePath); OpenCurrentPath(vFileDialogInternal); } if (ImGui::IsItemHovered()) diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 827a987e..2ad9cbc8 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -878,6 +878,7 @@ namespace IGFD bool fileListActuallyEmpty = false; std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called + std::string puError; // last error std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called size_t puDLGcountSelectionMax = 1U; // 0 for infinite // base max selection count set by user when OpenDialog/OpenModal was called bool puDLGDirectoryMode = false; // is directory mode (defiend like : puDLGDirectoryMode = (filters.empty())) @@ -1145,6 +1146,7 @@ namespace IGFD double DpiScale; bool singleClickSel; bool mobileMode; + std::string homePath; public: static FileDialog* Instance() // Singleton for easier accces form anywhere but only one dialog at a time diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 94ffa94e..a0a45e1e 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,5 +1,6 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" +#include "util.h" #include "../ta-log.h" #ifdef USE_NFD @@ -152,6 +153,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c ImGuiFileDialog::Instance()->singleClickSel=mobileUI; ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->mobileMode=mobileUI; + ImGuiFileDialog::Instance()->homePath=getHomeDir(); ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,allowMultiple?999:1,nullptr,0,clickCallback); } opened=true; @@ -235,6 +237,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c ImGuiFileDialog::Instance()->singleClickSel=false; ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->mobileMode=mobileUI; + ImGuiFileDialog::Instance()->homePath=getHomeDir(); ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); } opened=true; From 9b1fea5c36f2bed12e76b8d1238411ee2f5f17c2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 16 Jun 2023 16:12:20 -0500 Subject: [PATCH 37/46] IGFD: remove std::filesystem support it is unused in Furnace --- extern/igfd/ImGuiFileDialog.cpp | 89 +---------------------------- extern/igfd/ImGuiFileDialog.h | 12 +--- extern/igfd/ImGuiFileDialogConfig.h | 3 - 3 files changed, 3 insertions(+), 101 deletions(-) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 4ad01ba1..b4941c79 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -42,10 +42,6 @@ SOFTWARE. #include #include #include -// this option need c++17 -#ifdef USE_STD_FILESYSTEM - #include -#endif #if defined (__EMSCRIPTEN__) // EMSCRIPTEN #include #endif // EMSCRIPTEN @@ -53,12 +49,7 @@ SOFTWARE. #define stat _stat #define stricmp _stricmp #include - // this option need c++17 - #ifdef USE_STD_FILESYSTEM - #include - #else - #include "dirent/dirent.h" // directly open the dirent file attached to this lib - #endif // USE_STD_FILESYSTEM + #include "dirent/dirent.h" // directly open the dirent file attached to this lib #define PATH_SEP '\\' #ifndef PATH_MAX #define PATH_MAX 260 @@ -67,10 +58,7 @@ SOFTWARE. #define UNIX #define stricmp strcasecmp #include - // this option need c++17 - #ifndef USE_STD_FILESYSTEM - #include - #endif // USE_STD_FILESYSTEM + #include #define PATH_SEP '/' #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) @@ -320,12 +308,10 @@ namespace IGFD //// INLINE FUNCTIONS /////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////// -#ifndef USE_STD_FILESYSTEM inline int inAlphaSort(const struct dirent** a, const struct dirent** b) { return strcoll((*a)->d_name, (*b)->d_name); } -#endif ///////////////////////////////////////////////////////////////////////////////////// //// FILE EXTENTIONS INFOS ////////////////////////////////////////////////////////// @@ -497,16 +483,6 @@ namespace IGFD if (!name.empty()) { -#ifdef USE_STD_FILESYSTEM - namespace fs = std::filesystem; -#ifdef WIN32 - std::wstring wname = IGFD::Utils::string_to_wstring(name.c_str()); - fs::path pathName = fs::path(wname); -#else - fs::path pathName = fs::path(name); -#endif - bExists = fs::is_directory(pathName); -#else DIR* pDir = nullptr; pDir = opendir(name.c_str()); if (pDir != nullptr) @@ -514,7 +490,6 @@ namespace IGFD bExists = true; (void)closedir(pDir); } -#endif // USE_STD_FILESYSTEM } return bExists; // this is not a directory! @@ -529,18 +504,11 @@ namespace IGFD if (!IsDirectoryExist(name)) { #ifdef WIN32 -#ifdef USE_STD_FILESYSTEM - namespace fs = std::filesystem; - std::wstring wname = IGFD::Utils::string_to_wstring(name.c_str()); - fs::path pathName = fs::path(wname); - res = fs::create_directory(pathName); -#else std::wstring wname = IGFD::Utils::string_to_wstring(name); if (CreateDirectoryW(wname.c_str(), nullptr)) { res = true; } -#endif // USE_STD_FILESYSTEM #elif defined(__EMSCRIPTEN__) std::string str = std::string("FS.mkdir('") + name + "');"; emscripten_run_script(str.c_str()); @@ -563,26 +531,6 @@ namespace IGFD return res; } -#ifdef USE_STD_FILESYSTEM - // https://github.com/aiekick/ImGuiFileDialog/issues/54 - IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) - { - namespace fs = std::filesystem; - PathStruct res; - if (vPathFileName.empty()) - return res; - - auto fsPath = fs::path(vPathFileName); - - if (fs::is_regular_file(fsPath)) { - res.name = fsPath.string(); - res.path = fsPath.parent_path().string(); - res.isOk = true; - } - - return res; - } -#else IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) { PathStruct res; @@ -627,7 +575,6 @@ namespace IGFD return res; } -#endif // USE_STD_FILESYSTEM void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) { std::string st = vStr; @@ -1552,24 +1499,6 @@ namespace IGFD ClearFileLists(); -#ifdef USE_STD_FILESYSTEM - //const auto wpath = IGFD::Utils::WGetString(path.c_str()); - const std::filesystem::path fspath(path); - const auto dir_iter = std::filesystem::directory_iterator(fspath); - AddFile(vFileDialogInternal, path, "..", 'd'); - for (const auto& file : dir_iter) - { - char fileType = 0; - if (file.is_symlink()) - fileType = 'l'; - else if (file.is_directory()) - fileType = 'd'; - else - fileType = 'f'; - auto fileNameExt = file.path().filename().string(); - AddFile(vFileDialogInternal, path, fileNameExt, fileType); - } -#else // dirent struct dirent** files = nullptr; int n = scandir(path.c_str(), &files, nullptr, inAlphaSort); logV("IGFD: %d entries in directory",n); @@ -1643,7 +1572,6 @@ namespace IGFD } else { logV("IGFD: it's empty"); } -#endif // USE_STD_FILESYSTEM logV("IGFD: sorting fields..."); SortFields(vFileDialogInternal, puSortingField, false); @@ -1891,16 +1819,6 @@ namespace IGFD path += std::string(1u, PATH_SEP); #endif // WIN32 -#ifdef USE_STD_FILESYSTEM - namespace fs = std::filesystem; - bool dir_opened = fs::is_directory(vPath); - if (!dir_opened) - { - path = "."; - dir_opened = fs::is_directory(vPath); - } - if (dir_opened) -#else DIR* dir = opendir(path.c_str()); if (dir == nullptr) { @@ -1909,7 +1827,6 @@ namespace IGFD } if (dir != nullptr) -#endif // USE_STD_FILESYSTEM { #ifdef WIN32 DWORD numchar = 0; @@ -1945,9 +1862,7 @@ namespace IGFD #endif // WIN32 } } -#ifndef USE_STD_FILESYSTEM closedir(dir); -#endif } } diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 2ad9cbc8..9c2d246f 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -534,23 +534,13 @@ if (IGFD_DisplayDialog(cfiledialog, "filedlg", ImGuiWindowFlags_NoCollapse, minS // destroy ImGuiFileDialog IGFD_Destroy(cfiledialog); ------------------------------------------------------------------------------------------------------------------ -## Std::filesystem (c++17) can be used instead of dirent.h ------------------------------------------------------------------------------------------------------------------ - -you just need to uncomment that in the config file - -#define USE_STD_FILESYSTEM - -in this mode dirent is not more required - ----------------------------------------------------------------------------------------------------------------- ## How to Integrate ImGuiFileDialog in your project ----------------------------------------------------------------------------------------------------------------- ### ImGuiFileDialog require : -* dirent v1.23 (only when USE_STD_FILESYSTEM is not defined) (https://github.com/tronkko/dirent/tree/v1.23) lib, only for windows. Successfully tested with version v1.23 only +* dirent v1.23 (https://github.com/tronkko/dirent/tree/v1.23) lib, only for windows. Successfully tested with version v1.23 only * Dear ImGui (https://github.com/ocornut/imgui/tree/master) (with/without tables widgets) ### Customize ImGuiFileDialog : diff --git a/extern/igfd/ImGuiFileDialogConfig.h b/extern/igfd/ImGuiFileDialogConfig.h index 1db638dd..4a212b60 100644 --- a/extern/igfd/ImGuiFileDialogConfig.h +++ b/extern/igfd/ImGuiFileDialogConfig.h @@ -2,9 +2,6 @@ // uncomment and modify defines under for customize ImGuiFileDialog -//this options need c++17 -//#define USE_STD_FILESYSTEM - //#define MAX_FILE_DIALOG_NAME_BUFFER 1024 //#define MAX_PATH_BUFFER_SIZE 1024 From ad9981fdea5aed2ee89b75a49711982cd339b537 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 16 Jun 2023 16:26:22 -0500 Subject: [PATCH 38/46] IGFD: tabs to spaces... --- extern/igfd/ImGuiFileDialog.cpp | 7854 +++++++++++++++---------------- extern/igfd/ImGuiFileDialog.h | 1758 +++---- 2 files changed, 4806 insertions(+), 4806 deletions(-) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index b4941c79..a3104839 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -26,7 +26,7 @@ SOFTWARE. */ #ifndef IMGUI_DEFINE_MATH_OPERATORS - #define IMGUI_DEFINE_MATH_OPERATORS + #define IMGUI_DEFINE_MATH_OPERATORS #endif // IMGUI_DEFINE_MATH_OPERATORS #include "ImGuiFileDialog.h" @@ -43,23 +43,23 @@ SOFTWARE. #include #include #if defined (__EMSCRIPTEN__) // EMSCRIPTEN - #include + #include #endif // EMSCRIPTEN #ifdef WIN32 - #define stat _stat - #define stricmp _stricmp - #include - #include "dirent/dirent.h" // directly open the dirent file attached to this lib - #define PATH_SEP '\\' - #ifndef PATH_MAX - #define PATH_MAX 260 - #endif // PATH_MAX + #define stat _stat + #define stricmp _stricmp + #include + #include "dirent/dirent.h" // directly open the dirent file attached to this lib + #define PATH_SEP '\\' + #ifndef PATH_MAX + #define PATH_MAX 260 + #endif // PATH_MAX #elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__) - #define UNIX - #define stricmp strcasecmp - #include - #include - #define PATH_SEP '/' + #define UNIX + #define stricmp strcasecmp + #include + #include + #define PATH_SEP '/' #endif // defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) #include "imgui.h" @@ -229,29 +229,29 @@ namespace IGFD #define DisplayMode_ThumbailsList_ImageHeight 32.0f #endif // DisplayMode_ThumbailsList_ImageHeight #ifndef IMGUI_RADIO_BUTTON - inline bool inRadioButton(const char* vLabel, bool vToggled) - { - bool pressed = false; + inline bool inRadioButton(const char* vLabel, bool vToggled) + { + bool pressed = false; - if (vToggled) - { - ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); - ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); - ImGui::PushStyleColor(ImGuiCol_Button, te); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); - ImGui::PushStyleColor(ImGuiCol_Text, bua); - } + if (vToggled) + { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } - pressed = IMGUI_BUTTON(vLabel); + pressed = IMGUI_BUTTON(vLabel); - if (vToggled) - { - ImGui::PopStyleColor(4); //-V112 - } + if (vToggled) + { + ImGui::PopStyleColor(4); //-V112 + } - return pressed; - } + return pressed; + } #define IMGUI_RADIO_BUTTON inRadioButton #endif // IMGUI_RADIO_BUTTON #endif // USE_THUMBNAILS @@ -272,902 +272,902 @@ namespace IGFD #define removeBookmarkButtonString "-" #endif // removeBookmarkButtonString #ifndef IMGUI_TOGGLE_BUTTON - inline bool inToggleButton(const char* vLabel, bool* vToggled) - { - bool pressed = false; + inline bool inToggleButton(const char* vLabel, bool* vToggled) + { + bool pressed = false; - if (vToggled && *vToggled) - { - ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); - //ImVec4 buh = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); - //ImVec4 bu = ImGui::GetStyleColorVec4(ImGuiCol_Button); - ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); - ImGui::PushStyleColor(ImGuiCol_Button, te); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); - ImGui::PushStyleColor(ImGuiCol_Text, bua); - } + if (vToggled && *vToggled) + { + ImVec4 bua = ImGui::GetStyleColorVec4(ImGuiCol_ButtonActive); + //ImVec4 buh = ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered); + //ImVec4 bu = ImGui::GetStyleColorVec4(ImGuiCol_Button); + ImVec4 te = ImGui::GetStyleColorVec4(ImGuiCol_Text); + ImGui::PushStyleColor(ImGuiCol_Button, te); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, te); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, te); + ImGui::PushStyleColor(ImGuiCol_Text, bua); + } - pressed = IMGUI_BUTTON(vLabel); + pressed = IMGUI_BUTTON(vLabel); - if (vToggled && *vToggled) - { - ImGui::PopStyleColor(4); //-V112 - } + if (vToggled && *vToggled) + { + ImGui::PopStyleColor(4); //-V112 + } - if (vToggled && pressed) - *vToggled = !*vToggled; + if (vToggled && pressed) + *vToggled = !*vToggled; - return pressed; - } + return pressed; + } #define IMGUI_TOGGLE_BUTTON inToggleButton #endif // IMGUI_TOGGLE_BUTTON #endif // USE_BOOKMARK - ///////////////////////////////////////////////////////////////////////////////////// - //// INLINE FUNCTIONS /////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// INLINE FUNCTIONS /////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - inline int inAlphaSort(const struct dirent** a, const struct dirent** b) - { - return strcoll((*a)->d_name, (*b)->d_name); - } + inline int inAlphaSort(const struct dirent** a, const struct dirent** b) + { + return strcoll((*a)->d_name, (*b)->d_name); + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE EXTENTIONS INFOS ////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE EXTENTIONS INFOS ////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - IGFD::FileStyle::FileStyle() - : color(0, 0, 0, 0) - { + IGFD::FileStyle::FileStyle() + : color(0, 0, 0, 0) + { - } - - IGFD::FileStyle::FileStyle(const FileStyle& vStyle) - { - color = vStyle.color; - icon = vStyle.icon; - font = vStyle.font; - flags = vStyle.flags; - } + } + + IGFD::FileStyle::FileStyle(const FileStyle& vStyle) + { + color = vStyle.color; + icon = vStyle.icon; + font = vStyle.font; + flags = vStyle.flags; + } - IGFD::FileStyle::FileStyle(const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) - : color(vColor), icon(vIcon), font(vFont) - { + IGFD::FileStyle::FileStyle(const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + : color(vColor), icon(vIcon), font(vFont) + { - } + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE INFOS ///////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE INFOS ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - // https://github.com/ocornut/imgui/issues/1720 - bool IGFD::Utils::Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size) - { - using namespace ImGui; - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiID id = window->GetID("##Splitter"); - ImRect bb; - bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); - bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); - return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 1.0f); - } + // https://github.com/ocornut/imgui/issues/1720 + bool IGFD::Utils::Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size) + { + using namespace ImGui; + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiID id = window->GetID("##Splitter"); + ImRect bb; + bb.Min = window->DC.CursorPos + (split_vertically ? ImVec2(*size1, 0.0f) : ImVec2(0.0f, *size1)); + bb.Max = bb.Min + CalcItemSize(split_vertically ? ImVec2(thickness, splitter_long_axis_size) : ImVec2(splitter_long_axis_size, thickness), 0.0f, 0.0f); + return SplitterBehavior(bb, id, split_vertically ? ImGuiAxis_X : ImGuiAxis_Y, size1, size2, min_size1, min_size2, 1.0f); + } #ifdef WIN32 - bool IGFD::Utils::WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr) - { - bool found = false; - size_t pos = 0; - while ((pos = str.find(oldStr, pos)) != std::wstring::npos) - { - found = true; - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); - } - return found; - } + bool IGFD::Utils::WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr) + { + bool found = false; + size_t pos = 0; + while ((pos = str.find(oldStr, pos)) != std::wstring::npos) + { + found = true; + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return found; + } - std::vector IGFD::Utils::WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty) - { - std::vector arr; - if (!text.empty()) - { - std::wstring::size_type start = 0; - std::wstring::size_type end = text.find(delimiter, start); - while (end != std::wstring::npos) - { - std::wstring token = text.substr(start, end - start); - if (!token.empty() || (token.empty() && pushEmpty)) //-V728 - arr.push_back(token); - start = end + 1; - end = text.find(delimiter, start); - } - std::wstring token = text.substr(start); - if (!token.empty() || (token.empty() && pushEmpty)) //-V728 - arr.push_back(token); - } - return arr; - } + std::vector IGFD::Utils::WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty) + { + std::vector arr; + if (!text.empty()) + { + std::wstring::size_type start = 0; + std::wstring::size_type end = text.find(delimiter, start); + while (end != std::wstring::npos) + { + std::wstring token = text.substr(start, end - start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + start = end + 1; + end = text.find(delimiter, start); + } + std::wstring token = text.substr(start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + } + return arr; + } - std::wstring IGFD::Utils::string_to_wstring(const std::string& str) - { - std::wstring ret; - if (!str.empty()) - { - size_t sz = std::mbstowcs(nullptr, str.c_str(), str.size()); - if (sz) - { - ret.resize(sz); - std::mbstowcs((wchar_t*)ret.data(), str.c_str(), sz); - } - } - return ret; - } + std::wstring IGFD::Utils::string_to_wstring(const std::string& str) + { + std::wstring ret; + if (!str.empty()) + { + size_t sz = std::mbstowcs(nullptr, str.c_str(), str.size()); + if (sz) + { + ret.resize(sz); + std::mbstowcs((wchar_t*)ret.data(), str.c_str(), sz); + } + } + return ret; + } - std::string IGFD::Utils::wstring_to_string(const std::wstring& str) - { - std::string ret; - if (!str.empty()) - { - size_t sz = std::wcstombs(nullptr, str.c_str(), str.size()); - if (sz) - { - ret.resize(sz); - std::wcstombs((char*)ret.data(), str.c_str(), sz); - } - } - return ret; - } + std::string IGFD::Utils::wstring_to_string(const std::wstring& str) + { + std::string ret; + if (!str.empty()) + { + size_t sz = std::wcstombs(nullptr, str.c_str(), str.size()); + if (sz) + { + ret.resize(sz); + std::wcstombs((char*)ret.data(), str.c_str(), sz); + } + } + return ret; + } #endif // WIN32 - bool IGFD::Utils::ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr) - { - bool found = false; - size_t pos = 0; - while ((pos = str.find(oldStr, pos)) != std::string::npos) - { - found = true; - str.replace(pos, oldStr.length(), newStr); - pos += newStr.length(); - } - return found; - } + bool IGFD::Utils::ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr) + { + bool found = false; + size_t pos = 0; + while ((pos = str.find(oldStr, pos)) != std::string::npos) + { + found = true; + str.replace(pos, oldStr.length(), newStr); + pos += newStr.length(); + } + return found; + } - std::vector IGFD::Utils::SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty) - { - std::vector arr; - if (!text.empty()) - { - size_t start = 0; - size_t end = text.find(delimiter, start); - while (end != std::string::npos) - { - auto token = text.substr(start, end - start); - if (!token.empty() || (token.empty() && pushEmpty)) //-V728 - arr.push_back(token); - start = end + 1; - end = text.find(delimiter, start); - } - auto token = text.substr(start); - if (!token.empty() || (token.empty() && pushEmpty)) //-V728 - arr.push_back(token); - } - return arr; - } + std::vector IGFD::Utils::SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty) + { + std::vector arr; + if (!text.empty()) + { + size_t start = 0; + size_t end = text.find(delimiter, start); + while (end != std::string::npos) + { + auto token = text.substr(start, end - start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + start = end + 1; + end = text.find(delimiter, start); + } + auto token = text.substr(start); + if (!token.empty() || (token.empty() && pushEmpty)) //-V728 + arr.push_back(token); + } + return arr; + } - std::vector IGFD::Utils::GetDrivesList() - { - std::vector res; + std::vector IGFD::Utils::GetDrivesList() + { + std::vector res; #ifdef WIN32 - const DWORD mydrives = 2048; - char lpBuffer[2048]; + const DWORD mydrives = 2048; + char lpBuffer[2048]; #define mini(a,b) (((a) < (b)) ? (a) : (b)) - const DWORD countChars = mini(GetLogicalDriveStringsA(mydrives, lpBuffer), 2047); + const DWORD countChars = mini(GetLogicalDriveStringsA(mydrives, lpBuffer), 2047); #undef mini - if (countChars > 0) - { - std::string var = std::string(lpBuffer, (size_t)countChars); - IGFD::Utils::ReplaceString(var, "\\", ""); - res = IGFD::Utils::SplitStringToVector(var, '\0', false); - } + if (countChars > 0) + { + std::string var = std::string(lpBuffer, (size_t)countChars); + IGFD::Utils::ReplaceString(var, "\\", ""); + res = IGFD::Utils::SplitStringToVector(var, '\0', false); + } #endif // WIN32 - return res; - } + return res; + } - bool IGFD::Utils::IsDirectoryExist(const std::string& name) - { - bool bExists = false; + bool IGFD::Utils::IsDirectoryExist(const std::string& name) + { + bool bExists = false; - if (!name.empty()) - { - DIR* pDir = nullptr; - pDir = opendir(name.c_str()); - if (pDir != nullptr) - { - bExists = true; - (void)closedir(pDir); - } - } + if (!name.empty()) + { + DIR* pDir = nullptr; + pDir = opendir(name.c_str()); + if (pDir != nullptr) + { + bExists = true; + (void)closedir(pDir); + } + } - return bExists; // this is not a directory! - } + return bExists; // this is not a directory! + } - bool IGFD::Utils::CreateDirectoryIfNotExist(const std::string& name) - { - bool res = false; + bool IGFD::Utils::CreateDirectoryIfNotExist(const std::string& name) + { + bool res = false; - if (!name.empty()) - { - if (!IsDirectoryExist(name)) - { + if (!name.empty()) + { + if (!IsDirectoryExist(name)) + { #ifdef WIN32 - std::wstring wname = IGFD::Utils::string_to_wstring(name); - if (CreateDirectoryW(wname.c_str(), nullptr)) - { - res = true; - } + std::wstring wname = IGFD::Utils::string_to_wstring(name); + if (CreateDirectoryW(wname.c_str(), nullptr)) + { + res = true; + } #elif defined(__EMSCRIPTEN__) - std::string str = std::string("FS.mkdir('") + name + "');"; - emscripten_run_script(str.c_str()); - res = true; + std::string str = std::string("FS.mkdir('") + name + "');"; + emscripten_run_script(str.c_str()); + res = true; #elif defined(UNIX) - char buffer[PATH_MAX] = {}; - snprintf(buffer, PATH_MAX, "mkdir -p %s", name.c_str()); - const int dir_err = std::system(buffer); - if (dir_err != -1) - { - res = true; - } + char buffer[PATH_MAX] = {}; + snprintf(buffer, PATH_MAX, "mkdir -p %s", name.c_str()); + const int dir_err = std::system(buffer); + if (dir_err != -1) + { + res = true; + } #endif // WIN32 - if (!res) { - std::cout << "Error creating directory " << name << std::endl; - } - } - } + if (!res) { + std::cout << "Error creating directory " << name << std::endl; + } + } + } - return res; - } + return res; + } - IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) - { - PathStruct res; + IGFD::Utils::PathStruct IGFD::Utils::ParsePathFileName(const std::string& vPathFileName) + { + PathStruct res; - if (!vPathFileName.empty()) - { - std::string pfn = vPathFileName; - std::string separator(1u, PATH_SEP); - IGFD::Utils::ReplaceString(pfn, "\\", separator); - IGFD::Utils::ReplaceString(pfn, "/", separator); + if (!vPathFileName.empty()) + { + std::string pfn = vPathFileName; + std::string separator(1u, PATH_SEP); + IGFD::Utils::ReplaceString(pfn, "\\", separator); + IGFD::Utils::ReplaceString(pfn, "/", separator); - size_t lastSlash = pfn.find_last_of(separator); - if (lastSlash != std::string::npos) - { - res.name = pfn.substr(lastSlash + 1); - res.path = pfn.substr(0, lastSlash); - res.isOk = true; - } + size_t lastSlash = pfn.find_last_of(separator); + if (lastSlash != std::string::npos) + { + res.name = pfn.substr(lastSlash + 1); + res.path = pfn.substr(0, lastSlash); + res.isOk = true; + } - size_t lastPoint = pfn.find_last_of('.'); - if (lastPoint != std::string::npos) - { - if (!res.isOk) - { - res.name = pfn; - res.isOk = true; - } - res.ext = pfn.substr(lastPoint + 1); - IGFD::Utils::ReplaceString(res.name, "." + res.ext, ""); - } + size_t lastPoint = pfn.find_last_of('.'); + if (lastPoint != std::string::npos) + { + if (!res.isOk) + { + res.name = pfn; + res.isOk = true; + } + res.ext = pfn.substr(lastPoint + 1); + IGFD::Utils::ReplaceString(res.name, "." + res.ext, ""); + } if (res.path.empty()) { res.path=separator; } - if (!res.isOk) - { - res.name = std::move(pfn); - res.isOk = true; - } - } + if (!res.isOk) + { + res.name = std::move(pfn); + res.isOk = true; + } + } - return res; - } - void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) - { - std::string st = vStr; - size_t len = vBufferLen - 1u; - size_t slen = strlen(vBuffer); + return res; + } + void IGFD::Utils::AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) + { + std::string st = vStr; + size_t len = vBufferLen - 1u; + size_t slen = strlen(vBuffer); - if (!st.empty() && st != "\n") - { - IGFD::Utils::ReplaceString(st, "\n", ""); - IGFD::Utils::ReplaceString(st, "\r", ""); - } - vBuffer[slen] = '\0'; - std::string str = std::string(vBuffer); - //if (!str.empty()) str += "\n"; - str += vStr; - if (len > str.size()) len = str.size(); + if (!st.empty() && st != "\n") + { + IGFD::Utils::ReplaceString(st, "\n", ""); + IGFD::Utils::ReplaceString(st, "\r", ""); + } + vBuffer[slen] = '\0'; + std::string str = std::string(vBuffer); + //if (!str.empty()) str += "\n"; + str += vStr; + if (len > str.size()) len = str.size(); #ifdef MSVC - strncpy_s(vBuffer, vBufferLen, str.c_str(), len); + strncpy_s(vBuffer, vBufferLen, str.c_str(), len); #else // MSVC - strncpy(vBuffer, str.c_str(), len); + strncpy(vBuffer, str.c_str(), len); #endif // MSVC - vBuffer[len] = '\0'; - } + vBuffer[len] = '\0'; + } - void IGFD::Utils::ResetBuffer(char* vBuffer) - { - vBuffer[0] = '\0'; - } + void IGFD::Utils::ResetBuffer(char* vBuffer) + { + vBuffer[0] = '\0'; + } - void IGFD::Utils::SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) - { - ResetBuffer(vBuffer); - AppendToBuffer(vBuffer, vBufferLen, vStr); - } + void IGFD::Utils::SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr) + { + ResetBuffer(vBuffer); + AppendToBuffer(vBuffer, vBufferLen, vStr); + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE INFOS ///////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE INFOS ///////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - bool IGFD::FileInfos::IsTagFound(const std::string& vTag) const - { - if (!vTag.empty()) - { - if (fileNameExt_optimized == "..") return true; + bool IGFD::FileInfos::IsTagFound(const std::string& vTag) const + { + if (!vTag.empty()) + { + if (fileNameExt_optimized == "..") return true; - return - fileNameExt_optimized.find(vTag) != std::string::npos || // first try wihtout case and accents - fileNameExt.find(vTag) != std::string::npos; // second if searched with case and accents - } + return + fileNameExt_optimized.find(vTag) != std::string::npos || // first try wihtout case and accents + fileNameExt.find(vTag) != std::string::npos; // second if searched with case and accents + } - // if tag is empty => its a special case but all is found - return true; - } + // if tag is empty => its a special case but all is found + return true; + } - ///////////////////////////////////////////////////////////////////////////////////// - //// SEARCH MANAGER ///////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// SEARCH MANAGER ///////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - void IGFD::SearchManager::Clear() - { - puSearchTag.clear(); - IGFD::Utils::ResetBuffer(puSearchBuffer); - } + void IGFD::SearchManager::Clear() + { + puSearchTag.clear(); + IGFD::Utils::ResetBuffer(puSearchBuffer); + } - void IGFD::SearchManager::DrawSearchBar(FileDialogInternal& vFileDialogInternal) - { - // search field - if (IMGUI_BUTTON(resetButtonString "##BtnImGuiFileDialogSearchField")) - { - Clear(); - vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonResetSearchString); - ImGui::SameLine(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - bool edited = ImGui::InputTextWithHint("##InputImGuiFileDialogSearchField", searchString, puSearchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); - if (ImGui::GetItemID() == ImGui::GetActiveID()) - puSearchInputIsActive = true; - ImGui::PopItemWidth(); - if (edited) - { - puSearchTag = puSearchBuffer; - vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); - } - } + void IGFD::SearchManager::DrawSearchBar(FileDialogInternal& vFileDialogInternal) + { + // search field + if (IMGUI_BUTTON(resetButtonString "##BtnImGuiFileDialogSearchField")) + { + Clear(); + vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonResetSearchString); + ImGui::SameLine(); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + bool edited = ImGui::InputTextWithHint("##InputImGuiFileDialogSearchField", searchString, puSearchBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + if (ImGui::GetItemID() == ImGui::GetActiveID()) + puSearchInputIsActive = true; + ImGui::PopItemWidth(); + if (edited) + { + puSearchTag = puSearchBuffer; + vFileDialogInternal.puFileManager.ApplyFilteringOnFileList(vFileDialogInternal); + } + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILTER INFOS /////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILTER INFOS /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - void IGFD::FilterManager::FilterInfos::clear() - { - filter.clear(); - collectionfilters.clear(); - } + void IGFD::FilterManager::FilterInfos::clear() + { + filter.clear(); + collectionfilters.clear(); + } - bool IGFD::FilterManager::FilterInfos::empty() const - { - return filter.empty() && collectionfilters.empty(); - } + bool IGFD::FilterManager::FilterInfos::empty() const + { + return filter.empty() && collectionfilters.empty(); + } - bool IGFD::FilterManager::FilterInfos::exist(const std::string& vFilter) const - { - return filter == vFilter || (collectionfilters.find(vFilter) != collectionfilters.end()); - } + bool IGFD::FilterManager::FilterInfos::exist(const std::string& vFilter) const + { + return filter == vFilter || (collectionfilters.find(vFilter) != collectionfilters.end()); + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILTER MANAGER ///////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILTER MANAGER ///////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - void IGFD::FilterManager::ParseFilters(const char* vFilters) - { - prParsedFilters.clear(); + void IGFD::FilterManager::ParseFilters(const char* vFilters) + { + prParsedFilters.clear(); - if (vFilters) - puDLGFilters = vFilters; // file mode - else - puDLGFilters.clear(); // directory mode + if (vFilters) + puDLGFilters = vFilters; // file mode + else + puDLGFilters.clear(); // directory mode - if (!puDLGFilters.empty()) - { - // ".*,.cpp,.h,.hpp" - // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" + if (!puDLGFilters.empty()) + { + // ".*,.cpp,.h,.hpp" + // "Source files{.cpp,.h,.hpp},Image files{.png,.gif,.jpg,.jpeg},.md" - bool currentFilterFound = false; + bool currentFilterFound = false; - size_t nan = std::string::npos; - size_t p = 0, lp = 0; - while ((p = puDLGFilters.find_first_of("{,", p)) != nan) - { - FilterInfos infos; + size_t nan = std::string::npos; + size_t p = 0, lp = 0; + while ((p = puDLGFilters.find_first_of("{,", p)) != nan) + { + FilterInfos infos; - if (puDLGFilters[p] == '{') // { - { - infos.filter = puDLGFilters.substr(lp, p - lp); - p++; - lp = puDLGFilters.find('}', p); - if (lp != nan) - { - std::string fs = puDLGFilters.substr(p, lp - p); - auto arr = IGFD::Utils::SplitStringToVector(fs, ',', false); - for (auto a : arr) - { - infos.collectionfilters.emplace(a); - } - } - p = lp + 1; - } - else // , - { - infos.filter = puDLGFilters.substr(lp, p - lp); - p++; - } + if (puDLGFilters[p] == '{') // { + { + infos.filter = puDLGFilters.substr(lp, p - lp); + p++; + lp = puDLGFilters.find('}', p); + if (lp != nan) + { + std::string fs = puDLGFilters.substr(p, lp - p); + auto arr = IGFD::Utils::SplitStringToVector(fs, ',', false); + for (auto a : arr) + { + infos.collectionfilters.emplace(a); + } + } + p = lp + 1; + } + else // , + { + infos.filter = puDLGFilters.substr(lp, p - lp); + p++; + } - if (!currentFilterFound && prSelectedFilter.filter == infos.filter) - { - currentFilterFound = true; - prSelectedFilter = infos; - } + if (!currentFilterFound && prSelectedFilter.filter == infos.filter) + { + currentFilterFound = true; + prSelectedFilter = infos; + } - lp = p; - if (!infos.empty()) - prParsedFilters.emplace_back(infos); - } + lp = p; + if (!infos.empty()) + prParsedFilters.emplace_back(infos); + } - std::string token = puDLGFilters.substr(lp); - if (!token.empty()) - { - FilterInfos infos; - infos.filter = std::move(token); - prParsedFilters.emplace_back(infos); - } + std::string token = puDLGFilters.substr(lp); + if (!token.empty()) + { + FilterInfos infos; + infos.filter = std::move(token); + prParsedFilters.emplace_back(infos); + } - if (!currentFilterFound) - if (!prParsedFilters.empty()) - prSelectedFilter = *prParsedFilters.begin(); - } - } + if (!currentFilterFound) + if (!prParsedFilters.empty()) + prSelectedFilter = *prParsedFilters.begin(); + } + } - void IGFD::FilterManager::SetSelectedFilterWithExt(const std::string& vFilter) - { - if (!prParsedFilters.empty()) - { - if (!vFilter.empty()) - { - // std::map - for (const auto& infos : prParsedFilters) - { - if (vFilter == infos.filter) - { - prSelectedFilter = infos; - } - else - { - // maybe this ext is in an extention so we will - // explore the collections is they are existing - for (const auto& filter : infos.collectionfilters) - { - if (vFilter == filter) - { - prSelectedFilter = infos; - } - } - } - } - } + void IGFD::FilterManager::SetSelectedFilterWithExt(const std::string& vFilter) + { + if (!prParsedFilters.empty()) + { + if (!vFilter.empty()) + { + // std::map + for (const auto& infos : prParsedFilters) + { + if (vFilter == infos.filter) + { + prSelectedFilter = infos; + } + else + { + // maybe this ext is in an extention so we will + // explore the collections is they are existing + for (const auto& filter : infos.collectionfilters) + { + if (vFilter == filter) + { + prSelectedFilter = infos; + } + } + } + } + } - if (prSelectedFilter.empty()) - prSelectedFilter = *prParsedFilters.begin(); - } - } + if (prSelectedFilter.empty()) + prSelectedFilter = *prParsedFilters.begin(); + } + } - void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) - { - std::string _criteria; - if (vCriteria) - _criteria = std::string(vCriteria); - prFilesStyle[vFlags][_criteria] = std::make_shared(vInfos); - prFilesStyle[vFlags][_criteria]->flags = vFlags; - } + void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) + { + std::string _criteria; + if (vCriteria) + _criteria = std::string(vCriteria); + prFilesStyle[vFlags][_criteria] = std::make_shared(vInfos); + prFilesStyle[vFlags][_criteria]->flags = vFlags; + } - // will be called internally - // will not been exposed to IGFD API - bool IGFD::FilterManager::prFillFileStyle(std::shared_ptr vFileInfos) const - { - if (vFileInfos.use_count() && !prFilesStyle.empty()) - { - for (const auto& _flag : prFilesStyle) - { - for (const auto& _file : _flag.second) - { - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') - { - if (_file.first.empty()) // for all dirs - { - vFileInfos->fileStyle = _file.second; - } - else if (_file.first == vFileInfos->fileNameExt) // for dirs who are equal to style criteria - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') - { - if (_file.first.empty()) // for all files - { - vFileInfos->fileStyle = _file.second; - } - else if (_file.first == vFileInfos->fileNameExt) // for files who are equal to style criteria - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') - { - if (_file.first.empty()) // for all links - { - vFileInfos->fileStyle = _file.second; - } - else if (_file.first == vFileInfos->fileNameExt) // for links who are equal to style criteria - { - vFileInfos->fileStyle = _file.second; - } - } + // will be called internally + // will not been exposed to IGFD API + bool IGFD::FilterManager::prFillFileStyle(std::shared_ptr vFileInfos) const + { + if (vFileInfos.use_count() && !prFilesStyle.empty()) + { + for (const auto& _flag : prFilesStyle) + { + for (const auto& _file : _flag.second) + { + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first.empty()) // for all dirs + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for dirs who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first.empty()) // for all files + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for files who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first.empty()) // for all links + { + vFileInfos->fileStyle = _file.second; + } + else if (_file.first == vFileInfos->fileNameExt) // for links who are equal to style criteria + { + vFileInfos->fileStyle = _file.second; + } + } - if (_flag.first & IGFD_FileStyleByExtention) - { - if (_file.first == vFileInfos->fileExt) - { - vFileInfos->fileStyle = _file.second; - } + if (_flag.first & IGFD_FileStyleByExtention) + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } - // can make sense for some dirs like the hidden by ex ".git" - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') - { - if (_file.first == vFileInfos->fileExt) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') - { - if (_file.first == vFileInfos->fileExt) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') - { - if (_file.first == vFileInfos->fileExt) - { - vFileInfos->fileStyle = _file.second; - } - } - } - if (_flag.first & IGFD_FileStyleByFullName) - { - if (_file.first == vFileInfos->fileNameExt) - { - vFileInfos->fileStyle = _file.second; - } + // can make sense for some dirs like the hidden by ex ".git" + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first == vFileInfos->fileExt) + { + vFileInfos->fileStyle = _file.second; + } + } + } + if (_flag.first & IGFD_FileStyleByFullName) + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') - { - if (_file.first == vFileInfos->fileNameExt) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') - { - if (_file.first == vFileInfos->fileNameExt) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') - { - if (_file.first == vFileInfos->fileNameExt) - { - vFileInfos->fileStyle = _file.second; - } - } - } - if (_flag.first & IGFD_FileStyleByContainedInFullName) - { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) - { - vFileInfos->fileStyle = _file.second; - } + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (_file.first == vFileInfos->fileNameExt) + { + vFileInfos->fileStyle = _file.second; + } + } + } + if (_flag.first & IGFD_FileStyleByContainedInFullName) + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } - if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') - { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') - { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) - { - vFileInfos->fileStyle = _file.second; - } - } - else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') - { - if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) - { - vFileInfos->fileStyle = _file.second; - } - } - } + if (_flag.first & IGFD_FileStyleByTypeDir && vFileInfos->fileType == 'd') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeFile && vFileInfos->fileType == 'f') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + else if (_flag.first & IGFD_FileStyleByTypeLink && vFileInfos->fileType == 'l') + { + if (vFileInfos->fileNameExt.find(_file.first) != std::string::npos) + { + vFileInfos->fileStyle = _file.second; + } + } + } - if (vFileInfos->fileStyle.use_count()) - return true; - } - } - } + if (vFileInfos->fileStyle.use_count()) + return true; + } + } + } - return false; - } + return false; + } - void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) - { - std::string _criteria; - if (vCriteria) - _criteria = std::string(vCriteria); - prFilesStyle[vFlags][_criteria] = std::make_shared(vColor, vIcon, vFont); - prFilesStyle[vFlags][_criteria]->flags = vFlags; - } + void IGFD::FilterManager::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + { + std::string _criteria; + if (vCriteria) + _criteria = std::string(vCriteria); + prFilesStyle[vFlags][_criteria] = std::make_shared(vColor, vIcon, vFont); + prFilesStyle[vFlags][_criteria]->flags = vFlags; + } - // todo : to refactor this fucking function - bool IGFD::FilterManager::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) - { - if (vOutColor) - { - if (!prFilesStyle.empty()) - { - if (prFilesStyle.find(vFlags) != prFilesStyle.end()) // found - { - if (vFlags & IGFD_FileStyleByContainedInFullName) - { - // search for vCriteria who are containing the criteria - for (const auto& _file : prFilesStyle.at(vFlags)) - { - if (vCriteria.find(_file.first) != std::string::npos) - { - if (_file.second.use_count()) - { - *vOutColor = _file.second->color; - if (vOutIcon) - *vOutIcon = _file.second->icon; - if (vOutFont) - *vOutFont = _file.second->font; - return true; - } - } - } - } - else - { - if (prFilesStyle.at(vFlags).find(vCriteria) != prFilesStyle.at(vFlags).end()) // found - { - *vOutColor = prFilesStyle[vFlags][vCriteria]->color; - if (vOutIcon) - *vOutIcon = prFilesStyle[vFlags][vCriteria]->icon; - if (vOutFont) - *vOutFont = prFilesStyle[vFlags][vCriteria]->font; - return true; - } - } - } - else - { - // search for flag composition - for (const auto& _flag : prFilesStyle) - { - if (_flag.first & vFlags) - { - if (_flag.first & IGFD_FileStyleByContainedInFullName) - { - // search for vCriteria who are containing the criteria - for (const auto& _file : prFilesStyle.at(_flag.first)) - { - if (vCriteria.find(_file.first) != std::string::npos) - { - if (_file.second.use_count()) - { - *vOutColor = _file.second->color; - if (vOutIcon) - *vOutIcon = _file.second->icon; - if (vOutFont) - *vOutFont = _file.second->font; - return true; - } - } - } - } - else - { - if (prFilesStyle.at(_flag.first).find(vCriteria) != prFilesStyle.at(_flag.first).end()) // found - { - *vOutColor = prFilesStyle[_flag.first][vCriteria]->color; - if (vOutIcon) - *vOutIcon = prFilesStyle[_flag.first][vCriteria]->icon; - if (vOutFont) - *vOutFont = prFilesStyle[_flag.first][vCriteria]->font; - return true; - } - } - } - } - } - } - } - return false; - } + // todo : to refactor this fucking function + bool IGFD::FilterManager::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) + { + if (vOutColor) + { + if (!prFilesStyle.empty()) + { + if (prFilesStyle.find(vFlags) != prFilesStyle.end()) // found + { + if (vFlags & IGFD_FileStyleByContainedInFullName) + { + // search for vCriteria who are containing the criteria + for (const auto& _file : prFilesStyle.at(vFlags)) + { + if (vCriteria.find(_file.first) != std::string::npos) + { + if (_file.second.use_count()) + { + *vOutColor = _file.second->color; + if (vOutIcon) + *vOutIcon = _file.second->icon; + if (vOutFont) + *vOutFont = _file.second->font; + return true; + } + } + } + } + else + { + if (prFilesStyle.at(vFlags).find(vCriteria) != prFilesStyle.at(vFlags).end()) // found + { + *vOutColor = prFilesStyle[vFlags][vCriteria]->color; + if (vOutIcon) + *vOutIcon = prFilesStyle[vFlags][vCriteria]->icon; + if (vOutFont) + *vOutFont = prFilesStyle[vFlags][vCriteria]->font; + return true; + } + } + } + else + { + // search for flag composition + for (const auto& _flag : prFilesStyle) + { + if (_flag.first & vFlags) + { + if (_flag.first & IGFD_FileStyleByContainedInFullName) + { + // search for vCriteria who are containing the criteria + for (const auto& _file : prFilesStyle.at(_flag.first)) + { + if (vCriteria.find(_file.first) != std::string::npos) + { + if (_file.second.use_count()) + { + *vOutColor = _file.second->color; + if (vOutIcon) + *vOutIcon = _file.second->icon; + if (vOutFont) + *vOutFont = _file.second->font; + return true; + } + } + } + } + else + { + if (prFilesStyle.at(_flag.first).find(vCriteria) != prFilesStyle.at(_flag.first).end()) // found + { + *vOutColor = prFilesStyle[_flag.first][vCriteria]->color; + if (vOutIcon) + *vOutIcon = prFilesStyle[_flag.first][vCriteria]->icon; + if (vOutFont) + *vOutFont = prFilesStyle[_flag.first][vCriteria]->font; + return true; + } + } + } + } + } + } + } + return false; + } - void IGFD::FilterManager::ClearFilesStyle() - { - prFilesStyle.clear(); - } - - bool IGFD::FilterManager::IsCoveredByFilters(const std::string& vTag) const - { - if (!puDLGFilters.empty() && !prSelectedFilter.empty()) - { - // check if current file extention is covered by current filter - // we do that here, for avoid doing that during filelist display - // for better fps - if (prSelectedFilter.exist(vTag) || prSelectedFilter.filter == ".*") - { - return true; - } - } + void IGFD::FilterManager::ClearFilesStyle() + { + prFilesStyle.clear(); + } + + bool IGFD::FilterManager::IsCoveredByFilters(const std::string& vTag) const + { + if (!puDLGFilters.empty() && !prSelectedFilter.empty()) + { + // check if current file extention is covered by current filter + // we do that here, for avoid doing that during filelist display + // for better fps + if (prSelectedFilter.exist(vTag) || prSelectedFilter.filter == ".*") + { + return true; + } + } - return false; - } + return false; + } - bool IGFD::FilterManager::DrawFilterComboBox(FileDialogInternal& vFileDialogInternal) - { - // combobox of filters - if (!puDLGFilters.empty()) - { - ImGui::SameLine(); + bool IGFD::FilterManager::DrawFilterComboBox(FileDialogInternal& vFileDialogInternal) + { + // combobox of filters + if (!puDLGFilters.empty()) + { + ImGui::SameLine(); - bool needToApllyNewFilter = false; + bool needToApllyNewFilter = false; - ImGui::PushItemWidth(FILTER_COMBO_WIDTH*FileDialog::Instance()->DpiScale); - if (ImGui::BeginCombo("##Filters", prSelectedFilter.filter.c_str(), ImGuiComboFlags_None)) - { - intptr_t i = 0; - for (const auto& filter : prParsedFilters) - { - const bool item_selected = (filter.filter == prSelectedFilter.filter); - ImGui::PushID((void*)(intptr_t)i++); - if (ImGui::Selectable(filter.filter.c_str(), item_selected)) - { - prSelectedFilter = filter; - needToApllyNewFilter = true; - } - ImGui::PopID(); - } + ImGui::PushItemWidth(FILTER_COMBO_WIDTH*FileDialog::Instance()->DpiScale); + if (ImGui::BeginCombo("##Filters", prSelectedFilter.filter.c_str(), ImGuiComboFlags_None)) + { + intptr_t i = 0; + for (const auto& filter : prParsedFilters) + { + const bool item_selected = (filter.filter == prSelectedFilter.filter); + ImGui::PushID((void*)(intptr_t)i++); + if (ImGui::Selectable(filter.filter.c_str(), item_selected)) + { + prSelectedFilter = filter; + needToApllyNewFilter = true; + } + ImGui::PopID(); + } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); - if (needToApllyNewFilter) - { - vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); - } + if (needToApllyNewFilter) + { + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + } - return needToApllyNewFilter; - } + return needToApllyNewFilter; + } - return false; - } + return false; + } - IGFD::FilterManager::FilterInfos IGFD::FilterManager::GetSelectedFilter() - { - return prSelectedFilter; - } + IGFD::FilterManager::FilterInfos IGFD::FilterManager::GetSelectedFilter() + { + return prSelectedFilter; + } - std::string IGFD::FilterManager::ReplaceExtentionWithCurrentFilter(const std::string& vFile) const - { - auto result = vFile; + std::string IGFD::FilterManager::ReplaceExtentionWithCurrentFilter(const std::string& vFile) const + { + auto result = vFile; - if (!result.empty()) - { - // if not a collection we can replace the filter by the extention we want - if (prSelectedFilter.collectionfilters.empty()) - { - size_t lastPoint = vFile.find_last_of('.'); - if (lastPoint != std::string::npos) - { - result = result.substr(0, lastPoint); - } + if (!result.empty()) + { + // if not a collection we can replace the filter by the extention we want + if (prSelectedFilter.collectionfilters.empty()) + { + size_t lastPoint = vFile.find_last_of('.'); + if (lastPoint != std::string::npos) + { + result = result.substr(0, lastPoint); + } - result += prSelectedFilter.filter; - } - } + result += prSelectedFilter.filter; + } + } - return result; - } - - void IGFD::FilterManager::SetDefaultFilterIfNotDefined() - { - if (prSelectedFilter.empty() && // no filter selected - !prParsedFilters.empty()) // filter exist - prSelectedFilter = *prParsedFilters.begin(); // we take the first filter - } + return result; + } + + void IGFD::FilterManager::SetDefaultFilterIfNotDefined() + { + if (prSelectedFilter.empty() && // no filter selected + !prParsedFilters.empty()) // filter exist + prSelectedFilter = *prParsedFilters.begin(); // we take the first filter + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE MANAGER /////////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE MANAGER /////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - IGFD::FileManager::FileManager() - { - puFsRoot = std::string(1u, PATH_SEP); + IGFD::FileManager::FileManager() + { + puFsRoot = std::string(1u, PATH_SEP); for (int i=0; i<4; i++) { puSortingDirection[i]=true; } - } + } - void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) - { - puShowDrives = false; - ClearComposer(); - ClearFileLists(); - if (puDLGDirectoryMode) // directory mode - SetDefaultFileName("."); - else - SetDefaultFileName(""); + void IGFD::FileManager::OpenCurrentPath(const FileDialogInternal& vFileDialogInternal) + { + puShowDrives = false; + ClearComposer(); + ClearFileLists(); + if (puDLGDirectoryMode) // directory mode + SetDefaultFileName("."); + else + SetDefaultFileName(""); logV("IGFD: OpenCurrentPath()"); - ScanDir(vFileDialogInternal, GetCurrentPath()); - } + ScanDir(vFileDialogInternal, GetCurrentPath()); + } - void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder) - { + void IGFD::FileManager::SortFields(const FileDialogInternal& vFileDialogInternal, const SortingFieldEnum& vSortingField, const bool vCanChangeOrder) + { logV("IGFD: SortFields()"); - if (vSortingField != SortingFieldEnum::FIELD_NONE) - { - puHeaderFileName = tableHeaderFileNameString; - puHeaderFileType = tableHeaderFileTypeString; - puHeaderFileSize = tableHeaderFileSizeString; - puHeaderFileDate = tableHeaderFileDateString; + if (vSortingField != SortingFieldEnum::FIELD_NONE) + { + puHeaderFileName = tableHeaderFileNameString; + puHeaderFileType = tableHeaderFileTypeString; + puHeaderFileSize = tableHeaderFileSizeString; + puHeaderFileDate = tableHeaderFileDateString; #ifdef USE_THUMBNAILS - puHeaderFileThumbnails = tableHeaderFileThumbnailsString; + puHeaderFileThumbnails = tableHeaderFileThumbnailsString; #endif // #ifdef USE_THUMBNAILS - } else { + } else { logV("IGFD: sorting by NONE!"); } @@ -1175,2790 +1175,2790 @@ namespace IGFD logV("IGFD: with an empty file list?"); } - if (vSortingField == SortingFieldEnum::FIELD_FILENAME) - { + if (vSortingField == SortingFieldEnum::FIELD_FILENAME) + { logV("IGFD: sorting by name"); - if (vCanChangeOrder && puSortingField == vSortingField) { + if (vCanChangeOrder && puSortingField == vSortingField) { //printf("Change the sorting\n"); - puSortingDirection[0] = !puSortingDirection[0]; + puSortingDirection[0] = !puSortingDirection[0]; } - if (puSortingDirection[0]) - { + if (puSortingDirection[0]) + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileName = tableHeaderDescendingIcon + puHeaderFileName; + puHeaderFileName = tableHeaderDescendingIcon + puHeaderFileName; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - // this code fail in c:\\Users with the link "All users". got a invalid comparator - /* - // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 - // strict ordering for file/directory types beginning in '.' - // common on Linux platforms - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') - return false; - if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') - return true; - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') - { - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case - } - */ - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case - }); - } - else - { + // this code fail in c:\\Users with the link "All users". got a invalid comparator + /* + // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 + // strict ordering for file/directory types beginning in '.' + // common on Linux platforms + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + return false; + if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + return true; + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + { + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + } + */ + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) < 0); // sort in insensitive case + }); + } + else + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileName = tableHeaderAscendingIcon + puHeaderFileName; + puHeaderFileName = tableHeaderAscendingIcon + puHeaderFileName; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - // this code fail in c:\\Users with the link "All users". got a invalid comparator - /* - // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 - // strict ordering for file/directory types beginning in '.' - // common on Linux platforms - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') - return false; - if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') - return true; - if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') - { - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case - } - */ - return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case - }); - } - } - else if (vSortingField == SortingFieldEnum::FIELD_TYPE) - { + // this code fail in c:\\Users with the link "All users". got a invalid comparator + /* + // use code from https://github.com/jackm97/ImGuiFileDialog/commit/bf40515f5a1de3043e60562dc1a494ee7ecd3571 + // strict ordering for file/directory types beginning in '.' + // common on Linux platforms + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] != '.') + return false; + if (a->fileNameExt[0] != '.' && b->fileNameExt[0] == '.') + return true; + if (a->fileNameExt[0] == '.' && b->fileNameExt[0] == '.') + { + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + } + */ + return (stricmp(a->fileNameExt.c_str(), b->fileNameExt.c_str()) > 0); // sort in insensitive case + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_TYPE) + { logV("IGFD: sorting by type"); - if (vCanChangeOrder && puSortingField == vSortingField) - puSortingDirection[1] = !puSortingDirection[1]; + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[1] = !puSortingDirection[1]; - if (puSortingDirection[1]) - { + if (puSortingDirection[1]) + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileType = tableHeaderDescendingIcon + puHeaderFileType; + puHeaderFileType = tableHeaderDescendingIcon + puHeaderFileType; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { - if (!a.use_count() || !b.use_count()) - return false; + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileExt < b->fileExt); // else - }); - } - else - { + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileExt < b->fileExt); // else + }); + } + else + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileType = tableHeaderAscendingIcon + puHeaderFileType; + puHeaderFileType = tableHeaderAscendingIcon + puHeaderFileType; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileExt > b->fileExt); // else - }); - } - } - else if (vSortingField == SortingFieldEnum::FIELD_SIZE) - { + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileExt > b->fileExt); // else + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_SIZE) + { logV("IGFD: sorting by size"); - if (vCanChangeOrder && puSortingField == vSortingField) - puSortingDirection[2] = !puSortingDirection[2]; + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[2] = !puSortingDirection[2]; - if (puSortingDirection[2]) - { + if (puSortingDirection[2]) + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileSize = tableHeaderDescendingIcon + puHeaderFileSize; + puHeaderFileSize = tableHeaderDescendingIcon + puHeaderFileSize; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileSize < b->fileSize); // else - }); - } - else - { + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileSize < b->fileSize); // else + }); + } + else + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileSize = tableHeaderAscendingIcon + puHeaderFileSize; + puHeaderFileSize = tableHeaderAscendingIcon + puHeaderFileSize; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileSize > b->fileSize); // else - }); - } - } - else if (vSortingField == SortingFieldEnum::FIELD_DATE) - { + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileSize > b->fileSize); // else + }); + } + } + else if (vSortingField == SortingFieldEnum::FIELD_DATE) + { logV("IGFD: sorting by date"); - if (vCanChangeOrder && puSortingField == vSortingField) - puSortingDirection[3] = !puSortingDirection[3]; + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[3] = !puSortingDirection[3]; - if (puSortingDirection[3]) - { + if (puSortingDirection[3]) + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileDate = tableHeaderDescendingIcon + puHeaderFileDate; + puHeaderFileDate = tableHeaderDescendingIcon + puHeaderFileDate; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - return (a->fileModifDate < b->fileModifDate); // else - }); - } - else - { + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + return (a->fileModifDate < b->fileModifDate); // else + }); + } + else + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileDate = tableHeaderAscendingIcon + puHeaderFileDate; + puHeaderFileDate = tableHeaderAscendingIcon + puHeaderFileDate; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { if (a==NULL || b==NULL) return false; - if (!a.use_count() || !b.use_count()) - return false; + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - return (a->fileModifDate > b->fileModifDate); // else - }); - } - } + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + return (a->fileModifDate > b->fileModifDate); // else + }); + } + } #ifdef USE_THUMBNAILS - else if (vSortingField == SortingFieldEnum::FIELD_THUMBNAILS) - { - if (vCanChangeOrder && puSortingField == vSortingField) - puSortingDirection[4] = !puSortingDirection[4]; + else if (vSortingField == SortingFieldEnum::FIELD_THUMBNAILS) + { + if (vCanChangeOrder && puSortingField == vSortingField) + puSortingDirection[4] = !puSortingDirection[4]; - // we will compare thumbnails by : - // 1) width - // 2) height + // we will compare thumbnails by : + // 1) width + // 2) height - if (puSortingDirection[4]) - { + if (puSortingDirection[4]) + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileThumbnails = tableHeaderDescendingIcon + puHeaderFileThumbnails; + puHeaderFileThumbnails = tableHeaderDescendingIcon + puHeaderFileThumbnails; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { - if (!a.use_count() || !b.use_count()) - return false; + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first - if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) - return (a->thumbnailInfo.textureHeight < b->thumbnailInfo.textureHeight); - return (a->thumbnailInfo.textureWidth < b->thumbnailInfo.textureWidth); - }); - } + if (a->fileType != b->fileType) return (a->fileType == 'd'); // directory in first + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) + return (a->thumbnailInfo.textureHeight < b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth < b->thumbnailInfo.textureWidth); + }); + } - else - { + else + { #ifdef USE_CUSTOM_SORTING_ICON - puHeaderFileThumbnails = tableHeaderAscendingIcon + puHeaderFileThumbnails; + puHeaderFileThumbnails = tableHeaderAscendingIcon + puHeaderFileThumbnails; #endif // USE_CUSTOM_SORTING_ICON - std::sort(prFileList.begin(), prFileList.end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool - { - if (!a.use_count() || !b.use_count()) - return false; + std::sort(prFileList.begin(), prFileList.end(), + [](const std::shared_ptr& a, const std::shared_ptr& b) -> bool + { + if (!a.use_count() || !b.use_count()) + return false; - if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last - if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) - return (a->thumbnailInfo.textureHeight > b->thumbnailInfo.textureHeight); - return (a->thumbnailInfo.textureWidth > b->thumbnailInfo.textureWidth); - }); - } - } + if (a->fileType != b->fileType) return (a->fileType != 'd'); // directory in last + if (a->thumbnailInfo.textureWidth == b->thumbnailInfo.textureWidth) + return (a->thumbnailInfo.textureHeight > b->thumbnailInfo.textureHeight); + return (a->thumbnailInfo.textureWidth > b->thumbnailInfo.textureWidth); + }); + } + } #endif // USE_THUMBNAILS - if (vSortingField != SortingFieldEnum::FIELD_NONE) - { - puSortingField = vSortingField; - } + if (vSortingField != SortingFieldEnum::FIELD_NONE) + { + puSortingField = vSortingField; + } logV("IGFD: applying filtering on file list"); - ApplyFilteringOnFileList(vFileDialogInternal); - } + ApplyFilteringOnFileList(vFileDialogInternal); + } - void IGFD::FileManager::ClearFileLists() - { - prFilteredFileList.clear(); - prFileList.clear(); - } + void IGFD::FileManager::ClearFileLists() + { + prFilteredFileList.clear(); + prFileList.clear(); + } - std::string IGFD::FileManager::prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt) - { - auto fileNameExt = vFileNameExt; - // convert to lower case - for (char& c : fileNameExt) - c = (char)std::tolower(c); - return fileNameExt; - } + std::string IGFD::FileManager::prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt) + { + auto fileNameExt = vFileNameExt; + // convert to lower case + for (char& c : fileNameExt) + c = (char)std::tolower(c); + return fileNameExt; + } - void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType) - { - auto infos = std::make_shared(); + void IGFD::FileManager::AddFile(const FileDialogInternal& vFileDialogInternal, const std::string& vPath, const std::string& vFileName, const char& vFileType) + { + auto infos = std::make_shared(); - infos->filePath = vPath; - infos->fileNameExt = vFileName; - infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt); - infos->fileType = vFileType; + infos->filePath = vPath; + infos->fileNameExt = vFileName; + infos->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(infos->fileNameExt); + infos->fileType = vFileType; - if (infos->fileNameExt.empty() || ((infos->fileNameExt == "." || infos->fileNameExt == "..") && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807 - if (infos->fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') // dont show hidden files - if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos->fileNameExt != ".")) // except "." if in directory mode //-V728 - return; + if (infos->fileNameExt.empty() || ((infos->fileNameExt == "." || infos->fileNameExt == "..") && !vFileDialogInternal.puFilterManager.puDLGFilters.empty())) return; // filename empty or filename is the current dir '.' //-V807 + if (infos->fileNameExt != ".." && (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DontShowHiddenFiles) && infos->fileNameExt[0] == '.') // dont show hidden files + if (!vFileDialogInternal.puFilterManager.puDLGFilters.empty() || (vFileDialogInternal.puFilterManager.puDLGFilters.empty() && infos->fileNameExt != ".")) // except "." if in directory mode //-V728 + return; - if (infos->fileType == 'f' || - infos->fileType == 'l') // link can have the same extention of a file - { - size_t lpt = infos->fileNameExt.find_last_of('.'); - if (lpt != std::string::npos) - { - infos->fileExt = infos->fileNameExt.substr(lpt); - } + if (infos->fileType == 'f' || + infos->fileType == 'l') // link can have the same extention of a file + { + size_t lpt = infos->fileNameExt.find_last_of('.'); + if (lpt != std::string::npos) + { + infos->fileExt = infos->fileNameExt.substr(lpt); + } for (char& i: infos->fileExt) { if (i>='A' && i<='Z') i+='a'-'A'; } - if (!vFileDialogInternal.puFilterManager.IsCoveredByFilters(infos->fileExt)) - { - return; - } - } + if (!vFileDialogInternal.puFilterManager.IsCoveredByFilters(infos->fileExt)) + { + return; + } + } - vFileDialogInternal.puFilterManager.prFillFileStyle(infos); + vFileDialogInternal.puFilterManager.prFillFileStyle(infos); - prCompleteFileInfos(infos); - prFileList.push_back(infos); - } + prCompleteFileInfos(infos); + prFileList.push_back(infos); + } - void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) - { - std::string path = vPath; + void IGFD::FileManager::ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath) + { + std::string path = vPath; logV("IGFD: ScanDir(%s)",vPath); - if (prCurrentPathDecomposition.empty()) - { + if (prCurrentPathDecomposition.empty()) + { logV("IGFD: the current path decomposition is empty. setting."); - SetCurrentDir(path); - } + SetCurrentDir(path); + } - if (!prCurrentPathDecomposition.empty()) - { + if (!prCurrentPathDecomposition.empty()) + { logV("IGFD: the current path decomposition is not empty. trying."); #ifdef WIN32 - if (path == puFsRoot) - path += std::string(1u, PATH_SEP); + if (path == puFsRoot) + path += std::string(1u, PATH_SEP); #endif // WIN32 - ClearFileLists(); + ClearFileLists(); - struct dirent** files = nullptr; - int n = scandir(path.c_str(), &files, nullptr, inAlphaSort); + struct dirent** files = nullptr; + int n = scandir(path.c_str(), &files, nullptr, inAlphaSort); logV("IGFD: %d entries in directory",n); - if (n>0) - { - int i; + if (n>0) + { + int i; - for (i = 0; i < n; i++) - { - struct dirent* ent = files[i]; - std::string where = path + std::string("/") + std::string(ent->d_name); - char fileType = 0; + for (i = 0; i < n; i++) + { + struct dirent* ent = files[i]; + std::string where = path + std::string("/") + std::string(ent->d_name); + char fileType = 0; #ifdef HAVE_DIRENT_TYPE - if (ent->d_type != DT_UNKNOWN) - { - switch (ent->d_type) - { - case DT_REG: - fileType = 'f'; break; - case DT_DIR: - fileType = 'd'; break; - case DT_LNK: - DIR* dirTest = opendir(where.c_str()); - if (dirTest == NULL) - { - if (errno == ENOTDIR) - { - fileType = 'f'; - } - else - { - fileType = 'l'; - } - } - else - { - fileType = 'd'; - closedir(dirTest); - } - break; - } - } - else + if (ent->d_type != DT_UNKNOWN) + { + switch (ent->d_type) + { + case DT_REG: + fileType = 'f'; break; + case DT_DIR: + fileType = 'd'; break; + case DT_LNK: + DIR* dirTest = opendir(where.c_str()); + if (dirTest == NULL) + { + if (errno == ENOTDIR) + { + fileType = 'f'; + } + else + { + fileType = 'l'; + } + } + else + { + fileType = 'd'; + closedir(dirTest); + } + break; + } + } + else #endif // HAVE_DIRENT_TYPE - { - struct stat filestat; - if (stat(where.c_str(), &filestat) == 0) - { - if (S_ISDIR(filestat.st_mode)) - { - fileType = 'd'; - } - else - { - fileType = 'f'; - } - } - } + { + struct stat filestat; + if (stat(where.c_str(), &filestat) == 0) + { + if (S_ISDIR(filestat.st_mode)) + { + fileType = 'd'; + } + else + { + fileType = 'f'; + } + } + } - auto fileNameExt = ent->d_name; + auto fileNameExt = ent->d_name; - AddFile(vFileDialogInternal, path, fileNameExt, fileType); - } + AddFile(vFileDialogInternal, path, fileNameExt, fileType); + } - for (i = 0; i < n; i++) - { - free(files[i]); - } + for (i = 0; i < n; i++) + { + free(files[i]); + } - free(files); - } else { + free(files); + } else { logV("IGFD: it's empty"); } logV("IGFD: sorting fields..."); - SortFields(vFileDialogInternal, puSortingField, false); - } else { + SortFields(vFileDialogInternal, puSortingField, false); + } else { logE("IGFD: current path decomposition is empty!"); } fileListActuallyEmpty=prFileList.empty(); - } + } - bool IGFD::FileManager::GetDrives() - { - auto drives = IGFD::Utils::GetDrivesList(); - if (!drives.empty()) - { - prCurrentPath.clear(); - prCurrentPathDecomposition.clear(); - ClearFileLists(); - for (auto& drive : drives) - { - auto info = std::make_shared(); - info->fileNameExt = drive; - info->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(drive); - info->fileType = 'd'; + bool IGFD::FileManager::GetDrives() + { + auto drives = IGFD::Utils::GetDrivesList(); + if (!drives.empty()) + { + prCurrentPath.clear(); + prCurrentPathDecomposition.clear(); + ClearFileLists(); + for (auto& drive : drives) + { + auto info = std::make_shared(); + info->fileNameExt = drive; + info->fileNameExt_optimized = prOptimizeFilenameForSearchOperations(drive); + info->fileType = 'd'; - if (!info->fileNameExt.empty()) - { - prFileList.push_back(info); - } - } - puShowDrives = true; - return true; - } - return false; - } + if (!info->fileNameExt.empty()) + { + prFileList.push_back(info); + } + } + puShowDrives = true; + return true; + } + return false; + } - bool IGFD::FileManager::IsComposerEmpty() - { - return prCurrentPathDecomposition.empty(); - } - - size_t IGFD::FileManager::GetComposerSize() - { - return prCurrentPathDecomposition.size(); - } + bool IGFD::FileManager::IsComposerEmpty() + { + return prCurrentPathDecomposition.empty(); + } + + size_t IGFD::FileManager::GetComposerSize() + { + return prCurrentPathDecomposition.size(); + } - bool IGFD::FileManager::IsFileListEmpty() - { - return prFileList.empty(); - } + bool IGFD::FileManager::IsFileListEmpty() + { + return prFileList.empty(); + } - size_t IGFD::FileManager::GetFullFileListSize() - { - return prFileList.size(); - } + size_t IGFD::FileManager::GetFullFileListSize() + { + return prFileList.size(); + } - std::shared_ptr IGFD::FileManager::GetFullFileAt(size_t vIdx) - { - if (vIdx < prFileList.size()) - return prFileList[vIdx]; - return nullptr; - } + std::shared_ptr IGFD::FileManager::GetFullFileAt(size_t vIdx) + { + if (vIdx < prFileList.size()) + return prFileList[vIdx]; + return nullptr; + } - bool IGFD::FileManager::IsFilteredListEmpty() - { - return prFilteredFileList.empty(); - } + bool IGFD::FileManager::IsFilteredListEmpty() + { + return prFilteredFileList.empty(); + } - size_t IGFD::FileManager::GetFilteredListSize() - { - return prFilteredFileList.size(); - } + size_t IGFD::FileManager::GetFilteredListSize() + { + return prFilteredFileList.size(); + } - std::shared_ptr IGFD::FileManager::GetFilteredFileAt(size_t vIdx) - { - if (vIdx < prFilteredFileList.size()) - return prFilteredFileList[vIdx]; - return nullptr; - } + std::shared_ptr IGFD::FileManager::GetFilteredFileAt(size_t vIdx) + { + if (vIdx < prFilteredFileList.size()) + return prFilteredFileList[vIdx]; + return nullptr; + } - bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) - { - return prSelectedFileNames.find(vFileName) != prSelectedFileNames.end(); - } + bool IGFD::FileManager::IsFileNameSelected(const std::string& vFileName) + { + return prSelectedFileNames.find(vFileName) != prSelectedFileNames.end(); + } - std::string IGFD::FileManager::GetBack() - { - return prCurrentPathDecomposition.back(); - } + std::string IGFD::FileManager::GetBack() + { + return prCurrentPathDecomposition.back(); + } - void IGFD::FileManager::ClearComposer() - { - prCurrentPathDecomposition.clear(); - } + void IGFD::FileManager::ClearComposer() + { + prCurrentPathDecomposition.clear(); + } - void IGFD::FileManager::ClearAll() - { - ClearComposer(); - ClearFileLists(); - } + void IGFD::FileManager::ClearAll() + { + ClearComposer(); + ClearFileLists(); + } - void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) - { - prFilteredFileList.clear(); - for (const auto& file : prFileList) - { - if (!file.use_count()) - continue; - bool show = true; - if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) // if search tag - show = false; - if (puDLGDirectoryMode && file->fileType != 'd') // directory mode - show = false; - if (show) - prFilteredFileList.push_back(file); - } - } + void IGFD::FileManager::ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal) + { + prFilteredFileList.clear(); + for (const auto& file : prFileList) + { + if (!file.use_count()) + continue; + bool show = true; + if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) // if search tag + show = false; + if (puDLGDirectoryMode && file->fileType != 'd') // directory mode + show = false; + if (show) + prFilteredFileList.push_back(file); + } + } - std::string IGFD::FileManager::prRoundNumber(double vvalue, int n) - { - std::stringstream tmp; - tmp << std::setprecision(n) << std::fixed << vvalue; - return tmp.str(); - } + std::string IGFD::FileManager::prRoundNumber(double vvalue, int n) + { + std::stringstream tmp; + tmp << std::setprecision(n) << std::fixed << vvalue; + return tmp.str(); + } - std::string IGFD::FileManager::prFormatFileSize(size_t vByteSize) - { - if (vByteSize != 0) - { - static double lo = 1024.0; - static double ko = 1024.0 * 1024.0; - static double mo = 1024.0 * 1024.0 * 1024.0; + std::string IGFD::FileManager::prFormatFileSize(size_t vByteSize) + { + if (vByteSize != 0) + { + static double lo = 1024.0; + static double ko = 1024.0 * 1024.0; + static double mo = 1024.0 * 1024.0 * 1024.0; - auto v = (double)vByteSize; + auto v = (double)vByteSize; - if (v < lo) - return prRoundNumber(v, 0); // octet - else if (v < ko) - return prRoundNumber(v / lo, 2) + "K"; // ko - else if (v < mo) - return prRoundNumber(v / ko, 2) + "M"; // Mo - else - return prRoundNumber(v / mo, 2) + "G"; // Go - } + if (v < lo) + return prRoundNumber(v, 0); // octet + else if (v < ko) + return prRoundNumber(v / lo, 2) + "K"; // ko + else if (v < mo) + return prRoundNumber(v / ko, 2) + "M"; // Mo + else + return prRoundNumber(v / mo, 2) + "G"; // Go + } - return ""; - } + return ""; + } - void IGFD::FileManager::prCompleteFileInfos(const std::shared_ptr& vInfos) - { - if (!vInfos.use_count()) - return; + void IGFD::FileManager::prCompleteFileInfos(const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return; - if (vInfos->fileNameExt != "." && - vInfos->fileNameExt != "..") - { - // _stat struct : - //dev_t st_dev; /* ID of device containing file */ - //ino_t st_ino; /* inode number */ - //mode_t st_mode; /* protection */ - //nlink_t st_nlink; /* number of hard links */ - //uid_t st_uid; /* user ID of owner */ - //gid_t st_gid; /* group ID of owner */ - //dev_t st_rdev; /* device ID (if special file) */ - //off_t st_size; /* total size, in bytes */ - //blksize_t st_blksize; /* blocksize for file system I/O */ - //blkcnt_t st_blocks; /* number of 512B blocks allocated */ - //time_t st_atime; /* time of last access - not sure out of ntfs */ - //time_t st_mtime; /* time of last modification - not sure out of ntfs */ - //time_t st_ctime; /* time of last status change - not sure out of ntfs */ + if (vInfos->fileNameExt != "." && + vInfos->fileNameExt != "..") + { + // _stat struct : + //dev_t st_dev; /* ID of device containing file */ + //ino_t st_ino; /* inode number */ + //mode_t st_mode; /* protection */ + //nlink_t st_nlink; /* number of hard links */ + //uid_t st_uid; /* user ID of owner */ + //gid_t st_gid; /* group ID of owner */ + //dev_t st_rdev; /* device ID (if special file) */ + //off_t st_size; /* total size, in bytes */ + //blksize_t st_blksize; /* blocksize for file system I/O */ + //blkcnt_t st_blocks; /* number of 512B blocks allocated */ + //time_t st_atime; /* time of last access - not sure out of ntfs */ + //time_t st_mtime; /* time of last modification - not sure out of ntfs */ + //time_t st_ctime; /* time of last status change - not sure out of ntfs */ - std::string fpn; + std::string fpn; - if (vInfos->fileType == 'f' || vInfos->fileType == 'l' || vInfos->fileType == 'd') // file - fpn = vInfos->filePath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + if (vInfos->fileType == 'f' || vInfos->fileType == 'l' || vInfos->fileType == 'd') // file + fpn = vInfos->filePath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; - struct stat statInfos = {}; - char timebuf[100]; - int result = stat(fpn.c_str(), &statInfos); - if (result!=-1) - { - if (vInfos->fileType != 'd') - { - vInfos->fileSize = (size_t)statInfos.st_size; - vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); - } + struct stat statInfos = {}; + char timebuf[100]; + int result = stat(fpn.c_str(), &statInfos); + if (result!=-1) + { + if (vInfos->fileType != 'd') + { + vInfos->fileSize = (size_t)statInfos.st_size; + vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); + } - size_t len = 0; + size_t len = 0; #ifdef MSVC - struct tm _tm; - errno_t err = localtime_s(&_tm, &statInfos.st_mtime); - if (!err) len = strftime(timebuf, 99, DateTimeFormat, &_tm); + struct tm _tm; + errno_t err = localtime_s(&_tm, &statInfos.st_mtime); + if (!err) len = strftime(timebuf, 99, DateTimeFormat, &_tm); #else // MSVC - struct tm* _tm = localtime(&statInfos.st_mtime); - if (_tm) len = strftime(timebuf, 99, DateTimeFormat, _tm); + struct tm* _tm = localtime(&statInfos.st_mtime); + if (_tm) len = strftime(timebuf, 99, DateTimeFormat, _tm); #endif // MSVC - if (len) - { - vInfos->fileModifDate = std::string(timebuf, len); - } - } else { + if (len) + { + vInfos->fileModifDate = std::string(timebuf, len); + } + } else { vInfos->fileSize=0; vInfos->formatedFileSize = prFormatFileSize(vInfos->fileSize); vInfos->fileModifDate="???"; } - } - } + } + } - void IGFD::FileManager::prRemoveFileNameInSelection(const std::string& vFileName) - { - prSelectedFileNames.erase(vFileName); + void IGFD::FileManager::prRemoveFileNameInSelection(const std::string& vFileName) + { + prSelectedFileNames.erase(vFileName); - if (prSelectedFileNames.size() == 1) - { - snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); - } - else - { - snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); - } - } - - void IGFD::FileManager::prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName) - { - prSelectedFileNames.emplace(vFileName); + if (prSelectedFileNames.size() == 1) + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } + else + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); + } + } + + void IGFD::FileManager::prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName) + { + prSelectedFileNames.emplace(vFileName); - if (prSelectedFileNames.size() == 1) - { - snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); - } - else - { - snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); - } + if (prSelectedFileNames.size() == 1) + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%s", vFileName.c_str()); + } + else + { + snprintf(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, "%zu files Selected", prSelectedFileNames.size()); + } - if (vSetLastSelectionFileName) - prLastSelectedFileName = vFileName; - } + if (vSetLastSelectionFileName) + prLastSelectedFileName = vFileName; + } - void IGFD::FileManager::SetCurrentDir(const std::string& vPath) - { - std::string path = vPath; + void IGFD::FileManager::SetCurrentDir(const std::string& vPath) + { + std::string path = vPath; #ifdef WIN32 - if (puFsRoot == path) - path += std::string(1u, PATH_SEP); + if (puFsRoot == path) + path += std::string(1u, PATH_SEP); #endif // WIN32 - - DIR* dir = opendir(path.c_str()); - if (dir == nullptr) - { - path = "."; - dir = opendir(path.c_str()); - } + + DIR* dir = opendir(path.c_str()); + if (dir == nullptr) + { + path = "."; + dir = opendir(path.c_str()); + } - if (dir != nullptr) - { + if (dir != nullptr) + { #ifdef WIN32 - DWORD numchar = 0; - // numchar = GetFullPathNameA(path.c_str(), PATH_MAX, real_path, nullptr); - std::wstring wpath = IGFD::Utils::string_to_wstring(path); - numchar = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr); - std::wstring fpath(numchar, 0); - GetFullPathNameW(wpath.c_str(), numchar, (wchar_t*)fpath.data(), nullptr); - std::string real_path = IGFD::Utils::wstring_to_string(fpath); - if (real_path.back() == '\0') // for fix issue we can have with std::string concatenation.. if there is a \0 at end - real_path = real_path.substr(0, real_path.size() - 1U); - if (!real_path.empty()) + DWORD numchar = 0; + // numchar = GetFullPathNameA(path.c_str(), PATH_MAX, real_path, nullptr); + std::wstring wpath = IGFD::Utils::string_to_wstring(path); + numchar = GetFullPathNameW(wpath.c_str(), 0, nullptr, nullptr); + std::wstring fpath(numchar, 0); + GetFullPathNameW(wpath.c_str(), numchar, (wchar_t*)fpath.data(), nullptr); + std::string real_path = IGFD::Utils::wstring_to_string(fpath); + if (real_path.back() == '\0') // for fix issue we can have with std::string concatenation.. if there is a \0 at end + real_path = real_path.substr(0, real_path.size() - 1U); + if (!real_path.empty()) #elif defined(UNIX) // UNIX is LINUX or APPLE - char real_path[PATH_MAX]; - char* numchar = realpath(path.c_str(), real_path); - if (numchar != nullptr) + char real_path[PATH_MAX]; + char* numchar = realpath(path.c_str(), real_path); + if (numchar != nullptr) #endif // WIN32 - { - prCurrentPath = std::move(real_path); - if (prCurrentPath[prCurrentPath.size() - 1] == PATH_SEP) - { - prCurrentPath = prCurrentPath.substr(0, prCurrentPath.size() - 1); - } - IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); - prCurrentPathDecomposition = IGFD::Utils::SplitStringToVector(prCurrentPath, PATH_SEP, false); + { + prCurrentPath = std::move(real_path); + if (prCurrentPath[prCurrentPath.size() - 1] == PATH_SEP) + { + prCurrentPath = prCurrentPath.substr(0, prCurrentPath.size() - 1); + } + IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); + prCurrentPathDecomposition = IGFD::Utils::SplitStringToVector(prCurrentPath, PATH_SEP, false); #ifdef UNIX // UNIX is LINUX or APPLE - prCurrentPathDecomposition.insert(prCurrentPathDecomposition.begin(), std::string(1u, PATH_SEP)); + prCurrentPathDecomposition.insert(prCurrentPathDecomposition.begin(), std::string(1u, PATH_SEP)); #endif // UNIX - if (!prCurrentPathDecomposition.empty()) - { + if (!prCurrentPathDecomposition.empty()) + { #ifdef WIN32 - puFsRoot = prCurrentPathDecomposition[0]; + puFsRoot = prCurrentPathDecomposition[0]; #endif // WIN32 - } - } - closedir(dir); - } - } + } + } + closedir(dir); + } + } - bool IGFD::FileManager::CreateDir(const std::string& vPath) - { - bool res = false; + bool IGFD::FileManager::CreateDir(const std::string& vPath) + { + bool res = false; - if (!vPath.empty()) - { - std::string path = prCurrentPath + std::string(1u, PATH_SEP) + vPath; + if (!vPath.empty()) + { + std::string path = prCurrentPath + std::string(1u, PATH_SEP) + vPath; - res = IGFD::Utils::CreateDirectoryIfNotExist(path); - } + res = IGFD::Utils::CreateDirectoryIfNotExist(path); + } - return res; - } + return res; + } - void IGFD::FileManager::ComposeNewPath(std::vector::iterator vIter) - { - std::string res; + void IGFD::FileManager::ComposeNewPath(std::vector::iterator vIter) + { + std::string res; - while (true) - { - if (!res.empty()) - { + while (true) + { + if (!res.empty()) + { #ifdef WIN32 - res = *vIter + std::string(1u, PATH_SEP) + res; + res = *vIter + std::string(1u, PATH_SEP) + res; #elif defined(UNIX) // UNIX is LINUX or APPLE - if (*vIter == puFsRoot) - res = *vIter + res; - else - res = *vIter + PATH_SEP + res; + if (*vIter == puFsRoot) + res = *vIter + res; + else + res = *vIter + PATH_SEP + res; #endif // WIN32 - } - else - res = *vIter; + } + else + res = *vIter; - if (vIter == prCurrentPathDecomposition.begin()) - { + if (vIter == prCurrentPathDecomposition.begin()) + { #if defined(UNIX) // UNIX is LINUX or APPLE - if (res[0] != PATH_SEP) - res = PATH_SEP + res; + if (res[0] != PATH_SEP) + res = PATH_SEP + res; #endif // defined(UNIX) - break; - } + break; + } - --vIter; - } + --vIter; + } - prCurrentPath = std::move(res); - } + prCurrentPath = std::move(res); + } - bool IGFD::FileManager::SetPathOnParentDirectoryIfAny() - { - if (prCurrentPathDecomposition.size() > 1) - { - ComposeNewPath(prCurrentPathDecomposition.end() - 2); - return true; - } - return false; - } + bool IGFD::FileManager::SetPathOnParentDirectoryIfAny() + { + if (prCurrentPathDecomposition.size() > 1) + { + ComposeNewPath(prCurrentPathDecomposition.end() - 2); + return true; + } + return false; + } - std::string IGFD::FileManager::GetCurrentPath() - { - if (prCurrentPath.empty()) - prCurrentPath = "."; - return prCurrentPath; - } + std::string IGFD::FileManager::GetCurrentPath() + { + if (prCurrentPath.empty()) + prCurrentPath = "."; + return prCurrentPath; + } - void IGFD::FileManager::SetCurrentPath(const std::string& vCurrentPath) - { - if (vCurrentPath.empty()) - prCurrentPath = "."; - else - prCurrentPath = vCurrentPath; - } + void IGFD::FileManager::SetCurrentPath(const std::string& vCurrentPath) + { + if (vCurrentPath.empty()) + prCurrentPath = "."; + else + prCurrentPath = vCurrentPath; + } - bool IGFD::FileManager::IsFileExist(const std::string& vFile) - { - std::ifstream docFile(vFile, std::ios::in); - if (docFile.is_open()) - { - docFile.close(); - return true; - } - return false; - } + bool IGFD::FileManager::IsFileExist(const std::string& vFile) + { + std::ifstream docFile(vFile, std::ios::in); + if (docFile.is_open()) + { + docFile.close(); + return true; + } + return false; + } - void IGFD::FileManager::SetDefaultFileName(const std::string& vFileName) - { - puDLGDefaultFileName = vFileName; - IGFD::Utils::SetBuffer(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName); - } + void IGFD::FileManager::SetDefaultFileName(const std::string& vFileName) + { + puDLGDefaultFileName = vFileName; + IGFD::Utils::SetBuffer(puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFileName); + } - bool IGFD::FileManager::SelectDirectory(const std::shared_ptr& vInfos) - { - if (!vInfos.use_count()) - return false; + bool IGFD::FileManager::SelectDirectory(const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return false; - bool pathClick = false; + bool pathClick = false; - if (vInfos->fileNameExt == "..") - { - pathClick = SetPathOnParentDirectoryIfAny(); - } - else - { - std::string newPath; + if (vInfos->fileNameExt == "..") + { + pathClick = SetPathOnParentDirectoryIfAny(); + } + else + { + std::string newPath; - if (puShowDrives) - { - newPath = vInfos->fileNameExt + std::string(1u, PATH_SEP); - } - else - { + if (puShowDrives) + { + newPath = vInfos->fileNameExt + std::string(1u, PATH_SEP); + } + else + { #ifdef __linux__ - if (puFsRoot == prCurrentPath) - newPath = prCurrentPath + vInfos->fileNameExt; - else + if (puFsRoot == prCurrentPath) + newPath = prCurrentPath + vInfos->fileNameExt; + else #endif // __linux__ - newPath = prCurrentPath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; - } + newPath = prCurrentPath + std::string(1u, PATH_SEP) + vInfos->fileNameExt; + } - if (IGFD::Utils::IsDirectoryExist(newPath)) - { - if (puShowDrives) - { - prCurrentPath = vInfos->fileNameExt; - puFsRoot = prCurrentPath; - } - else - { - prCurrentPath = newPath; //-V820 - } - pathClick = true; - } - } + if (IGFD::Utils::IsDirectoryExist(newPath)) + { + if (puShowDrives) + { + prCurrentPath = vInfos->fileNameExt; + puFsRoot = prCurrentPath; + } + else + { + prCurrentPath = newPath; //-V820 + } + pathClick = true; + } + } - return pathClick; - } + return pathClick; + } - void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) - { - if (!vInfos.use_count()) - return; + void IGFD::FileManager::SelectFileName(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr& vInfos) + { + if (!vInfos.use_count()) + return; - if (ImGui::GetIO().KeyCtrl) - { - if (puDLGcountSelectionMax == 0) // infinite selection - { - if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add - { - prAddFileNameInSelection(vInfos->fileNameExt, true); - } - else // found +> remove - { - prRemoveFileNameInSelection(vInfos->fileNameExt); - } - } - else // selection limited by size - { - if (prSelectedFileNames.size() < puDLGcountSelectionMax) - { - if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add - { - prAddFileNameInSelection(vInfos->fileNameExt, true); - } - else // found +> remove - { - prRemoveFileNameInSelection(vInfos->fileNameExt); - } - } - } - } - else if (ImGui::GetIO().KeyShift) - { - if (puDLGcountSelectionMax != 1) - { - prSelectedFileNames.clear(); - // we will iterate filelist and get the last selection after the start selection - bool startMultiSelection = false; - std::string fileNameToSelect = vInfos->fileNameExt; - std::string savedLastSelectedFileName; // for invert selection mode - for (const auto& file : prFileList) - { - if (!file.use_count()) - continue; + if (ImGui::GetIO().KeyCtrl) + { + if (puDLGcountSelectionMax == 0) // infinite selection + { + if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + { + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + else // found +> remove + { + prRemoveFileNameInSelection(vInfos->fileNameExt); + } + } + else // selection limited by size + { + if (prSelectedFileNames.size() < puDLGcountSelectionMax) + { + if (prSelectedFileNames.find(vInfos->fileNameExt) == prSelectedFileNames.end()) // not found +> add + { + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + else // found +> remove + { + prRemoveFileNameInSelection(vInfos->fileNameExt); + } + } + } + } + else if (ImGui::GetIO().KeyShift) + { + if (puDLGcountSelectionMax != 1) + { + prSelectedFileNames.clear(); + // we will iterate filelist and get the last selection after the start selection + bool startMultiSelection = false; + std::string fileNameToSelect = vInfos->fileNameExt; + std::string savedLastSelectedFileName; // for invert selection mode + for (const auto& file : prFileList) + { + if (!file.use_count()) + continue; - bool canTake = true; - if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) canTake = false; - if (canTake) // if not filtered, we will take files who are filtered by the dialog - { - if (file->fileNameExt == prLastSelectedFileName) - { - startMultiSelection = true; - prAddFileNameInSelection(prLastSelectedFileName, false); - } - else if (startMultiSelection) - { - if (puDLGcountSelectionMax == 0) // infinite selection - { - prAddFileNameInSelection(file->fileNameExt, false); - } - else // selection limited by size - { - if (prSelectedFileNames.size() < puDLGcountSelectionMax) - { - prAddFileNameInSelection(file->fileNameExt, false); - } - else - { - startMultiSelection = false; - if (!savedLastSelectedFileName.empty()) - prLastSelectedFileName = savedLastSelectedFileName; - break; - } - } - } + bool canTake = true; + if (!file->IsTagFound(vFileDialogInternal.puSearchManager.puSearchTag)) canTake = false; + if (canTake) // if not filtered, we will take files who are filtered by the dialog + { + if (file->fileNameExt == prLastSelectedFileName) + { + startMultiSelection = true; + prAddFileNameInSelection(prLastSelectedFileName, false); + } + else if (startMultiSelection) + { + if (puDLGcountSelectionMax == 0) // infinite selection + { + prAddFileNameInSelection(file->fileNameExt, false); + } + else // selection limited by size + { + if (prSelectedFileNames.size() < puDLGcountSelectionMax) + { + prAddFileNameInSelection(file->fileNameExt, false); + } + else + { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) + prLastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } - if (file->fileNameExt == fileNameToSelect) - { - if (!startMultiSelection) // we are before the last Selected FileName, so we must inverse - { - savedLastSelectedFileName = prLastSelectedFileName; - prLastSelectedFileName = fileNameToSelect; - fileNameToSelect = savedLastSelectedFileName; - startMultiSelection = true; - prAddFileNameInSelection(prLastSelectedFileName, false); - } - else - { - startMultiSelection = false; - if (!savedLastSelectedFileName.empty()) - prLastSelectedFileName = savedLastSelectedFileName; - break; - } - } - } - } - } - } - else - { - prSelectedFileNames.clear(); - IGFD::Utils::ResetBuffer(puFileNameBuffer); - prAddFileNameInSelection(vInfos->fileNameExt, true); - } - } + if (file->fileNameExt == fileNameToSelect) + { + if (!startMultiSelection) // we are before the last Selected FileName, so we must inverse + { + savedLastSelectedFileName = prLastSelectedFileName; + prLastSelectedFileName = fileNameToSelect; + fileNameToSelect = savedLastSelectedFileName; + startMultiSelection = true; + prAddFileNameInSelection(prLastSelectedFileName, false); + } + else + { + startMultiSelection = false; + if (!savedLastSelectedFileName.empty()) + prLastSelectedFileName = savedLastSelectedFileName; + break; + } + } + } + } + } + } + else + { + prSelectedFileNames.clear(); + IGFD::Utils::ResetBuffer(puFileNameBuffer); + prAddFileNameInSelection(vInfos->fileNameExt, true); + } + } - void IGFD::FileManager::DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal) - { - if (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableCreateDirectoryButton) - return; + void IGFD::FileManager::DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal) + { + if (vFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableCreateDirectoryButton) + return; - if (IMGUI_BUTTON(createDirButtonString)) - { - if (!prCreateDirectoryMode) - { - prCreateDirectoryMode = true; - IGFD::Utils::ResetBuffer(puDirectoryNameBuffer); - } - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonCreateDirString); + if (IMGUI_BUTTON(createDirButtonString)) + { + if (!prCreateDirectoryMode) + { + prCreateDirectoryMode = true; + IGFD::Utils::ResetBuffer(puDirectoryNameBuffer); + } + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonCreateDirString); - if (prCreateDirectoryMode) - { - ImGui::SameLine(); + if (prCreateDirectoryMode) + { + ImGui::SameLine(); - ImGui::PushItemWidth(100.0f); - ImGui::InputText("##DirectoryFileName", puDirectoryNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); - ImGui::PopItemWidth(); + ImGui::PushItemWidth(100.0f); + ImGui::InputText("##DirectoryFileName", puDirectoryNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + ImGui::PopItemWidth(); - ImGui::SameLine(); + ImGui::SameLine(); - if (IMGUI_BUTTON(okButtonString)) - { - std::string newDir = std::string(puDirectoryNameBuffer); - if (CreateDir(newDir)) - { - SetCurrentPath(prCurrentPath + std::string(1u, PATH_SEP) + newDir); - OpenCurrentPath(vFileDialogInternal); - } + if (IMGUI_BUTTON(okButtonString)) + { + std::string newDir = std::string(puDirectoryNameBuffer); + if (CreateDir(newDir)) + { + SetCurrentPath(prCurrentPath + std::string(1u, PATH_SEP) + newDir); + OpenCurrentPath(vFileDialogInternal); + } - prCreateDirectoryMode = false; - } + prCreateDirectoryMode = false; + } - ImGui::SameLine(); + ImGui::SameLine(); - if (IMGUI_BUTTON(cancelButtonString)) - { - prCreateDirectoryMode = false; - } - } - } + if (IMGUI_BUTTON(cancelButtonString)) + { + prCreateDirectoryMode = false; + } + } + } - void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) - { - if (IMGUI_BUTTON(homeButtonString)) - { - SetCurrentPath(FileDialog::Instance()->homePath); - OpenCurrentPath(vFileDialogInternal); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonResetPathString); + void IGFD::FileManager::DrawPathComposer(const FileDialogInternal& vFileDialogInternal) + { + if (IMGUI_BUTTON(homeButtonString)) + { + SetCurrentPath(FileDialog::Instance()->homePath); + OpenCurrentPath(vFileDialogInternal); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonResetPathString); ImGui::SameLine(); if (IMGUI_BUTTON(parentDirString)) - { - if (SetPathOnParentDirectoryIfAny()) { - OpenCurrentPath(vFileDialogInternal); + { + if (SetPathOnParentDirectoryIfAny()) { + OpenCurrentPath(vFileDialogInternal); } - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonParentDirString); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonParentDirString); #ifdef WIN32 - ImGui::SameLine(); + ImGui::SameLine(); - if (IMGUI_BUTTON(drivesButtonString)) - { - puDrivesClicked = true; - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonDriveString); + if (IMGUI_BUTTON(drivesButtonString)) + { + puDrivesClicked = true; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonDriveString); #endif // WIN32 - ImGui::SameLine(); - - if (IMGUI_BUTTON(editPathButtonString)) - { - puInputPathActivated = true; - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(buttonEditPathString); + ImGui::SameLine(); + + if (IMGUI_BUTTON(editPathButtonString)) + { + puInputPathActivated = true; + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(buttonEditPathString); - ImGui::SameLine(); + ImGui::SameLine(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - // show current path - if (!prCurrentPathDecomposition.empty()) - { - ImGui::SameLine(); + // show current path + if (!prCurrentPathDecomposition.empty()) + { + ImGui::SameLine(); - if (puInputPathActivated) - { - ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##pathedition", puInputPathBuffer, MAX_PATH_BUFFER_SIZE); - ImGui::PopItemWidth(); - } - else - { - int _id = 0; - for (auto itPathDecomp = prCurrentPathDecomposition.begin(); - itPathDecomp != prCurrentPathDecomposition.end(); ++itPathDecomp) - { - if (itPathDecomp != prCurrentPathDecomposition.begin()) - ImGui::SameLine(); - ImGui::PushID(_id++); - bool click = IMGUI_PATH_BUTTON((*itPathDecomp).c_str()); - ImGui::PopID(); - if (click) - { - ComposeNewPath(itPathDecomp); - puPathClicked = true; - break; - } - // activate input for path - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) - { - ComposeNewPath(itPathDecomp); - IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); - puInputPathActivated = true; - break; - } - } - } - } - } + if (puInputPathActivated) + { + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##pathedition", puInputPathBuffer, MAX_PATH_BUFFER_SIZE); + ImGui::PopItemWidth(); + } + else + { + int _id = 0; + for (auto itPathDecomp = prCurrentPathDecomposition.begin(); + itPathDecomp != prCurrentPathDecomposition.end(); ++itPathDecomp) + { + if (itPathDecomp != prCurrentPathDecomposition.begin()) + ImGui::SameLine(); + ImGui::PushID(_id++); + bool click = IMGUI_PATH_BUTTON((*itPathDecomp).c_str()); + ImGui::PopID(); + if (click) + { + ComposeNewPath(itPathDecomp); + puPathClicked = true; + break; + } + // activate input for path + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) + { + ComposeNewPath(itPathDecomp); + IGFD::Utils::SetBuffer(puInputPathBuffer, MAX_PATH_BUFFER_SIZE, prCurrentPath); + puInputPathActivated = true; + break; + } + } + } + } + } - std::string IGFD::FileManager::GetResultingPath() - { - std::string path = prCurrentPath; + std::string IGFD::FileManager::GetResultingPath() + { + std::string path = prCurrentPath; - if (puDLGDirectoryMode) // if directory mode - { - std::string selectedDirectory = puFileNameBuffer; - if (!selectedDirectory.empty() && - selectedDirectory != ".") - path += std::string(1u, PATH_SEP) + selectedDirectory; - } + if (puDLGDirectoryMode) // if directory mode + { + std::string selectedDirectory = puFileNameBuffer; + if (!selectedDirectory.empty() && + selectedDirectory != ".") + path += std::string(1u, PATH_SEP) + selectedDirectory; + } - return path; - } + return path; + } - std::string IGFD::FileManager::GetResultingFileName(FileDialogInternal& vFileDialogInternal) - { - if (!puDLGDirectoryMode) // if not directory mode - { - return puFileNameBuffer; //vFileDialogInternal.puFilterManager.ReplaceExtentionWithCurrentFilter(std::string(puFileNameBuffer)); - } + std::string IGFD::FileManager::GetResultingFileName(FileDialogInternal& vFileDialogInternal) + { + if (!puDLGDirectoryMode) // if not directory mode + { + return puFileNameBuffer; //vFileDialogInternal.puFilterManager.ReplaceExtentionWithCurrentFilter(std::string(puFileNameBuffer)); + } - return ""; // directory mode - } + return ""; // directory mode + } - std::string IGFD::FileManager::GetResultingFilePathName(FileDialogInternal& vFileDialogInternal) - { - std::string result = GetResultingPath(); + std::string IGFD::FileManager::GetResultingFilePathName(FileDialogInternal& vFileDialogInternal) + { + std::string result = GetResultingPath(); - std::string filename = GetResultingFileName(vFileDialogInternal); - if (!filename.empty()) - { + std::string filename = GetResultingFileName(vFileDialogInternal); + if (!filename.empty()) + { #ifdef UNIX - if (puFsRoot != result) + if (puFsRoot != result) #endif // UNIX - result += std::string(1u, PATH_SEP); + result += std::string(1u, PATH_SEP); - result += filename; - } + result += filename; + } - return result; - } + return result; + } - std::map IGFD::FileManager::GetResultingSelection() - { - std::map res; + std::map IGFD::FileManager::GetResultingSelection() + { + std::map res; - for (auto& selectedFileName : prSelectedFileNames) - { - std::string result = GetResultingPath(); + for (auto& selectedFileName : prSelectedFileNames) + { + std::string result = GetResultingPath(); #ifdef UNIX - if (puFsRoot != result) + if (puFsRoot != result) #endif // UNIX - result += std::string(1u, PATH_SEP); + result += std::string(1u, PATH_SEP); - result += selectedFileName; + result += selectedFileName; - res[selectedFileName] = result; - } + res[selectedFileName] = result; + } - return res; - } + return res; + } - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE DIALOG INTERNAL /////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE DIALOG INTERNAL /////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - void IGFD::FileDialogInternal::NewFrame() - { - puCanWeContinue = true; // reset flag for possibily validate the dialog - puIsOk = false; // reset dialog result - puFileManager.puDrivesClicked = false; - puFileManager.puPathClicked = false; - - puNeedToExitDialog = false; + void IGFD::FileDialogInternal::NewFrame() + { + puCanWeContinue = true; // reset flag for possibily validate the dialog + puIsOk = false; // reset dialog result + puFileManager.puDrivesClicked = false; + puFileManager.puPathClicked = false; + + puNeedToExitDialog = false; #ifdef USE_DIALOG_EXIT_WITH_KEY - if (ImGui::IsKeyPressed(IGFD_EXIT_KEY)) - { - // we do that here with the data's defined at the last frame - // because escape key can quit input activation and at the end of the frame all flag will be false - // so we will detect nothing - if (!(puFileManager.puInputPathActivated || - puSearchManager.puSearchInputIsActive || - puFileInputIsActive || - puFileListViewIsActive)) - { - puNeedToExitDialog = true; // need to quit dialog - } - } - else + if (ImGui::IsKeyPressed(IGFD_EXIT_KEY)) + { + // we do that here with the data's defined at the last frame + // because escape key can quit input activation and at the end of the frame all flag will be false + // so we will detect nothing + if (!(puFileManager.puInputPathActivated || + puSearchManager.puSearchInputIsActive || + puFileInputIsActive || + puFileListViewIsActive)) + { + puNeedToExitDialog = true; // need to quit dialog + } + } + else #endif - { - puSearchManager.puSearchInputIsActive = false; - puFileInputIsActive = false; - puFileListViewIsActive = false; - } - } + { + puSearchManager.puSearchInputIsActive = false; + puFileInputIsActive = false; + puFileListViewIsActive = false; + } + } - void IGFD::FileDialogInternal::EndFrame() - { - // directory change - if (puFileManager.puPathClicked) - { - puFileManager.OpenCurrentPath(*this); - } + void IGFD::FileDialogInternal::EndFrame() + { + // directory change + if (puFileManager.puPathClicked) + { + puFileManager.OpenCurrentPath(*this); + } - if (puFileManager.puDrivesClicked) - { - if (puFileManager.GetDrives()) - { - puFileManager.ApplyFilteringOnFileList(*this); - } - } + if (puFileManager.puDrivesClicked) + { + if (puFileManager.GetDrives()) + { + puFileManager.ApplyFilteringOnFileList(*this); + } + } - if (puFileManager.puInputPathActivated) - { - auto gio = ImGui::GetIO(); - if (ImGui::IsKeyReleased(ImGuiKey_Enter)) - { - puFileManager.SetCurrentPath(std::string(puFileManager.puInputPathBuffer)); - puFileManager.OpenCurrentPath(*this); - puFileManager.puInputPathActivated = false; - } - if (ImGui::IsKeyReleased(ImGuiKey_Escape)) - { - puFileManager.puInputPathActivated = false; - } - } - } + if (puFileManager.puInputPathActivated) + { + auto gio = ImGui::GetIO(); + if (ImGui::IsKeyReleased(ImGuiKey_Enter)) + { + puFileManager.SetCurrentPath(std::string(puFileManager.puInputPathBuffer)); + puFileManager.OpenCurrentPath(*this); + puFileManager.puInputPathActivated = false; + } + if (ImGui::IsKeyReleased(ImGuiKey_Escape)) + { + puFileManager.puInputPathActivated = false; + } + } + } - void IGFD::FileDialogInternal::ResetForNewDialog() - { - puFileManager.fileListActuallyEmpty=false; - } + void IGFD::FileDialogInternal::ResetForNewDialog() + { + puFileManager.fileListActuallyEmpty=false; + } - ///////////////////////////////////////////////////////////////////////////////////// - //// THUMBNAIL FEATURE ////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// THUMBNAIL FEATURE ////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - IGFD::ThumbnailFeature::ThumbnailFeature() - { + IGFD::ThumbnailFeature::ThumbnailFeature() + { #ifdef USE_THUMBNAILS - prDisplayMode = DisplayModeEnum::FILE_LIST; + prDisplayMode = DisplayModeEnum::FILE_LIST; #endif - } + } - IGFD::ThumbnailFeature::~ThumbnailFeature() = default; + IGFD::ThumbnailFeature::~ThumbnailFeature() = default; - void IGFD::ThumbnailFeature::NewThumbnailFrame(FileDialogInternal& vFileDialogInternal) - { - (void)vFileDialogInternal; + void IGFD::ThumbnailFeature::NewThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { + (void)vFileDialogInternal; #ifdef USE_THUMBNAILS - prStartThumbnailFileDatasExtraction(); + prStartThumbnailFileDatasExtraction(); #endif - } + } - void IGFD::ThumbnailFeature::EndThumbnailFrame(FileDialogInternal& vFileDialogInternal) - { + void IGFD::ThumbnailFeature::EndThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { #ifdef USE_THUMBNAILS - prClearThumbnails(vFileDialogInternal); + prClearThumbnails(vFileDialogInternal); #endif - } + } - void IGFD::ThumbnailFeature::QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal) - { + void IGFD::ThumbnailFeature::QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal) + { #ifdef USE_THUMBNAILS - prStopThumbnailFileDatasExtraction(); - prClearThumbnails(vFileDialogInternal); + prStopThumbnailFileDatasExtraction(); + prClearThumbnails(vFileDialogInternal); #endif - } + } #ifdef USE_THUMBNAILS - void IGFD::ThumbnailFeature::prStartThumbnailFileDatasExtraction() - { - const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); - if (!res) - { - prIsWorking = true; - prCountFiles = 0U; - prThumbnailGenerationThread = std::shared_ptr( - new std::thread(&IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc, this), - [this](std::thread* obj) - { - prIsWorking = false; - if (obj) - obj->join(); - }); - } - } + void IGFD::ThumbnailFeature::prStartThumbnailFileDatasExtraction() + { + const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); + if (!res) + { + prIsWorking = true; + prCountFiles = 0U; + prThumbnailGenerationThread = std::shared_ptr( + new std::thread(&IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc, this), + [this](std::thread* obj) + { + prIsWorking = false; + if (obj) + obj->join(); + }); + } + } - bool IGFD::ThumbnailFeature::prStopThumbnailFileDatasExtraction() - { - const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); - if (res) - { - prThumbnailGenerationThread.reset(); - } + bool IGFD::ThumbnailFeature::prStopThumbnailFileDatasExtraction() + { + const bool res = prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable(); + if (res) + { + prThumbnailGenerationThread.reset(); + } - return res; - } - - void IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc() - { - prCountFiles = 0U; - prIsWorking = true; + return res; + } + + void IGFD::ThumbnailFeature::prThreadThumbnailFileDatasExtractionFunc() + { + prCountFiles = 0U; + prIsWorking = true; - // infinite loop while is thread working - while(prIsWorking) - { - if (!prThumbnailFileDatasToGet.empty()) - { - std::shared_ptr file = nullptr; - prThumbnailFileDatasToGetMutex.lock(); - //get the first file in the list - file = (*prThumbnailFileDatasToGet.begin()); - prThumbnailFileDatasToGetMutex.unlock(); + // infinite loop while is thread working + while(prIsWorking) + { + if (!prThumbnailFileDatasToGet.empty()) + { + std::shared_ptr file = nullptr; + prThumbnailFileDatasToGetMutex.lock(); + //get the first file in the list + file = (*prThumbnailFileDatasToGet.begin()); + prThumbnailFileDatasToGetMutex.unlock(); - // retrieve datas of the texture file if its an image file - if (file.use_count()) - { - if (file->fileType == 'f') //-V522 - { - if (file->fileExt == ".png" - || file->fileExt == ".bmp" - || file->fileExt == ".tga" - || file->fileExt == ".jpg" || file->fileExt == ".jpeg" - || file->fileExt == ".gif" - || file->fileExt == ".psd" - || file->fileExt == ".pic" - || file->fileExt == ".ppm" || file->fileExt == ".pgm" - //|| file->fileExt == ".hdr" => format float so in few times - ) - { - auto fpn = file->filePath + std::string(1u, PATH_SEP) + file->fileNameExt; + // retrieve datas of the texture file if its an image file + if (file.use_count()) + { + if (file->fileType == 'f') //-V522 + { + if (file->fileExt == ".png" + || file->fileExt == ".bmp" + || file->fileExt == ".tga" + || file->fileExt == ".jpg" || file->fileExt == ".jpeg" + || file->fileExt == ".gif" + || file->fileExt == ".psd" + || file->fileExt == ".pic" + || file->fileExt == ".ppm" || file->fileExt == ".pgm" + //|| file->fileExt == ".hdr" => format float so in few times + ) + { + auto fpn = file->filePath + std::string(1u, PATH_SEP) + file->fileNameExt; - int w = 0; - int h = 0; - int chans = 0; - uint8_t *datas = stbi_load(fpn.c_str(), &w, &h, &chans, STBI_rgb_alpha); - if (datas) - { - if (w && h) - { - // resize with respect to glyph ratio - const float ratioX = (float)w / (float)h; - const float newX = DisplayMode_ThumbailsList_ImageHeight * ratioX; - float newY = w / ratioX; - if (newX < w) - newY = DisplayMode_ThumbailsList_ImageHeight; + int w = 0; + int h = 0; + int chans = 0; + uint8_t *datas = stbi_load(fpn.c_str(), &w, &h, &chans, STBI_rgb_alpha); + if (datas) + { + if (w && h) + { + // resize with respect to glyph ratio + const float ratioX = (float)w / (float)h; + const float newX = DisplayMode_ThumbailsList_ImageHeight * ratioX; + float newY = w / ratioX; + if (newX < w) + newY = DisplayMode_ThumbailsList_ImageHeight; - const auto newWidth = (int)newX; - const auto newHeight = (int)newY; - const auto newBufSize = (size_t)(newWidth * newHeight * 4U); //-V112 //-V1028 - auto resizedData = new uint8_t[newBufSize]; - - const int resizeSucceeded = stbir_resize_uint8( - datas, w, h, 0, - resizedData, newWidth, newHeight, 0, - 4); //-V112 + const auto newWidth = (int)newX; + const auto newHeight = (int)newY; + const auto newBufSize = (size_t)(newWidth * newHeight * 4U); //-V112 //-V1028 + auto resizedData = new uint8_t[newBufSize]; + + const int resizeSucceeded = stbir_resize_uint8( + datas, w, h, 0, + resizedData, newWidth, newHeight, 0, + 4); //-V112 - if (resizeSucceeded) - { - auto th = &file->thumbnailInfo; + if (resizeSucceeded) + { + auto th = &file->thumbnailInfo; - th->textureFileDatas = resizedData; - th->textureWidth = newWidth; - th->textureHeight = newHeight; - th->textureChannels = 4; //-V112 + th->textureFileDatas = resizedData; + th->textureWidth = newWidth; + th->textureHeight = newHeight; + th->textureChannels = 4; //-V112 - // we set that at least, because will launch the gpu creation of the texture in the main thread - th->isReadyToUpload = true; + // we set that at least, because will launch the gpu creation of the texture in the main thread + th->isReadyToUpload = true; - // need gpu loading - prAddThumbnailToCreate(file); - } - } - else - { - printf("image loading fail : w:%i h:%i c:%i\n", w, h, 4); //-V112 - } + // need gpu loading + prAddThumbnailToCreate(file); + } + } + else + { + printf("image loading fail : w:%i h:%i c:%i\n", w, h, 4); //-V112 + } - stbi_image_free(datas); - } - } - } + stbi_image_free(datas); + } + } + } - // peu importe le resultat on vire le fichicer - // remove form this list - // write => thread concurency issues - prThumbnailFileDatasToGetMutex.lock(); - prThumbnailFileDatasToGet.pop_front(); - prThumbnailFileDatasToGetMutex.unlock(); - } - } - } - } + // peu importe le resultat on vire le fichicer + // remove form this list + // write => thread concurency issues + prThumbnailFileDatasToGetMutex.lock(); + prThumbnailFileDatasToGet.pop_front(); + prThumbnailFileDatasToGetMutex.unlock(); + } + } + } + } - inline void inVariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...) - { - va_list args; - va_start(args, fmt); - char TempBuffer[512]; - const int w = vsnprintf(TempBuffer, 511, fmt, args); - va_end(args); - if (w) - { - ImGui::ProgressBar(fraction, size_arg, TempBuffer); - } - } + inline void inVariadicProgressBar(float fraction, const ImVec2& size_arg, const char* fmt, ...) + { + va_list args; + va_start(args, fmt); + char TempBuffer[512]; + const int w = vsnprintf(TempBuffer, 511, fmt, args); + va_end(args); + if (w) + { + ImGui::ProgressBar(fraction, size_arg, TempBuffer); + } + } - void IGFD::ThumbnailFeature::prDrawThumbnailGenerationProgress() - { - if (prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable()) - { - if (!prThumbnailFileDatasToGet.empty()) - { - const auto p = (float)((double)prCountFiles / (double)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues - inVariadicProgressBar(p, ImVec2(50, 0), "%u/%u", prCountFiles, (uint32_t)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues - ImGui::SameLine(); - } - } - } + void IGFD::ThumbnailFeature::prDrawThumbnailGenerationProgress() + { + if (prThumbnailGenerationThread.use_count() && prThumbnailGenerationThread->joinable()) + { + if (!prThumbnailFileDatasToGet.empty()) + { + const auto p = (float)((double)prCountFiles / (double)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues + inVariadicProgressBar(p, ImVec2(50, 0), "%u/%u", prCountFiles, (uint32_t)prThumbnailFileDatasToGet.size()); // read => no thread concurency issues + ImGui::SameLine(); + } + } + } - void IGFD::ThumbnailFeature::prAddThumbnailToLoad(const std::shared_ptr& vFileInfos) - { - if (vFileInfos.use_count()) - { - if (vFileInfos->fileType == 'f') - { - if (vFileInfos->fileExt == ".png" - || vFileInfos->fileExt == ".bmp" - || vFileInfos->fileExt == ".tga" - || vFileInfos->fileExt == ".jpg" || vFileInfos->fileExt == ".jpeg" - || vFileInfos->fileExt == ".gif" - || vFileInfos->fileExt == ".psd" - || vFileInfos->fileExt == ".pic" - || vFileInfos->fileExt == ".ppm" || vFileInfos->fileExt == ".pgm" - //|| file->fileExt == ".hdr" => format float so in few times - ) - { - // write => thread concurency issues - prThumbnailFileDatasToGetMutex.lock(); - prThumbnailFileDatasToGet.push_back(vFileInfos); - vFileInfos->thumbnailInfo.isLoadingOrLoaded = true; - prThumbnailFileDatasToGetMutex.unlock(); - } - } - } - } - - void IGFD::ThumbnailFeature::prAddThumbnailToCreate(const std::shared_ptr& vFileInfos) - { - if (vFileInfos.use_count()) - { - // write => thread concurency issues - prThumbnailToCreateMutex.lock(); - prThumbnailToCreate.push_back(vFileInfos); - prThumbnailToCreateMutex.unlock(); - } - } + void IGFD::ThumbnailFeature::prAddThumbnailToLoad(const std::shared_ptr& vFileInfos) + { + if (vFileInfos.use_count()) + { + if (vFileInfos->fileType == 'f') + { + if (vFileInfos->fileExt == ".png" + || vFileInfos->fileExt == ".bmp" + || vFileInfos->fileExt == ".tga" + || vFileInfos->fileExt == ".jpg" || vFileInfos->fileExt == ".jpeg" + || vFileInfos->fileExt == ".gif" + || vFileInfos->fileExt == ".psd" + || vFileInfos->fileExt == ".pic" + || vFileInfos->fileExt == ".ppm" || vFileInfos->fileExt == ".pgm" + //|| file->fileExt == ".hdr" => format float so in few times + ) + { + // write => thread concurency issues + prThumbnailFileDatasToGetMutex.lock(); + prThumbnailFileDatasToGet.push_back(vFileInfos); + vFileInfos->thumbnailInfo.isLoadingOrLoaded = true; + prThumbnailFileDatasToGetMutex.unlock(); + } + } + } + } + + void IGFD::ThumbnailFeature::prAddThumbnailToCreate(const std::shared_ptr& vFileInfos) + { + if (vFileInfos.use_count()) + { + // write => thread concurency issues + prThumbnailToCreateMutex.lock(); + prThumbnailToCreate.push_back(vFileInfos); + prThumbnailToCreateMutex.unlock(); + } + } - void IGFD::ThumbnailFeature::prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info) - { - // write => thread concurency issues - prThumbnailToDestroyMutex.lock(); - prThumbnailToDestroy.push_back(vIGFD_Thumbnail_Info); - prThumbnailToDestroyMutex.unlock(); - } + void IGFD::ThumbnailFeature::prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info) + { + // write => thread concurency issues + prThumbnailToDestroyMutex.lock(); + prThumbnailToDestroy.push_back(vIGFD_Thumbnail_Info); + prThumbnailToDestroyMutex.unlock(); + } - void IGFD::ThumbnailFeature::prDrawDisplayModeToolBar() - { - if (IMGUI_RADIO_BUTTON(DisplayMode_FilesList_ButtonString, - prDisplayMode == DisplayModeEnum::FILE_LIST)) - prDisplayMode = DisplayModeEnum::FILE_LIST; - if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_FilesList_ButtonHelp); - ImGui::SameLine(); - if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsList_ButtonString, - prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST)) - prDisplayMode = DisplayModeEnum::THUMBNAILS_LIST; - if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsList_ButtonHelp); - ImGui::SameLine(); - /* todo - if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsGrid_ButtonString, - prDisplayMode == DisplayModeEnum::THUMBNAILS_GRID)) - prDisplayMode = DisplayModeEnum::THUMBNAILS_GRID; - if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsGrid_ButtonHelp); - ImGui::SameLine(); - */ - prDrawThumbnailGenerationProgress(); - } + void IGFD::ThumbnailFeature::prDrawDisplayModeToolBar() + { + if (IMGUI_RADIO_BUTTON(DisplayMode_FilesList_ButtonString, + prDisplayMode == DisplayModeEnum::FILE_LIST)) + prDisplayMode = DisplayModeEnum::FILE_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_FilesList_ButtonHelp); + ImGui::SameLine(); + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsList_ButtonString, + prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST)) + prDisplayMode = DisplayModeEnum::THUMBNAILS_LIST; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsList_ButtonHelp); + ImGui::SameLine(); + /* todo + if (IMGUI_RADIO_BUTTON(DisplayMode_ThumbailsGrid_ButtonString, + prDisplayMode == DisplayModeEnum::THUMBNAILS_GRID)) + prDisplayMode = DisplayModeEnum::THUMBNAILS_GRID; + if (ImGui::IsItemHovered()) ImGui::SetTooltip(DisplayMode_ThumbailsGrid_ButtonHelp); + ImGui::SameLine(); + */ + prDrawThumbnailGenerationProgress(); + } - void IGFD::ThumbnailFeature::prClearThumbnails(FileDialogInternal& vFileDialogInternal) - { - // directory wil be changed so the file list will be erased - if (vFileDialogInternal.puFileManager.puPathClicked) - { - size_t count = vFileDialogInternal.puFileManager.GetFullFileListSize(); - for (size_t idx = 0U; idx < count; idx++) - { - auto file = vFileDialogInternal.puFileManager.GetFullFileAt(idx); - if (file.use_count()) - { - if (file->thumbnailInfo.isReadyToDisplay) //-V522 - { - prAddThumbnailToDestroy(file->thumbnailInfo); - } - } - } - } - } + void IGFD::ThumbnailFeature::prClearThumbnails(FileDialogInternal& vFileDialogInternal) + { + // directory wil be changed so the file list will be erased + if (vFileDialogInternal.puFileManager.puPathClicked) + { + size_t count = vFileDialogInternal.puFileManager.GetFullFileListSize(); + for (size_t idx = 0U; idx < count; idx++) + { + auto file = vFileDialogInternal.puFileManager.GetFullFileAt(idx); + if (file.use_count()) + { + if (file->thumbnailInfo.isReadyToDisplay) //-V522 + { + prAddThumbnailToDestroy(file->thumbnailInfo); + } + } + } + } + } - void IGFD::ThumbnailFeature::SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun) - { - prCreateThumbnailFun = vCreateThumbnailFun; - } + void IGFD::ThumbnailFeature::SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun) + { + prCreateThumbnailFun = vCreateThumbnailFun; + } - void IGFD::ThumbnailFeature::SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun) - { - prDestroyThumbnailFun = vCreateThumbnailFun; - } + void IGFD::ThumbnailFeature::SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun) + { + prDestroyThumbnailFun = vCreateThumbnailFun; + } - void IGFD::ThumbnailFeature::ManageGPUThumbnails() - { - if (prCreateThumbnailFun) - { - if (!prThumbnailToCreate.empty()) - { - for (const auto& file : prThumbnailToCreate) - { - if (file.use_count()) - { - prCreateThumbnailFun(&file->thumbnailInfo); - } - } - prThumbnailToCreateMutex.lock(); - prThumbnailToCreate.clear(); - prThumbnailToCreateMutex.unlock(); - } - } - else - { - printf("No Callback found for create texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); - } + void IGFD::ThumbnailFeature::ManageGPUThumbnails() + { + if (prCreateThumbnailFun) + { + if (!prThumbnailToCreate.empty()) + { + for (const auto& file : prThumbnailToCreate) + { + if (file.use_count()) + { + prCreateThumbnailFun(&file->thumbnailInfo); + } + } + prThumbnailToCreateMutex.lock(); + prThumbnailToCreate.clear(); + prThumbnailToCreateMutex.unlock(); + } + } + else + { + printf("No Callback found for create texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); + } - if (prDestroyThumbnailFun) - { - if (!prThumbnailToDestroy.empty()) - { - for (auto thumbnail : prThumbnailToDestroy) - { - prDestroyThumbnailFun(&thumbnail); - } - prThumbnailToDestroyMutex.lock(); - prThumbnailToDestroy.clear(); - prThumbnailToDestroyMutex.unlock(); - } - } - else - { - printf("No Callback found for destroy texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); - } - } + if (prDestroyThumbnailFun) + { + if (!prThumbnailToDestroy.empty()) + { + for (auto thumbnail : prThumbnailToDestroy) + { + prDestroyThumbnailFun(&thumbnail); + } + prThumbnailToDestroyMutex.lock(); + prThumbnailToDestroy.clear(); + prThumbnailToDestroyMutex.unlock(); + } + } + else + { + printf("No Callback found for destroy texture\nYou need to define the callback with a call to SetCreateThumbnailCallback\n"); + } + } #endif // USE_THUMBNAILS - ///////////////////////////////////////////////////////////////////////////////////// - //// BOOKMARK FEATURE /////////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// BOOKMARK FEATURE /////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - IGFD::BookMarkFeature::BookMarkFeature() - { + IGFD::BookMarkFeature::BookMarkFeature() + { #ifdef USE_BOOKMARK - prBookmarkWidth = defaultBookmarkPaneWith; + prBookmarkWidth = defaultBookmarkPaneWith; #endif // USE_BOOKMARK - } + } #ifdef USE_BOOKMARK - void IGFD::BookMarkFeature::prDrawBookmarkButton() - { - IMGUI_TOGGLE_BUTTON(bookmarksButtonString, &prBookmarkPaneShown); + void IGFD::BookMarkFeature::prDrawBookmarkButton() + { + IMGUI_TOGGLE_BUTTON(bookmarksButtonString, &prBookmarkPaneShown); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip(bookmarksButtonHelpString); - } - bool IGFD::BookMarkFeature::prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize) - { - bool res = false; + if (ImGui::IsItemHovered()) + ImGui::SetTooltip(bookmarksButtonHelpString); + } + bool IGFD::BookMarkFeature::prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize) + { + bool res = false; - ImGui::BeginChild("##bookmarkpane", vSize); + ImGui::BeginChild("##bookmarkpane", vSize); - static int selectedBookmarkForEdition = -1; + static int selectedBookmarkForEdition = -1; - if (IMGUI_BUTTON(addBookmarkButtonString "##ImGuiFileDialogAddBookmark")) - { - if (!vFileDialogInternal.puFileManager.IsComposerEmpty()) - { - BookmarkStruct bookmark; - bookmark.name = vFileDialogInternal.puFileManager.GetBack(); - bookmark.path = vFileDialogInternal.puFileManager.GetCurrentPath(); - prBookmarks.push_back(bookmark); - } - } - if (selectedBookmarkForEdition >= 0 && - selectedBookmarkForEdition < (int)prBookmarks.size()) - { - ImGui::SameLine(); - if (IMGUI_BUTTON(removeBookmarkButtonString "##ImGuiFileDialogAddBookmark")) - { - prBookmarks.erase(prBookmarks.begin() + selectedBookmarkForEdition); - if (selectedBookmarkForEdition == (int)prBookmarks.size()) - selectedBookmarkForEdition--; - } + if (IMGUI_BUTTON(addBookmarkButtonString "##ImGuiFileDialogAddBookmark")) + { + if (!vFileDialogInternal.puFileManager.IsComposerEmpty()) + { + BookmarkStruct bookmark; + bookmark.name = vFileDialogInternal.puFileManager.GetBack(); + bookmark.path = vFileDialogInternal.puFileManager.GetCurrentPath(); + prBookmarks.push_back(bookmark); + } + } + if (selectedBookmarkForEdition >= 0 && + selectedBookmarkForEdition < (int)prBookmarks.size()) + { + ImGui::SameLine(); + if (IMGUI_BUTTON(removeBookmarkButtonString "##ImGuiFileDialogAddBookmark")) + { + prBookmarks.erase(prBookmarks.begin() + selectedBookmarkForEdition); + if (selectedBookmarkForEdition == (int)prBookmarks.size()) + selectedBookmarkForEdition--; + } - if (selectedBookmarkForEdition >= 0 && - selectedBookmarkForEdition < (int)prBookmarks.size()) - { - ImGui::SameLine(); + if (selectedBookmarkForEdition >= 0 && + selectedBookmarkForEdition < (int)prBookmarks.size()) + { + ImGui::SameLine(); - ImGui::PushItemWidth(vSize.x - ImGui::GetCursorPosX()); - if (ImGui::InputText("##ImGuiFileDialogBookmarkEdit", prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER)) - { - prBookmarks[(size_t)selectedBookmarkForEdition].name = std::string(prBookmarkEditBuffer); - } - ImGui::PopItemWidth(); - } - } + ImGui::PushItemWidth(vSize.x - ImGui::GetCursorPosX()); + if (ImGui::InputText("##ImGuiFileDialogBookmarkEdit", prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER)) + { + prBookmarks[(size_t)selectedBookmarkForEdition].name = std::string(prBookmarkEditBuffer); + } + ImGui::PopItemWidth(); + } + } - ImGui::Separator(); + ImGui::Separator(); - if (!prBookmarks.empty()) - { - prBookmarkClipper.Begin((int)prBookmarks.size(), ImGui::GetTextLineHeightWithSpacing()); - while (prBookmarkClipper.Step()) - { - for (int i = prBookmarkClipper.DisplayStart; i < prBookmarkClipper.DisplayEnd; i++) - { - if (i < 0) continue; - const BookmarkStruct& bookmark = prBookmarks[(size_t)i]; - ImGui::PushID(i); - if (ImGui::Selectable(bookmark.name.c_str(), selectedBookmarkForEdition == i, - ImGuiSelectableFlags_AllowDoubleClick) | - (selectedBookmarkForEdition == -1 && - bookmark.path == vFileDialogInternal.puFileManager.GetCurrentPath())) // select if path is current - { - selectedBookmarkForEdition = i; - IGFD::Utils::ResetBuffer(prBookmarkEditBuffer); - IGFD::Utils::AppendToBuffer(prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER, bookmark.name); + if (!prBookmarks.empty()) + { + prBookmarkClipper.Begin((int)prBookmarks.size(), ImGui::GetTextLineHeightWithSpacing()); + while (prBookmarkClipper.Step()) + { + for (int i = prBookmarkClipper.DisplayStart; i < prBookmarkClipper.DisplayEnd; i++) + { + if (i < 0) continue; + const BookmarkStruct& bookmark = prBookmarks[(size_t)i]; + ImGui::PushID(i); + if (ImGui::Selectable(bookmark.name.c_str(), selectedBookmarkForEdition == i, + ImGuiSelectableFlags_AllowDoubleClick) | + (selectedBookmarkForEdition == -1 && + bookmark.path == vFileDialogInternal.puFileManager.GetCurrentPath())) // select if path is current + { + selectedBookmarkForEdition = i; + IGFD::Utils::ResetBuffer(prBookmarkEditBuffer); + IGFD::Utils::AppendToBuffer(prBookmarkEditBuffer, MAX_FILE_DIALOG_NAME_BUFFER, bookmark.name); - if (ImGui::IsMouseDoubleClicked(0)) // apply path - { - vFileDialogInternal.puFileManager.SetCurrentPath(bookmark.path); - vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); - res = true; - } - } - ImGui::PopID(); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("%s", bookmark.path.c_str()); //-V111 - } - } - prBookmarkClipper.End(); - } + if (ImGui::IsMouseDoubleClicked(0)) // apply path + { + vFileDialogInternal.puFileManager.SetCurrentPath(bookmark.path); + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + res = true; + } + } + ImGui::PopID(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", bookmark.path.c_str()); //-V111 + } + } + prBookmarkClipper.End(); + } - ImGui::EndChild(); + ImGui::EndChild(); - return res; - } + return res; + } - std::string IGFD::BookMarkFeature::SerializeBookmarks() - { - std::string res; + std::string IGFD::BookMarkFeature::SerializeBookmarks() + { + std::string res; - size_t idx = 0; - for (auto& it : prBookmarks) - { - if (idx++ != 0) - res += "##"; // ## because reserved by imgui, so an input text cant have ## - res += it.name + "##" + it.path; - } + size_t idx = 0; + for (auto& it : prBookmarks) + { + if (idx++ != 0) + res += "##"; // ## because reserved by imgui, so an input text cant have ## + res += it.name + "##" + it.path; + } - return res; - } + return res; + } - void IGFD::BookMarkFeature::DeserializeBookmarks(const std::string& vBookmarks) - { - if (!vBookmarks.empty()) - { - prBookmarks.clear(); - auto arr = IGFD::Utils::SplitStringToVector(vBookmarks, '#', false); - for (size_t i = 0; i < arr.size(); i += 2) - { - BookmarkStruct bookmark; - bookmark.name = arr[i]; - if (i + 1 < arr.size()) // for avoid crash if arr size is impair due to user mistake after edition - { - // if bad format we jump this bookmark - bookmark.path = arr[i + 1]; - prBookmarks.push_back(bookmark); - } - } - } - } + void IGFD::BookMarkFeature::DeserializeBookmarks(const std::string& vBookmarks) + { + if (!vBookmarks.empty()) + { + prBookmarks.clear(); + auto arr = IGFD::Utils::SplitStringToVector(vBookmarks, '#', false); + for (size_t i = 0; i < arr.size(); i += 2) + { + BookmarkStruct bookmark; + bookmark.name = arr[i]; + if (i + 1 < arr.size()) // for avoid crash if arr size is impair due to user mistake after edition + { + // if bad format we jump this bookmark + bookmark.path = arr[i + 1]; + prBookmarks.push_back(bookmark); + } + } + } + } #endif // USE_BOOKMARK - ///////////////////////////////////////////////////////////////////////////////////// - //// KEY EXPLORER FEATURE /////////////////////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// KEY EXPLORER FEATURE /////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - KeyExplorerFeature::KeyExplorerFeature() = default; + KeyExplorerFeature::KeyExplorerFeature() = default; #ifdef USE_EXPLORATION_BY_KEYS - bool IGFD::KeyExplorerFeature::prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC) - { - bool found = false; + bool IGFD::KeyExplorerFeature::prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC) + { + bool found = false; - auto& fdi = vFileDialogInternal.puFileManager; - if (!fdi.IsFilteredListEmpty()) - { - auto countFiles = fdi.GetFilteredListSize(); - for (size_t i = prLocateFileByInputChar_lastFileIdx; i < countFiles; i++) - { - auto nfo = fdi.GetFilteredFileAt(i); - if (nfo.use_count()) - { - if (nfo->fileNameExt_optimized[0] == vC || // lower case search //-V522 - nfo->fileNameExt[0] == vC) // maybe upper case search - { - //float p = ((float)i) * ImGui::GetTextLineHeightWithSpacing(); - float p = (float)((double)i / (double)countFiles) * ImGui::GetScrollMaxY(); - ImGui::SetScrollY(p); - prLocateFileByInputChar_lastFound = true; - prLocateFileByInputChar_lastFileIdx = i; - prStartFlashItem(prLocateFileByInputChar_lastFileIdx); + auto& fdi = vFileDialogInternal.puFileManager; + if (!fdi.IsFilteredListEmpty()) + { + auto countFiles = fdi.GetFilteredListSize(); + for (size_t i = prLocateFileByInputChar_lastFileIdx; i < countFiles; i++) + { + auto nfo = fdi.GetFilteredFileAt(i); + if (nfo.use_count()) + { + if (nfo->fileNameExt_optimized[0] == vC || // lower case search //-V522 + nfo->fileNameExt[0] == vC) // maybe upper case search + { + //float p = ((float)i) * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)i / (double)countFiles) * ImGui::GetScrollMaxY(); + ImGui::SetScrollY(p); + prLocateFileByInputChar_lastFound = true; + prLocateFileByInputChar_lastFileIdx = i; + prStartFlashItem(prLocateFileByInputChar_lastFileIdx); - auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); - if (infos.use_count()) - { - if (infos->fileType == 'd') //-V522 - { - if (fdi.puDLGDirectoryMode) // directory chooser - { - fdi.SelectFileName(vFileDialogInternal, infos); - } - } - else - { - fdi.SelectFileName(vFileDialogInternal, infos); - } + auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); + if (infos.use_count()) + { + if (infos->fileType == 'd') //-V522 + { + if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + } + else + { + fdi.SelectFileName(vFileDialogInternal, infos); + } - found = true; - break; - } - } - } - } - } + found = true; + break; + } + } + } + } + } - return found; - } + return found; + } - void IGFD::KeyExplorerFeature::prLocateByInputKey(FileDialogInternal& vFileDialogInternal) - { - ImGuiContext& g = *GImGui; - auto& fdi = vFileDialogInternal.puFileManager; - if (!g.ActiveId && !fdi.IsFilteredListEmpty()) - { - auto& queueChar = ImGui::GetIO().InputQueueCharacters; - auto countFiles = fdi.GetFilteredListSize(); + void IGFD::KeyExplorerFeature::prLocateByInputKey(FileDialogInternal& vFileDialogInternal) + { + ImGuiContext& g = *GImGui; + auto& fdi = vFileDialogInternal.puFileManager; + if (!g.ActiveId && !fdi.IsFilteredListEmpty()) + { + auto& queueChar = ImGui::GetIO().InputQueueCharacters; + auto countFiles = fdi.GetFilteredListSize(); - // point by char - if (!queueChar.empty()) - { - ImWchar c = queueChar.back(); - if (prLocateFileByInputChar_InputQueueCharactersSize != queueChar.size()) - { - if (c == prLocateFileByInputChar_lastChar) // next file starting with same char until - { - if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) - prLocateFileByInputChar_lastFileIdx++; - else - prLocateFileByInputChar_lastFileIdx = 0; - } + // point by char + if (!queueChar.empty()) + { + ImWchar c = queueChar.back(); + if (prLocateFileByInputChar_InputQueueCharactersSize != queueChar.size()) + { + if (c == prLocateFileByInputChar_lastChar) // next file starting with same char until + { + if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) + prLocateFileByInputChar_lastFileIdx++; + else + prLocateFileByInputChar_lastFileIdx = 0; + } - if (!prLocateItem_Loop(vFileDialogInternal, c)) - { - // not found, loop again from 0 this time - prLocateFileByInputChar_lastFileIdx = 0; - prLocateItem_Loop(vFileDialogInternal, c); - } + if (!prLocateItem_Loop(vFileDialogInternal, c)) + { + // not found, loop again from 0 this time + prLocateFileByInputChar_lastFileIdx = 0; + prLocateItem_Loop(vFileDialogInternal, c); + } - prLocateFileByInputChar_lastChar = c; - } - } + prLocateFileByInputChar_lastChar = c; + } + } - prLocateFileByInputChar_InputQueueCharactersSize = queueChar.size(); - } - } + prLocateFileByInputChar_InputQueueCharactersSize = queueChar.size(); + } + } - void IGFD::KeyExplorerFeature::prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID) - { - auto& fdi = vFileDialogInternal.puFileManager; - if (!fdi.IsFilteredListEmpty()) - { - bool canWeExplore = false; - bool hasNav = (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); - - ImGuiContext& g = *GImGui; - if (!hasNav && !g.ActiveId) // no nav and no activated inputs - canWeExplore = true; + void IGFD::KeyExplorerFeature::prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID) + { + auto& fdi = vFileDialogInternal.puFileManager; + if (!fdi.IsFilteredListEmpty()) + { + bool canWeExplore = false; + bool hasNav = (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); + + ImGuiContext& g = *GImGui; + if (!hasNav && !g.ActiveId) // no nav and no activated inputs + canWeExplore = true; - if (g.NavId && g.NavId == vListViewID) - { - if (ImGui::IsKeyPressedMap(IGFD_KEY_ENTER) || - ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) || - ImGui::IsKeyPressedMap(ImGuiKey_Space)) - { - ImGui::ActivateItem(vListViewID); - ImGui::SetActiveID(vListViewID, g.CurrentWindow); - } - } - - if (vListViewID == g.LastActiveId-1) // if listview id is the last acticated nav id (ImGui::ActivateItem(vListViewID);) - canWeExplore = true; + if (g.NavId && g.NavId == vListViewID) + { + if (ImGui::IsKeyPressedMap(IGFD_KEY_ENTER) || + ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter) || + ImGui::IsKeyPressedMap(ImGuiKey_Space)) + { + ImGui::ActivateItem(vListViewID); + ImGui::SetActiveID(vListViewID, g.CurrentWindow); + } + } + + if (vListViewID == g.LastActiveId-1) // if listview id is the last acticated nav id (ImGui::ActivateItem(vListViewID);) + canWeExplore = true; - if (canWeExplore) - { - if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) - { - ImGui::ClearActiveID(); - g.LastActiveId = 0; - } + if (canWeExplore) + { + if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + { + ImGui::ClearActiveID(); + g.LastActiveId = 0; + } - auto countFiles = fdi.GetFilteredListSize(); + auto countFiles = fdi.GetFilteredListSize(); - // explore - bool exploreByKey = false; - bool enterInDirectory = false; - bool exitDirectory = false; + // explore + bool exploreByKey = false; + bool enterInDirectory = false; + bool exitDirectory = false; - if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_UpArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_UP))) - { - exploreByKey = true; - if (prLocateFileByInputChar_lastFileIdx > 0) - prLocateFileByInputChar_lastFileIdx--; - else - prLocateFileByInputChar_lastFileIdx = countFiles - 1U; - } - else if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_DownArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_DOWN))) - { - exploreByKey = true; - if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) - prLocateFileByInputChar_lastFileIdx++; - else - prLocateFileByInputChar_lastFileIdx = 0U; - } - else if (ImGui::IsKeyReleased(IGFD_KEY_ENTER)) - { - exploreByKey = true; - enterInDirectory = true; - } - else if (ImGui::IsKeyReleased(IGFD_KEY_BACKSPACE)) - { - exploreByKey = true; - exitDirectory = true; - } + if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_UpArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_UP))) + { + exploreByKey = true; + if (prLocateFileByInputChar_lastFileIdx > 0) + prLocateFileByInputChar_lastFileIdx--; + else + prLocateFileByInputChar_lastFileIdx = countFiles - 1U; + } + else if ((hasNav && ImGui::IsKeyPressedMap(ImGuiKey_DownArrow)) || (!hasNav && ImGui::IsKeyPressed(IGFD_KEY_DOWN))) + { + exploreByKey = true; + if (prLocateFileByInputChar_lastFileIdx < countFiles - 1U) + prLocateFileByInputChar_lastFileIdx++; + else + prLocateFileByInputChar_lastFileIdx = 0U; + } + else if (ImGui::IsKeyReleased(IGFD_KEY_ENTER)) + { + exploreByKey = true; + enterInDirectory = true; + } + else if (ImGui::IsKeyReleased(IGFD_KEY_BACKSPACE)) + { + exploreByKey = true; + exitDirectory = true; + } - if (exploreByKey) - { - //float totalHeight = prFilteredFileList.size() * ImGui::GetTextLineHeightWithSpacing(); - float p = (float)((double)prLocateFileByInputChar_lastFileIdx / (double)(countFiles - 1U)) * ImGui::GetScrollMaxY();// seems not udpated in tables version outside tables - //float p = ((float)locateFileByInputChar_lastFileIdx) * ImGui::GetTextLineHeightWithSpacing(); - ImGui::SetScrollY(p); - prStartFlashItem(prLocateFileByInputChar_lastFileIdx); + if (exploreByKey) + { + //float totalHeight = prFilteredFileList.size() * ImGui::GetTextLineHeightWithSpacing(); + float p = (float)((double)prLocateFileByInputChar_lastFileIdx / (double)(countFiles - 1U)) * ImGui::GetScrollMaxY();// seems not udpated in tables version outside tables + //float p = ((float)locateFileByInputChar_lastFileIdx) * ImGui::GetTextLineHeightWithSpacing(); + ImGui::SetScrollY(p); + prStartFlashItem(prLocateFileByInputChar_lastFileIdx); - auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); - if (infos.use_count()) - { - if (infos->fileType == 'd') //-V522 - { - if (!fdi.puDLGDirectoryMode || enterInDirectory) - { - if (enterInDirectory) - { - if (fdi.SelectDirectory(infos)) - { - // changement de repertoire - vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); - if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) - { - prLocateFileByInputChar_lastFileIdx = 0; - } - } - } - } - else // directory chooser - { - fdi.SelectFileName(vFileDialogInternal, infos); - } - } - else - { - fdi.SelectFileName(vFileDialogInternal, infos); - } + auto infos = fdi.GetFilteredFileAt(prLocateFileByInputChar_lastFileIdx); + if (infos.use_count()) + { + if (infos->fileType == 'd') //-V522 + { + if (!fdi.puDLGDirectoryMode || enterInDirectory) + { + if (enterInDirectory) + { + if (fdi.SelectDirectory(infos)) + { + // changement de repertoire + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) + { + prLocateFileByInputChar_lastFileIdx = 0; + } + } + } + } + else // directory chooser + { + fdi.SelectFileName(vFileDialogInternal, infos); + } + } + else + { + fdi.SelectFileName(vFileDialogInternal, infos); + } - if (exitDirectory) - { - auto nfo = std::make_shared(); - nfo->fileNameExt = ".."; + if (exitDirectory) + { + auto nfo = std::make_shared(); + nfo->fileNameExt = ".."; - if (fdi.SelectDirectory(nfo)) - { - // changement de repertoire - vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); - if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) - { - prLocateFileByInputChar_lastFileIdx = 0; - } - } + if (fdi.SelectDirectory(nfo)) + { + // changement de repertoire + vFileDialogInternal.puFileManager.OpenCurrentPath(vFileDialogInternal); + if (prLocateFileByInputChar_lastFileIdx > countFiles - 1U) + { + prLocateFileByInputChar_lastFileIdx = 0; + } + } #ifdef WIN32 - else - { - if (fdi.GetComposerSize() == 1U) - { - if (fdi.GetDrives()) - { - fdi.ApplyFilteringOnFileList(vFileDialogInternal); - } - } - } + else + { + if (fdi.GetComposerSize() == 1U) + { + if (fdi.GetDrives()) + { + fdi.ApplyFilteringOnFileList(vFileDialogInternal); + } + } + } #endif // WIN32 - } - } - } - } - } - } + } + } + } + } + } + } - bool IGFD::KeyExplorerFeature::prFlashableSelectable(const char* label, bool selected, - ImGuiSelectableFlags flags, bool vFlashing, const ImVec2& size_arg) - { - using namespace ImGui; + bool IGFD::KeyExplorerFeature::prFlashableSelectable(const char* label, bool selected, + ImGuiSelectableFlags flags, bool vFlashing, const ImVec2& size_arg) + { + using namespace ImGui; - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; + ImGuiContext& g = *GImGui; + const ImGuiStyle& style = g.Style; - // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. - ImGuiID id = window->GetID(label); - ImVec2 label_size = CalcTextSize(label, nullptr, true); - ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); //-V550 - ImVec2 pos = window->DC.CursorPos; - pos.y += window->DC.CurrLineTextBaseOffset; - ItemSize(size, 0.0f); + // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle. + ImGuiID id = window->GetID(label); + ImVec2 label_size = CalcTextSize(label, nullptr, true); + ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y); //-V550 + ImVec2 pos = window->DC.CursorPos; + pos.y += window->DC.CurrLineTextBaseOffset; + ItemSize(size, 0.0f); - // Fill horizontal space - // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. - const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; - const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; - const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; - if (fabs(size_arg.x) < FLT_EPSILON || (flags & ImGuiSelectableFlags_SpanAvailWidth)) - size.x = ImMax(label_size.x, max_x - min_x); + // Fill horizontal space + // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets. + const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0; + const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x; + const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + if (fabs(size_arg.x) < FLT_EPSILON || (flags & ImGuiSelectableFlags_SpanAvailWidth)) + size.x = ImMax(label_size.x, max_x - min_x); - // Text stays at the submission position, but bounding box may be extended on both sides - const ImVec2 text_min = pos; - const ImVec2 text_max(min_x + size.x, pos.y + size.y); + // Text stays at the submission position, but bounding box may be extended on both sides + const ImVec2 text_min = pos; + const ImVec2 text_max(min_x + size.x, pos.y + size.y); - // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. - ImRect bb(min_x, pos.y, text_max.x, text_max.y); - if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) - { - const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; - const float spacing_y = style.ItemSpacing.y; - const float spacing_L = IM_FLOOR(spacing_x * 0.50f); - const float spacing_U = IM_FLOOR(spacing_y * 0.50f); - bb.Min.x -= spacing_L; - bb.Min.y -= spacing_U; - bb.Max.x += (spacing_x - spacing_L); - bb.Max.y += (spacing_y - spacing_U); - } - //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } + // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable. + ImRect bb(min_x, pos.y, text_max.x, text_max.y); + if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0) + { + const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x; + const float spacing_y = style.ItemSpacing.y; + const float spacing_L = IM_FLOOR(spacing_x * 0.50f); + const float spacing_U = IM_FLOOR(spacing_y * 0.50f); + bb.Min.x -= spacing_L; + bb.Min.y -= spacing_U; + bb.Max.x += (spacing_x - spacing_L); + bb.Max.y += (spacing_y - spacing_U); + } + //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); } - // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. - const float backup_clip_rect_min_x = window->ClipRect.Min.x; - const float backup_clip_rect_max_x = window->ClipRect.Max.x; - if (span_all_columns) - { - window->ClipRect.Min.x = window->ParentWorkRect.Min.x; - window->ClipRect.Max.x = window->ParentWorkRect.Max.x; - } + // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable.. + const float backup_clip_rect_min_x = window->ClipRect.Min.x; + const float backup_clip_rect_max_x = window->ClipRect.Max.x; + if (span_all_columns) + { + window->ClipRect.Min.x = window->ParentWorkRect.Min.x; + window->ClipRect.Max.x = window->ParentWorkRect.Max.x; + } - bool item_add; - const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; - if (disabled_item) - { - ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; - g.CurrentItemFlags |= ImGuiItemFlags_Disabled; - item_add = ItemAdd(bb, id); - g.CurrentItemFlags = backup_item_flags; - } - else - { - item_add = ItemAdd(bb, id); - } + bool item_add; + const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0; + if (disabled_item) + { + ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + g.CurrentItemFlags |= ImGuiItemFlags_Disabled; + item_add = ItemAdd(bb, id); + g.CurrentItemFlags = backup_item_flags; + } + else + { + item_add = ItemAdd(bb, id); + } - if (span_all_columns) - { - window->ClipRect.Min.x = backup_clip_rect_min_x; - window->ClipRect.Max.x = backup_clip_rect_max_x; - } + if (span_all_columns) + { + window->ClipRect.Min.x = backup_clip_rect_min_x; + window->ClipRect.Max.x = backup_clip_rect_max_x; + } - if (!item_add) - return false; + if (!item_add) + return false; - const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; - if (disabled_item && !disabled_global) // Only testing this as an optimization - BeginDisabled(true); + const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0; + if (disabled_item && !disabled_global) // Only testing this as an optimization + BeginDisabled(true); - // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, - // which would be advantageous since most selectable are not selected. - if (span_all_columns && window->DC.CurrentColumns) - PushColumnsBackground(); - else if (span_all_columns && g.CurrentTable) - TablePushBackgroundChannel(); + // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only, + // which would be advantageous since most selectable are not selected. + if (span_all_columns && window->DC.CurrentColumns) + PushColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePushBackgroundChannel(); - // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries - ImGuiButtonFlags button_flags = 0; - if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } - if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } - if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } - if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } - if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } + // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries + ImGuiButtonFlags button_flags = 0; + if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; } + if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; } + if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; } + if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; } + if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; } - const bool was_selected = selected; - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + const bool was_selected = selected; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); - // Auto-select when moved into - // - This will be more fully fleshed in the range-select branch - // - This is not exposed as it won't nicely work with some user side handling of shift/control - // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons - // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) - // - (2) usage will fail with clipped items - // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. - if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) - if (g.NavJustMovedToId == id) - selected = pressed = true; + // Auto-select when moved into + // - This will be more fully fleshed in the range-select branch + // - This is not exposed as it won't nicely work with some user side handling of shift/control + // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons + // - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope()) + // - (2) usage will fail with clipped items + // The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API. + if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == window->DC.NavFocusScopeIdCurrent) + if (g.NavJustMovedToId == id) + selected = pressed = true; - // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard - if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) - { - if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) - { - SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); - g.NavDisableHighlight = true; - } - } - if (pressed) - MarkItemEdited(id); + // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with gamepad/keyboard + if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover))) + { + if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent) + { + SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos)); + g.NavDisableHighlight = true; + } + } + if (pressed) + MarkItemEdited(id); - if (flags & ImGuiSelectableFlags_AllowItemOverlap) - SetItemAllowOverlap(); + if (flags & ImGuiSelectableFlags_AllowItemOverlap) + SetItemAllowOverlap(); - // In this branch, Selectable() cannot toggle the selection so this will never trigger. - if (selected != was_selected) //-V547 - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; + // In this branch, Selectable() cannot toggle the selection so this will never trigger. + if (selected != was_selected) //-V547 + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection; - // Render - if ((held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) || vFlashing) - hovered = true; - if (hovered || selected) - { - const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); - RenderFrame(bb.Min, bb.Max, col, false, 0.0f); - } - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + // Render + if ((held && (flags & ImGuiSelectableFlags_DrawHoveredWhenHeld)) || vFlashing) + hovered = true; + if (hovered || selected) + { + const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); + RenderFrame(bb.Min, bb.Max, col, false, 0.0f); + } + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); - if (span_all_columns && window->DC.CurrentColumns) - PopColumnsBackground(); - else if (span_all_columns && g.CurrentTable) - TablePopBackgroundChannel(); + if (span_all_columns && window->DC.CurrentColumns) + PopColumnsBackground(); + else if (span_all_columns && g.CurrentTable) + TablePopBackgroundChannel(); - RenderTextClipped(text_min, text_max, label, nullptr, &label_size, style.SelectableTextAlign, &bb); + RenderTextClipped(text_min, text_max, label, nullptr, &label_size, style.SelectableTextAlign, &bb); - // Automatically close popups - if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) - CloseCurrentPopup(); + // Automatically close popups + if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.LastItemData.InFlags & ImGuiItemFlags_SelectableDontClosePopup)) + CloseCurrentPopup(); - if (disabled_item && !disabled_global) - EndDisabled(); + if (disabled_item && !disabled_global) + EndDisabled(); - IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); - return pressed; //-V1020 - } + IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags); + return pressed; //-V1020 + } - void IGFD::KeyExplorerFeature::prStartFlashItem(size_t vIdx) - { - prFlashAlpha = 1.0f; - prFlashedItem = vIdx; - } + void IGFD::KeyExplorerFeature::prStartFlashItem(size_t vIdx) + { + prFlashAlpha = 1.0f; + prFlashedItem = vIdx; + } - bool IGFD::KeyExplorerFeature::prBeginFlashItem(size_t vIdx) - { - bool res = false; + bool IGFD::KeyExplorerFeature::prBeginFlashItem(size_t vIdx) + { + bool res = false; - if (prFlashedItem == vIdx && - std::abs(prFlashAlpha - 0.0f) > 0.00001f) - { - prFlashAlpha -= prFlashAlphaAttenInSecs * ImGui::GetIO().DeltaTime; - if (prFlashAlpha < 0.0f) prFlashAlpha = 0.0f; + if (prFlashedItem == vIdx && + std::abs(prFlashAlpha - 0.0f) > 0.00001f) + { + prFlashAlpha -= prFlashAlphaAttenInSecs * ImGui::GetIO().DeltaTime; + if (prFlashAlpha < 0.0f) prFlashAlpha = 0.0f; - ImVec4 hov = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered); - hov.w = prFlashAlpha; - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hov); - res = true; - } + ImVec4 hov = ImGui::GetStyleColorVec4(ImGuiCol_HeaderHovered); + hov.w = prFlashAlpha; + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, hov); + res = true; + } - return res; - } + return res; + } - void IGFD::KeyExplorerFeature::prEndFlashItem() - { - ImGui::PopStyleColor(); - } + void IGFD::KeyExplorerFeature::prEndFlashItem() + { + ImGui::PopStyleColor(); + } - void IGFD::KeyExplorerFeature::SetFlashingAttenuationInSeconds(float vAttenValue) - { - prFlashAlphaAttenInSecs = 1.0f / ImMax(vAttenValue, 0.01f); - } + void IGFD::KeyExplorerFeature::SetFlashingAttenuationInSeconds(float vAttenValue) + { + prFlashAlphaAttenInSecs = 1.0f / ImMax(vAttenValue, 0.01f); + } #endif // USE_EXPLORATION_BY_KEYS - ///////////////////////////////////////////////////////////////////////////////////// - //// FILE DIALOG CONSTRUCTOR / DESTRUCTOR /////////////////////////////////////////// - ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + //// FILE DIALOG CONSTRUCTOR / DESTRUCTOR /////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// - IGFD::FileDialog::FileDialog() : BookMarkFeature(), KeyExplorerFeature(), ThumbnailFeature() {DpiScale=1.0f; singleClickSel=false; mobileMode=false;} - IGFD::FileDialog::~FileDialog() = default; + IGFD::FileDialog::FileDialog() : BookMarkFeature(), KeyExplorerFeature(), ThumbnailFeature() {DpiScale=1.0f; singleClickSel=false; mobileMode=false;} + IGFD::FileDialog::~FileDialog() = default; - ////////////////////////////////////////////////////////////////////////////////////////////////// - ///// FILE DIALOG STANDARD DIALOG //////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG STANDARD DIALOG //////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// - // path and fileNameExt can be specified - void IGFD::FileDialog::OpenDialog( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vPath, - const std::string& vFileName, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - prFileDialogInternal.ResetForNewDialog(); + prFileDialogInternal.ResetForNewDialog(); - prFileDialogInternal.puDLGkey = vKey; - prFileDialogInternal.puDLGtitle = vTitle; - prFileDialogInternal.puDLGuserDatas = vUserDatas; - prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; prFileDialogInternal.puDLGselFun = vSelectFun; - prFileDialogInternal.puDLGoptionsPane = nullptr; - prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; - prFileDialogInternal.puDLGmodal = false; + prFileDialogInternal.puDLGoptionsPane = nullptr; + prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; + prFileDialogInternal.puDLGmodal = false; - prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); - prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); - prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); - if (vPath.empty()) - prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); - else - prFileDialogInternal.puFileManager.puDLGpath = vPath; - prFileDialogInternal.puFileManager.SetCurrentPath(vPath); - prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; - prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + if (vPath.empty()) + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + else + prFileDialogInternal.puFileManager.puDLGpath = vPath; + prFileDialogInternal.puFileManager.SetCurrentPath(vPath); + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; + prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); - prFileDialogInternal.puFileManager.ClearAll(); - - prFileDialogInternal.puShowDialog = true; // open dialog - } + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; // open dialog + } - // path and filename are obtained from filePathName - void IGFD::FileDialog::OpenDialog( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vFilePathName, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - prFileDialogInternal.ResetForNewDialog(); + prFileDialogInternal.ResetForNewDialog(); - prFileDialogInternal.puDLGkey = vKey; - prFileDialogInternal.puDLGtitle = vTitle; - prFileDialogInternal.puDLGoptionsPane = nullptr; - prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; - prFileDialogInternal.puDLGuserDatas = vUserDatas; - prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGoptionsPane = nullptr; + prFileDialogInternal.puDLGoptionsPaneWidth = 0.0f; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; prFileDialogInternal.puDLGselFun = vSelectFun; - prFileDialogInternal.puDLGmodal = false; + prFileDialogInternal.puDLGmodal = false; - auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); - if (ps.isOk) - { - prFileDialogInternal.puFileManager.puDLGpath = ps.path; - prFileDialogInternal.puFileManager.SetDefaultFileName(""); - prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; - } - else - { - prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); - prFileDialogInternal.puFileManager.SetDefaultFileName(""); - prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); - } + auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); + if (ps.isOk) + { + prFileDialogInternal.puFileManager.puDLGpath = ps.path; + prFileDialogInternal.puFileManager.SetDefaultFileName(""); + prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; + } + else + { + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + prFileDialogInternal.puFileManager.SetDefaultFileName(""); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + } - prFileDialogInternal.puFilterManager.ParseFilters(vFilters); - prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( - prFileDialogInternal.puFilterManager.puDLGdefaultExt); - - prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( + prFileDialogInternal.puFilterManager.puDLGdefaultExt); + + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); - prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); - prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 - prFileDialogInternal.puFileManager.ClearAll(); - - prFileDialogInternal.puShowDialog = true; - } + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; + } - // with pane - // path and fileNameExt can be specified - void IGFD::FileDialog::OpenDialog( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vPath, - const std::string& vFileName, - const PaneFun& vSidePane, - const float& vSidePaneWidth, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // with pane + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - prFileDialogInternal.ResetForNewDialog(); + prFileDialogInternal.ResetForNewDialog(); - prFileDialogInternal.puDLGkey = vKey; - prFileDialogInternal.puDLGtitle = vTitle; - prFileDialogInternal.puDLGuserDatas = vUserDatas; - prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; prFileDialogInternal.puDLGselFun = vSelectFun; - prFileDialogInternal.puDLGoptionsPane = vSidePane; - prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; - prFileDialogInternal.puDLGmodal = false; + prFileDialogInternal.puDLGoptionsPane = vSidePane; + prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; + prFileDialogInternal.puDLGmodal = false; - prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); - prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); - prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; - prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); - if (vPath.empty()) - prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); - else - prFileDialogInternal.puFileManager.puDLGpath = vPath; + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = (size_t)vCountSelectionMax; + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + if (vPath.empty()) + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + else + prFileDialogInternal.puFileManager.puDLGpath = vPath; - prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); - prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); + prFileDialogInternal.puFileManager.SetDefaultFileName(vFileName); - prFileDialogInternal.puFileManager.ClearAll(); - - prFileDialogInternal.puShowDialog = true; // open dialog - } + prFileDialogInternal.puFileManager.ClearAll(); + + prFileDialogInternal.puShowDialog = true; // open dialog + } - // with pane - // path and filename are obtained from filePathName - void IGFD::FileDialog::OpenDialog( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vFilePathName, - const PaneFun& vSidePane, - const float& vSidePaneWidth, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // with pane + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenDialog( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - prFileDialogInternal.ResetForNewDialog(); + prFileDialogInternal.ResetForNewDialog(); - prFileDialogInternal.puDLGkey = vKey; - prFileDialogInternal.puDLGtitle = vTitle; - prFileDialogInternal.puDLGoptionsPane = vSidePane; - prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; - prFileDialogInternal.puDLGuserDatas = vUserDatas; - prFileDialogInternal.puDLGflags = vFlags; + prFileDialogInternal.puDLGkey = vKey; + prFileDialogInternal.puDLGtitle = vTitle; + prFileDialogInternal.puDLGoptionsPane = vSidePane; + prFileDialogInternal.puDLGoptionsPaneWidth = vSidePaneWidth; + prFileDialogInternal.puDLGuserDatas = vUserDatas; + prFileDialogInternal.puDLGflags = vFlags; prFileDialogInternal.puDLGselFun = vSelectFun; - prFileDialogInternal.puDLGmodal = false; + prFileDialogInternal.puDLGmodal = false; - auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); - if (ps.isOk) - { - prFileDialogInternal.puFileManager.puDLGpath = ps.path; - prFileDialogInternal.puFileManager.SetDefaultFileName(vFilePathName); - prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; - } - else - { - prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); - prFileDialogInternal.puFileManager.SetDefaultFileName(""); - prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); - } + auto ps = IGFD::Utils::ParsePathFileName(vFilePathName); + if (ps.isOk) + { + prFileDialogInternal.puFileManager.puDLGpath = ps.path; + prFileDialogInternal.puFileManager.SetDefaultFileName(vFilePathName); + prFileDialogInternal.puFilterManager.puDLGdefaultExt = "." + ps.ext; + } + else + { + prFileDialogInternal.puFileManager.puDLGpath = prFileDialogInternal.puFileManager.GetCurrentPath(); + prFileDialogInternal.puFileManager.SetDefaultFileName(""); + prFileDialogInternal.puFilterManager.puDLGdefaultExt.clear(); + } - prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); + prFileDialogInternal.puFileManager.SetCurrentPath(prFileDialogInternal.puFileManager.puDLGpath); - prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 - prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); - prFileDialogInternal.puFilterManager.ParseFilters(vFilters); - prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( - prFileDialogInternal.puFilterManager.puDLGdefaultExt); + prFileDialogInternal.puFileManager.puDLGcountSelectionMax = vCountSelectionMax; //-V101 + prFileDialogInternal.puFileManager.puDLGDirectoryMode = (vFilters == nullptr); + prFileDialogInternal.puFilterManager.ParseFilters(vFilters); + prFileDialogInternal.puFilterManager.SetSelectedFilterWithExt( + prFileDialogInternal.puFilterManager.puDLGdefaultExt); - prFileDialogInternal.puFileManager.ClearAll(); + prFileDialogInternal.puFileManager.ClearAll(); - prFileDialogInternal.puShowDialog = true; - } + prFileDialogInternal.puShowDialog = true; + } - ////////////////////////////////////////////////////////////////////////////////////////////////// - ///// FILE DIALOG MODAL DIALOG /////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG MODAL DIALOG /////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// - void IGFD::FileDialog::OpenModal( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vPath, - const std::string& vFileName, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - OpenDialog( - vKey, vTitle, vFilters, - vPath, vFileName, - vCountSelectionMax, vUserDatas, vFlags, vSelectFun); + OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); - prFileDialogInternal.puDLGmodal = true; - } + prFileDialogInternal.puDLGmodal = true; + } - void IGFD::FileDialog::OpenModal( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vFilePathName, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - OpenDialog( - vKey, vTitle, vFilters, - vFilePathName, - vCountSelectionMax, vUserDatas, vFlags, vSelectFun); + OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); - prFileDialogInternal.puDLGmodal = true; - } + prFileDialogInternal.puDLGmodal = true; + } - // with pane - // path and fileNameExt can be specified - void IGFD::FileDialog::OpenModal( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vPath, - const std::string& vFileName, - const PaneFun& vSidePane, - const float& vSidePaneWidth, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // with pane + // path and fileNameExt can be specified + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vPath, + const std::string& vFileName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - OpenDialog( - vKey, vTitle, vFilters, - vPath, vFileName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, vFlags, vSelectFun); + OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); - prFileDialogInternal.puDLGmodal = true; - } + prFileDialogInternal.puDLGmodal = true; + } - // with pane - // path and filename are obtained from filePathName - void IGFD::FileDialog::OpenModal( - const std::string& vKey, - const std::string& vTitle, - const char* vFilters, - const std::string& vFilePathName, - const PaneFun& vSidePane, - const float& vSidePaneWidth, - const int& vCountSelectionMax, - UserDatas vUserDatas, - ImGuiFileDialogFlags vFlags, + // with pane + // path and filename are obtained from filePathName + void IGFD::FileDialog::OpenModal( + const std::string& vKey, + const std::string& vTitle, + const char* vFilters, + const std::string& vFilePathName, + const PaneFun& vSidePane, + const float& vSidePaneWidth, + const int& vCountSelectionMax, + UserDatas vUserDatas, + ImGuiFileDialogFlags vFlags, SelectFun vSelectFun) - { - if (prFileDialogInternal.puShowDialog) // if already opened, quit - return; + { + if (prFileDialogInternal.puShowDialog) // if already opened, quit + return; - OpenDialog( - vKey, vTitle, vFilters, - vFilePathName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, vFlags, vSelectFun); + OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, vFlags, vSelectFun); - prFileDialogInternal.puDLGmodal = true; - } + prFileDialogInternal.puDLGmodal = true; + } - ////////////////////////////////////////////////////////////////////////////////////////////////// - ///// FILE DIALOG DISPLAY FUNCTION /////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// + ///// FILE DIALOG DISPLAY FUNCTION /////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////// - bool IGFD::FileDialog::Display(const std::string& vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) - { - bool res = false; + bool IGFD::FileDialog::Display(const std::string& vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) + { + bool res = false; - if (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey) - { - if (prFileDialogInternal.puUseCustomLocale) - setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleBegin.c_str()); + if (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey) + { + if (prFileDialogInternal.puUseCustomLocale) + setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleBegin.c_str()); - auto& fdFile = prFileDialogInternal.puFileManager; - auto& fdFilter = prFileDialogInternal.puFilterManager; + auto& fdFile = prFileDialogInternal.puFileManager; + auto& fdFilter = prFileDialogInternal.puFilterManager; - static ImGuiWindowFlags flags; + static ImGuiWindowFlags flags; - // to be sure than only one dialog is displayed per frame - ImGuiContext& g = *GImGui; - if (g.FrameCount == prFileDialogInternal.puLastImGuiFrameCount) // one instance was displayed this frame before for this key +> quit - return res; - prFileDialogInternal.puLastImGuiFrameCount = g.FrameCount; // mark this instance as used this frame + // to be sure than only one dialog is displayed per frame + ImGuiContext& g = *GImGui; + if (g.FrameCount == prFileDialogInternal.puLastImGuiFrameCount) // one instance was displayed this frame before for this key +> quit + return res; + prFileDialogInternal.puLastImGuiFrameCount = g.FrameCount; // mark this instance as used this frame - std::string name = prFileDialogInternal.puDLGtitle + "##" + prFileDialogInternal.puDLGkey; - if (prFileDialogInternal.puName != name) - { - fdFile.ClearComposer(); - fdFile.ClearFileLists(); - flags = vFlags; - } + std::string name = prFileDialogInternal.puDLGtitle + "##" + prFileDialogInternal.puDLGkey; + if (prFileDialogInternal.puName != name) + { + fdFile.ClearComposer(); + fdFile.ClearFileLists(); + flags = vFlags; + } - NewFrame(); + NewFrame(); #ifdef IMGUI_HAS_VIEWPORT - if (!ImGui::GetIO().ConfigViewportsNoDecoration) - { - // https://github.com/ocornut/imgui/issues/4534 - ImGuiWindowClass window_class; - window_class.ViewportFlagsOverrideClear = ImGuiViewportFlags_NoDecoration; - ImGui::SetNextWindowClass(&window_class); - } + if (!ImGui::GetIO().ConfigViewportsNoDecoration) + { + // https://github.com/ocornut/imgui/issues/4534 + ImGuiWindowClass window_class; + window_class.ViewportFlagsOverrideClear = ImGuiViewportFlags_NoDecoration; + ImGui::SetNextWindowClass(&window_class); + } #endif // IMGUI_HAS_VIEWPORT - ImGui::SetNextWindowSizeConstraints(vMinSize, vMaxSize); + ImGui::SetNextWindowSizeConstraints(vMinSize, vMaxSize); - bool beg = false; - if (prFileDialogInternal.puDLGmodal && - !prFileDialogInternal.puOkResultToConfirm) // disable modal because the confirm dialog for overwrite is a new modal - { - ImGui::OpenPopup(name.c_str()); - beg = ImGui::BeginPopupModal(name.c_str(), (bool*)nullptr, - flags | ImGuiWindowFlags_NoScrollbar); - } - else - { - beg = ImGui::Begin(name.c_str(), (bool*)nullptr, flags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); - } - if (beg) - { + bool beg = false; + if (prFileDialogInternal.puDLGmodal && + !prFileDialogInternal.puOkResultToConfirm) // disable modal because the confirm dialog for overwrite is a new modal + { + ImGui::OpenPopup(name.c_str()); + beg = ImGui::BeginPopupModal(name.c_str(), (bool*)nullptr, + flags | ImGuiWindowFlags_NoScrollbar); + } + else + { + beg = ImGui::Begin(name.c_str(), (bool*)nullptr, flags | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking); + } + if (beg) + { ImGui::SetWindowPos(ImVec2((ImGui::GetMainViewport()->Size.x-ImGui::GetWindowWidth())*0.5f,(ImGui::GetMainViewport()->Size.y-ImGui::GetWindowHeight())*0.5f)); if (ImGui::GetWindowSize().xViewport->Idx != 0) - flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar; - else - flags = vFlags; - } + // if decoration is enabled we disable the resizing feature of imgui for avoid crash with SDL2 and GLFW3 + if (ImGui::GetIO().ConfigViewportsNoDecoration) + { + flags = vFlags; + } + else + { + auto win = ImGui::GetCurrentWindowRead(); + if (win->Viewport->Idx != 0) + flags |= ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar; + else + flags = vFlags; + } #endif // IMGUI_HAS_VIEWPORT - prFileDialogInternal.puName = name; //-V820 - puAnyWindowsHovered |= ImGui::IsWindowHovered(); + prFileDialogInternal.puName = name; //-V820 + puAnyWindowsHovered |= ImGui::IsWindowHovered(); - if (fdFile.puDLGpath.empty()) - fdFile.puDLGpath = "."; // defaut path is '.' + if (fdFile.puDLGpath.empty()) + fdFile.puDLGpath = "."; // defaut path is '.' - fdFilter.SetDefaultFilterIfNotDefined(); + fdFilter.SetDefaultFilterIfNotDefined(); - // init list of files - if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives && !fdFile.fileListActuallyEmpty) - { - IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path - if (!fdFile.puDLGDefaultFileName.empty()) - { - fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName); - fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt); - } else if (fdFile.puDLGDirectoryMode) { // directory mode - fdFile.SetDefaultFileName("."); + // init list of files + if (fdFile.IsFileListEmpty() && !fdFile.puShowDrives && !fdFile.fileListActuallyEmpty) + { + IGFD::Utils::ReplaceString(fdFile.puDLGDefaultFileName, fdFile.puDLGpath, ""); // local path + if (!fdFile.puDLGDefaultFileName.empty()) + { + fdFile.SetDefaultFileName(fdFile.puDLGDefaultFileName); + fdFilter.SetSelectedFilterWithExt(fdFilter.puDLGdefaultExt); + } else if (fdFile.puDLGDirectoryMode) { // directory mode + fdFile.SetDefaultFileName("."); } logV("IGFD: fdFile.IsFileListEmpty() and !fdFile.puShowDrives"); - fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath); - } + fdFile.ScanDir(prFileDialogInternal, fdFile.puDLGpath); + } - // draw dialog parts - prDrawHeader(); // bookmark, directory, path - res = prDrawContent(); // bookmark, files view, side pane - bool res1 = prDrawFooter(); // file field, filter combobox, ok/cancel buttons + // draw dialog parts + prDrawHeader(); // bookmark, directory, path + res = prDrawContent(); // bookmark, files view, side pane + bool res1 = prDrawFooter(); // file field, filter combobox, ok/cancel buttons if (!res) res=res1; - EndFrame(); + EndFrame(); - // for display in dialog center, the confirm to overwrite dlg - prFileDialogInternal.puDialogCenterPos = ImGui::GetCurrentWindowRead()->ContentRegionRect.GetCenter(); + // for display in dialog center, the confirm to overwrite dlg + prFileDialogInternal.puDialogCenterPos = ImGui::GetCurrentWindowRead()->ContentRegionRect.GetCenter(); - // when the confirm to overwrite dialog will appear we need to - // disable the modal mode of the main file dialog - // see prOkResultToConfirm under - if (prFileDialogInternal.puDLGmodal && - !prFileDialogInternal.puOkResultToConfirm) - ImGui::EndPopup(); - } + // when the confirm to overwrite dialog will appear we need to + // disable the modal mode of the main file dialog + // see prOkResultToConfirm under + if (prFileDialogInternal.puDLGmodal && + !prFileDialogInternal.puOkResultToConfirm) + ImGui::EndPopup(); + } - // same things here regarding prOkResultToConfirm - if (!prFileDialogInternal.puDLGmodal || prFileDialogInternal.puOkResultToConfirm) - ImGui::End(); + // same things here regarding prOkResultToConfirm + if (!prFileDialogInternal.puDLGmodal || prFileDialogInternal.puOkResultToConfirm) + ImGui::End(); - // confirm the result and show the confirm to overwrite dialog if needed - res = prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(res, vFlags); - - if (prFileDialogInternal.puUseCustomLocale) - setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleEnd.c_str()); - } + // confirm the result and show the confirm to overwrite dialog if needed + res = prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(res, vFlags); + + if (prFileDialogInternal.puUseCustomLocale) + setlocale(prFileDialogInternal.puLocaleCategory, prFileDialogInternal.puLocaleEnd.c_str()); + } - return res; - } + return res; + } - void IGFD::FileDialog::NewFrame() - { - prFileDialogInternal.NewFrame(); - NewThumbnailFrame(prFileDialogInternal); - } - - void IGFD::FileDialog::EndFrame() - { - EndThumbnailFrame(prFileDialogInternal); - prFileDialogInternal.EndFrame(); - - } - void IGFD::FileDialog::QuitFrame() - { - QuitThumbnailFrame(prFileDialogInternal); - } + void IGFD::FileDialog::NewFrame() + { + prFileDialogInternal.NewFrame(); + NewThumbnailFrame(prFileDialogInternal); + } + + void IGFD::FileDialog::EndFrame() + { + EndThumbnailFrame(prFileDialogInternal); + prFileDialogInternal.EndFrame(); + + } + void IGFD::FileDialog::QuitFrame() + { + QuitThumbnailFrame(prFileDialogInternal); + } - void IGFD::FileDialog::prDrawHeader() - { + void IGFD::FileDialog::prDrawHeader() + { #ifdef USE_BOOKMARK - prDrawBookmarkButton(); - ImGui::SameLine(); + prDrawBookmarkButton(); + ImGui::SameLine(); #endif // USE_BOOKMARK - prFileDialogInternal.puFileManager.DrawDirectoryCreation(prFileDialogInternal); - ImGui::SameLine(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - ImGui::SameLine(); - prFileDialogInternal.puFileManager.DrawPathComposer(prFileDialogInternal); + prFileDialogInternal.puFileManager.DrawDirectoryCreation(prFileDialogInternal); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + prFileDialogInternal.puFileManager.DrawPathComposer(prFileDialogInternal); #ifdef USE_THUMBNAILS - if (!(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode)) - { - prDrawDisplayModeToolBar(); - ImGui::SameLine(); - ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); - ImGui::SameLine(); - } + if (!(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode)) + { + prDrawDisplayModeToolBar(); + ImGui::SameLine(); + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + ImGui::SameLine(); + } #endif // USE_THUMBNAILS - prFileDialogInternal.puSearchManager.DrawSearchBar(prFileDialogInternal); - } + prFileDialogInternal.puSearchManager.DrawSearchBar(prFileDialogInternal); + } - bool IGFD::FileDialog::prDrawContent() - { + bool IGFD::FileDialog::prDrawContent() + { bool escape = false; - ImVec2 size = ImGui::GetContentRegionAvail() - ImVec2(0.0f, prFileDialogInternal.puFooterHeight); + ImVec2 size = ImGui::GetContentRegionAvail() - ImVec2(0.0f, prFileDialogInternal.puFooterHeight); #ifdef USE_BOOKMARK - if (prBookmarkPaneShown) - { - //size.x -= prBookmarkWidth; - float otherWidth = size.x - prBookmarkWidth; - ImGui::PushID("##splitterbookmark"); - IGFD::Utils::Splitter(true, 4.0f, - &prBookmarkWidth, &otherWidth, 10.0f, - 10.0f + prFileDialogInternal.puDLGoptionsPaneWidth, size.y); - ImGui::PopID(); - size.x -= otherWidth; - prDrawBookmarkPane(prFileDialogInternal, size); - ImGui::SameLine(); - } + if (prBookmarkPaneShown) + { + //size.x -= prBookmarkWidth; + float otherWidth = size.x - prBookmarkWidth; + ImGui::PushID("##splitterbookmark"); + IGFD::Utils::Splitter(true, 4.0f, + &prBookmarkWidth, &otherWidth, 10.0f, + 10.0f + prFileDialogInternal.puDLGoptionsPaneWidth, size.y); + ImGui::PopID(); + size.x -= otherWidth; + prDrawBookmarkPane(prFileDialogInternal, size); + ImGui::SameLine(); + } #endif // USE_BOOKMARK - size.x = ImGui::GetContentRegionAvail().x - prFileDialogInternal.puDLGoptionsPaneWidth; + size.x = ImGui::GetContentRegionAvail().x - prFileDialogInternal.puDLGoptionsPaneWidth; - if (prFileDialogInternal.puDLGoptionsPane) - { - ImGui::PushID("##splittersidepane"); - IGFD::Utils::Splitter(true, 4.0f, &size.x, &prFileDialogInternal.puDLGoptionsPaneWidth, 10.0f, 10.0f, size.y); - ImGui::PopID(); - } + if (prFileDialogInternal.puDLGoptionsPane) + { + ImGui::PushID("##splittersidepane"); + IGFD::Utils::Splitter(true, 4.0f, &size.x, &prFileDialogInternal.puDLGoptionsPaneWidth, 10.0f, 10.0f, size.y); + ImGui::PopID(); + } #ifdef USE_THUMBNAILS - if (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode) - { - prDrawFileListView(size); - } - else - { - switch (prDisplayMode) - { - case DisplayModeEnum::FILE_LIST: - prDrawFileListView(size); - break; - case DisplayModeEnum::THUMBNAILS_LIST: - prDrawThumbnailsListView(size); - break; - case DisplayModeEnum::THUMBNAILS_GRID: - prDrawThumbnailsGridView(size); - } - } + if (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_DisableThumbnailMode) + { + prDrawFileListView(size); + } + else + { + switch (prDisplayMode) + { + case DisplayModeEnum::FILE_LIST: + prDrawFileListView(size); + break; + case DisplayModeEnum::THUMBNAILS_LIST: + prDrawThumbnailsListView(size); + break; + case DisplayModeEnum::THUMBNAILS_GRID: + prDrawThumbnailsGridView(size); + } + } #else - escape = prDrawFileListView(size); + escape = prDrawFileListView(size); #endif // USE_THUMBNAILS - if (prFileDialogInternal.puDLGoptionsPane) - { - prDrawSidePane(size.y); - } + if (prFileDialogInternal.puDLGoptionsPane) + { + prDrawSidePane(size.y); + } return escape; - } + } - bool IGFD::FileDialog::prDrawFooter() - { - auto& fdFile = prFileDialogInternal.puFileManager; - - float posY = ImGui::GetCursorPos().y; // height of last bar calc + bool IGFD::FileDialog::prDrawFooter() + { + auto& fdFile = prFileDialogInternal.puFileManager; + + float posY = ImGui::GetCursorPos().y; // height of last bar calc - if (!fdFile.puDLGDirectoryMode) - ImGui::Text(fileNameString); - else // directory chooser - ImGui::Text(dirNameString); + if (!fdFile.puDLGDirectoryMode) + ImGui::Text(fileNameString); + else // directory chooser + ImGui::Text(dirNameString); - ImGui::SameLine(); + ImGui::SameLine(); - // Input file fields - float width = ImGui::GetContentRegionAvail().x; + // Input file fields + float width = ImGui::GetContentRegionAvail().x; // fix this! fix this! fix this! - if (!fdFile.puDLGDirectoryMode) - width -= FILTER_COMBO_WIDTH*DpiScale; - ImGui::PushItemWidth(width); - ImGui::InputText("##FileName", fdFile.puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); - if (ImGui::GetItemID() == ImGui::GetActiveID()) - prFileDialogInternal.puFileInputIsActive = true; - ImGui::PopItemWidth(); + if (!fdFile.puDLGDirectoryMode) + width -= FILTER_COMBO_WIDTH*DpiScale; + ImGui::PushItemWidth(width); + ImGui::InputText("##FileName", fdFile.puFileNameBuffer, MAX_FILE_DIALOG_NAME_BUFFER); + if (ImGui::GetItemID() == ImGui::GetActiveID()) + prFileDialogInternal.puFileInputIsActive = true; + ImGui::PopItemWidth(); - // combobox of filters - prFileDialogInternal.puFilterManager.DrawFilterComboBox(prFileDialogInternal); + // combobox of filters + prFileDialogInternal.puFilterManager.DrawFilterComboBox(prFileDialogInternal); - bool res = false; + bool res = false; - // OK Button - if (prFileDialogInternal.puCanWeContinue && strlen(fdFile.puFileNameBuffer)) - { - if (IMGUI_BUTTON(okButtonString "##validationdialog")) - { - prFileDialogInternal.puIsOk = true; - res = true; - } + // OK Button + if (prFileDialogInternal.puCanWeContinue && strlen(fdFile.puFileNameBuffer)) + { + if (IMGUI_BUTTON(okButtonString "##validationdialog")) + { + prFileDialogInternal.puIsOk = true; + res = true; + } - ImGui::SameLine(); - } + ImGui::SameLine(); + } - // Cancel Button - if (IMGUI_BUTTON(cancelButtonString "##validationdialog") || - prFileDialogInternal.puNeedToExitDialog) // dialog exit asked - { - prFileDialogInternal.puIsOk = false; - res = true; - } + // Cancel Button + if (IMGUI_BUTTON(cancelButtonString "##validationdialog") || + prFileDialogInternal.puNeedToExitDialog) // dialog exit asked + { + prFileDialogInternal.puIsOk = false; + res = true; + } - prFileDialogInternal.puFooterHeight = ImGui::GetCursorPosY() - posY; + prFileDialogInternal.puFooterHeight = ImGui::GetCursorPosY() - posY; - return res; - } + return res; + } // returns 0 if not break loop, 1 if break loop, 2 if exit dialog - int IGFD::FileDialog::prSelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, ...) - { - if (!vInfos.use_count()) - return 0; + int IGFD::FileDialog::prSelectableItem(int vidx, std::shared_ptr vInfos, bool vSelected, const char* vFmt, ...) + { + if (!vInfos.use_count()) + return 0; - auto& fdi = prFileDialogInternal.puFileManager; + auto& fdi = prFileDialogInternal.puFileManager; - static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | - ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; + static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | + ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; // TODO BUG?! // YES BUG: THIS JUST CRASHED FOR SOME REASON - va_list args; - va_start(args, vFmt); - vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); - va_end(args); + va_list args; + va_start(args, vFmt); + vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); + va_end(args); - float h = /*mobileMode?(ImGui::GetFontSize()+10.0f*DpiScale):*/0.0f; + float h = /*mobileMode?(ImGui::GetFontSize()+10.0f*DpiScale):*/0.0f; #ifdef USE_THUMBNAILS - if (prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST) - h = DisplayMode_ThumbailsList_ImageHeight; + if (prDisplayMode == DisplayModeEnum::THUMBNAILS_LIST) + h = DisplayMode_ThumbailsList_ImageHeight; #endif // USE_THUMBNAILS #ifdef USE_EXPLORATION_BY_KEYS - bool flashed = prBeginFlashItem((size_t)vidx); - bool res = prFlashableSelectable(fdi.puVariadicBuffer, vSelected, selectableFlags, - flashed, ImVec2(-1.0f, h)); - if (flashed) - prEndFlashItem(); + bool flashed = prBeginFlashItem((size_t)vidx); + bool res = prFlashableSelectable(fdi.puVariadicBuffer, vSelected, selectableFlags, + flashed, ImVec2(-1.0f, h)); + if (flashed) + prEndFlashItem(); #else // USE_EXPLORATION_BY_KEYS - (void)vidx; // remove a warnings ofr unused var + (void)vidx; // remove a warnings ofr unused var - bool res = ImGui::Selectable(fdi.puVariadicBuffer, vSelected, selectableFlags, ImVec2(-1.0f, h)); + bool res = ImGui::Selectable(fdi.puVariadicBuffer, vSelected, selectableFlags, ImVec2(-1.0f, h)); #endif // USE_EXPLORATION_BY_KEYS - if (res) - { - if (vInfos->fileType == 'd') - { + if (res) + { + if (vInfos->fileType == 'd') + { bool isSelectingDir=false; - // nav system, selectebale cause open directory or select directory - if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) - { - if (fdi.puDLGDirectoryMode) // directory chooser - { - fdi.SelectFileName(prFileDialogInternal, vInfos); - } - else - { - fdi.puPathClicked = fdi.SelectDirectory(vInfos); + // nav system, selectebale cause open directory or select directory + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) + { + if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(prFileDialogInternal, vInfos); + } + else + { + fdi.puPathClicked = fdi.SelectDirectory(vInfos); isSelectingDir=true; - } - } - else // no nav system => classic behavior - { - if (DOUBLE_CLICKED) // 0 -> left mouse button double click - { + } + } + else // no nav system => classic behavior + { + if (DOUBLE_CLICKED) // 0 -> left mouse button double click + { isSelectingDir=true; - fdi.puPathClicked = fdi.SelectDirectory(vInfos); - } - else if (fdi.puDLGDirectoryMode) // directory chooser - { - fdi.SelectFileName(prFileDialogInternal, vInfos); - } - } + fdi.puPathClicked = fdi.SelectDirectory(vInfos); + } + else if (fdi.puDLGDirectoryMode) // directory chooser + { + fdi.SelectFileName(prFileDialogInternal, vInfos); + } + } - return isSelectingDir; // needToBreakTheloop - } - else - { + return isSelectingDir; // needToBreakTheloop + } + else + { if (DOUBLE_CLICKED) { fdi.SelectFileName(prFileDialogInternal, vInfos); prFileDialogInternal.puIsOk = true; return 2; } else { - fdi.SelectFileName(prFileDialogInternal, vInfos); + fdi.SelectFileName(prFileDialogInternal, vInfos); if (prFileDialogInternal.puDLGselFun!=NULL) { std::string argPath; for (auto& i: GetSelection()) { @@ -3969,581 +3969,581 @@ namespace IGFD } } } - } - } + } + } - return 0; - } + return 0; + } - void IGFD::FileDialog::prBeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) - { - vOutStr.clear(); - vOutShowColor = false; + void IGFD::FileDialog::prBeginFileColorIconStyle(std::shared_ptr vFileInfos, bool& vOutShowColor, std::string& vOutStr, ImFont** vOutFont) + { + vOutStr.clear(); + vOutShowColor = false; - if (vFileInfos->fileStyle.use_count()) //-V807 //-V522 - { - vOutShowColor = true; + if (vFileInfos->fileStyle.use_count()) //-V807 //-V522 + { + vOutShowColor = true; - *vOutFont = vFileInfos->fileStyle->font; - } + *vOutFont = vFileInfos->fileStyle->font; + } - if (vOutShowColor && !vFileInfos->fileStyle->icon.empty()) vOutStr = vFileInfos->fileStyle->icon; - else if (vFileInfos->fileType == 'd') vOutStr = dirEntryString; - else if (vFileInfos->fileType == 'l') vOutStr = linkEntryString; - else if (vFileInfos->fileType == 'f') vOutStr = fileEntryString; + if (vOutShowColor && !vFileInfos->fileStyle->icon.empty()) vOutStr = vFileInfos->fileStyle->icon; + else if (vFileInfos->fileType == 'd') vOutStr = dirEntryString; + else if (vFileInfos->fileType == 'l') vOutStr = linkEntryString; + else if (vFileInfos->fileType == 'f') vOutStr = fileEntryString; - vOutStr += " " + vFileInfos->fileNameExt; + vOutStr += " " + vFileInfos->fileNameExt; - if (vOutShowColor) - ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color); - if (*vOutFont) - ImGui::PushFont(*vOutFont); - } + if (vOutShowColor) + ImGui::PushStyleColor(ImGuiCol_Text, vFileInfos->fileStyle->color); + if (*vOutFont) + ImGui::PushFont(*vOutFont); + } - void IGFD::FileDialog::prEndFileColorIconStyle(const bool& vShowColor, ImFont* vFont) - { - if (vFont) - ImGui::PopFont(); - if (vShowColor) - ImGui::PopStyleColor(); - } + void IGFD::FileDialog::prEndFileColorIconStyle(const bool& vShowColor, ImFont* vFont) + { + if (vFont) + ImGui::PopFont(); + if (vShowColor) + ImGui::PopStyleColor(); + } - bool IGFD::FileDialog::prDrawFileListView(ImVec2 vSize) - { + bool IGFD::FileDialog::prDrawFileListView(ImVec2 vSize) + { bool escape = false; - auto& fdi = prFileDialogInternal.puFileManager; + auto& fdi = prFileDialogInternal.puFileManager; - ImGui::PushID(this); + ImGui::PushID(this); - static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | - ImGuiTableFlags_NoHostExtendY + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_NoHostExtendY #ifndef USE_CUSTOM_SORTING_ICON - | ImGuiTableFlags_Sortable + | ImGuiTableFlags_Sortable #endif // USE_CUSTOM_SORTING_ICON - ; - auto listViewID = ImGui::GetID("##FileDialog_fileTable"); - if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 4, flags, vSize, 0.0f)) //-V112 - { - ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible - ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); - ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); - ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); - ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 4, flags, vSize, 0.0f)) //-V112 + { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); + ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); + ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); + ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); #ifndef USE_CUSTOM_SORTING_ICON - // Sort our data if sort specs have been changed! - if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) - { - if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) - { - if (sorts_specs->Specs->ColumnUserID == 0) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); - else if (sorts_specs->Specs->ColumnUserID == 1) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); - else if (sorts_specs->Specs->ColumnUserID == 2) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); - else //if (sorts_specs->Specs->ColumnUserID == 3) => alwayd true for the moment, to uncomment if we add a fourth column - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) + { + if (sorts_specs->Specs->ColumnUserID == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (sorts_specs->Specs->ColumnUserID == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (sorts_specs->Specs->ColumnUserID == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else //if (sorts_specs->Specs->ColumnUserID == 3) => alwayd true for the moment, to uncomment if we add a fourth column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); - sorts_specs->SpecsDirty = false; - } - } + sorts_specs->SpecsDirty = false; + } + } - ImGui::TableHeadersRow(); + ImGui::TableHeadersRow(); #else // USE_CUSTOM_SORTING_ICON - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - for (int column = 0; column < 4; column++) //-V112 - { - ImGui::TableSetColumnIndex(column); - const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() - ImGui::PushID(column); - ImGui::TableHeader(column_name); - ImGui::PopID(); - if (ImGui::IsItemClicked()) - { - if (column == 0) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); - else if (column == 1) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); - else if (column == 2) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); - else //if (column == 3) => alwayd true for the moment, to uncomment if we add a fourth column - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); - } - } + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 4; column++) //-V112 + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) + { + if (column == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (column == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (column == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else //if (column == 3) => alwayd true for the moment, to uncomment if we add a fourth column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + } + } #endif // USE_CUSTOM_SORTING_ICON - if (!fdi.IsFilteredListEmpty()) - { - std::string _str; - ImFont* _font = nullptr; - bool _showColor = false; - - prFileListClipper.Begin((int)fdi.GetFilteredListSize(), ImGui::GetTextLineHeightWithSpacing()); - while (prFileListClipper.Step()) - { - for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) - { - if (i < 0) continue; + if (!fdi.IsFilteredListEmpty()) + { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; + + prFileListClipper.Begin((int)fdi.GetFilteredListSize(), ImGui::GetTextLineHeightWithSpacing()); + while (prFileListClipper.Step()) + { + for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) + { + if (i < 0) continue; - auto infos = fdi.GetFilteredFileAt((size_t)i); - if (!infos.use_count()) - continue; + auto infos = fdi.GetFilteredFileAt((size_t)i); + if (!infos.use_count()) + continue; - prBeginFileColorIconStyle(infos, _showColor, _str, &_font); - - bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found + prBeginFileColorIconStyle(infos, _showColor, _str, &_font); + + bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found - ImGui::TableNextRow(); + ImGui::TableNextRow(); - int needToBreakTheloop = false; + int needToBreakTheloop = false; - if (ImGui::TableNextColumn()) // file name - { + if (ImGui::TableNextColumn()) // file name + { // TODO BUG?!?!?! // YES BUG - needToBreakTheloop = prSelectableItem(i, infos, selected, "%s", _str.c_str()); + needToBreakTheloop = prSelectableItem(i, infos, selected, "%s", _str.c_str()); if (needToBreakTheloop==2) escape=true; - } - if (ImGui::TableNextColumn()) // file type - { - ImGui::Text("%s", infos->fileExt.c_str()); - } - if (ImGui::TableNextColumn()) // file size - { - if (infos->fileType != 'd') - { - ImGui::Text("%s ", infos->formatedFileSize.c_str()); - } - else - { - ImGui::Text("%s",""); - } - } - if (ImGui::TableNextColumn()) // file date + time - { - ImGui::Text("%s", infos->fileModifDate.c_str()); - } + } + if (ImGui::TableNextColumn()) // file type + { + ImGui::Text("%s", infos->fileExt.c_str()); + } + if (ImGui::TableNextColumn()) // file size + { + if (infos->fileType != 'd') + { + ImGui::Text("%s ", infos->formatedFileSize.c_str()); + } + else + { + ImGui::Text("%s",""); + } + } + if (ImGui::TableNextColumn()) // file date + time + { + ImGui::Text("%s", infos->fileModifDate.c_str()); + } - prEndFileColorIconStyle(_showColor, _font); + prEndFileColorIconStyle(_showColor, _font); - if (needToBreakTheloop==1) - break; - } - } - prFileListClipper.End(); - } + if (needToBreakTheloop==1) + break; + } + } + prFileListClipper.End(); + } #ifdef USE_EXPLORATION_BY_KEYS - if (!fdi.puInputPathActivated) - { - prLocateByInputKey(prFileDialogInternal); - prExploreWithkeys(prFileDialogInternal, listViewID); - } + if (!fdi.puInputPathActivated) + { + prLocateByInputKey(prFileDialogInternal); + prExploreWithkeys(prFileDialogInternal, listViewID); + } #endif // USE_EXPLORATION_BY_KEYS - ImGuiContext& g = *GImGui; - if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) - { - prFileDialogInternal.puFileListViewIsActive = true; - } + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) + { + prFileDialogInternal.puFileListViewIsActive = true; + } - ImGui::EndTable(); - } + ImGui::EndTable(); + } - ImGui::PopID(); + ImGui::PopID(); return escape; - } + } #ifdef USE_THUMBNAILS - void IGFD::FileDialog::prDrawThumbnailsListView(ImVec2 vSize) - { - auto& fdi = prFileDialogInternal.puFileManager; + void IGFD::FileDialog::prDrawThumbnailsListView(ImVec2 vSize) + { + auto& fdi = prFileDialogInternal.puFileManager; - ImGui::PushID(this); + ImGui::PushID(this); - static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | - ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | - ImGuiTableFlags_NoHostExtendY + static ImGuiTableFlags flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Hideable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_NoHostExtendY #ifndef USE_CUSTOM_SORTING_ICON - | ImGuiTableFlags_Sortable + | ImGuiTableFlags_Sortable #endif // USE_CUSTOM_SORTING_ICON - ; - auto listViewID = ImGui::GetID("##FileDialog_fileTable"); - if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 5, flags, vSize, 0.0f)) - { - ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible - ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); - ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); - ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); - ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | - ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); - // not needed to have an option for hide the thumbnails since this is why this view is used - ImGui::TableSetupColumn(fdi.puHeaderFileThumbnails.c_str(), ImGuiTableColumnFlags_WidthFixed, -1, 4); //-V112 + ; + auto listViewID = ImGui::GetID("##FileDialog_fileTable"); + if (ImGui::BeginTableEx("##FileDialog_fileTable", listViewID, 5, flags, vSize, 0.0f)) + { + ImGui::TableSetupScrollFreeze(0, 1); // Make header always visible + ImGui::TableSetupColumn(fdi.puHeaderFileName.c_str(), ImGuiTableColumnFlags_WidthStretch, -1, 0); + ImGui::TableSetupColumn(fdi.puHeaderFileType.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnType) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 1); + ImGui::TableSetupColumn(fdi.puHeaderFileSize.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnSize) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 2); + ImGui::TableSetupColumn(fdi.puHeaderFileDate.c_str(), ImGuiTableColumnFlags_WidthFixed | + ((prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_HideColumnDate) ? ImGuiTableColumnFlags_DefaultHide : 0), -1, 3); + // not needed to have an option for hide the thumbnails since this is why this view is used + ImGui::TableSetupColumn(fdi.puHeaderFileThumbnails.c_str(), ImGuiTableColumnFlags_WidthFixed, -1, 4); //-V112 #ifndef USE_CUSTOM_SORTING_ICON - // Sort our data if sort specs have been changed! - if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) - { - if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) - { - if (sorts_specs->Specs->ColumnUserID == 0) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); - else if (sorts_specs->Specs->ColumnUserID == 1) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); - else if (sorts_specs->Specs->ColumnUserID == 2) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); - else if (sorts_specs->Specs->ColumnUserID == 3) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); - else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we add another column - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); - sorts_specs->SpecsDirty = false; - } - } + // Sort our data if sort specs have been changed! + if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs()) + { + if (sorts_specs->SpecsDirty && !fdi.IsFileListEmpty()) + { + if (sorts_specs->Specs->ColumnUserID == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (sorts_specs->Specs->ColumnUserID == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (sorts_specs->Specs->ColumnUserID == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else if (sorts_specs->Specs->ColumnUserID == 3) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + else // if (sorts_specs->Specs->ColumnUserID == 4) = > always true for the moment, to uncomment if we add another column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); + sorts_specs->SpecsDirty = false; + } + } - ImGui::TableHeadersRow(); + ImGui::TableHeadersRow(); #else // USE_CUSTOM_SORTING_ICON - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - for (int column = 0; column < 5; column++) - { - ImGui::TableSetColumnIndex(column); - const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() - ImGui::PushID(column); - ImGui::TableHeader(column_name); - ImGui::PopID(); - if (ImGui::IsItemClicked()) - { - if (column == 0) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); - else if (column == 1) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); - else if (column == 2) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); - else if (column == 3) - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); - else // if (column == 4) = > always true for the moment, to uncomment if we add another column - fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); - } - } + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + for (int column = 0; column < 5; column++) + { + ImGui::TableSetColumnIndex(column); + const char* column_name = ImGui::TableGetColumnName(column); // Retrieve name passed to TableSetupColumn() + ImGui::PushID(column); + ImGui::TableHeader(column_name); + ImGui::PopID(); + if (ImGui::IsItemClicked()) + { + if (column == 0) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_FILENAME, true); + else if (column == 1) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_TYPE, true); + else if (column == 2) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_SIZE, true); + else if (column == 3) + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_DATE, true); + else // if (column == 4) = > always true for the moment, to uncomment if we add another column + fdi.SortFields(prFileDialogInternal, IGFD::FileManager::SortingFieldEnum::FIELD_THUMBNAILS, true); + } + } #endif // USE_CUSTOM_SORTING_ICON - if (!fdi.IsFilteredListEmpty()) - { - std::string _str; - ImFont* _font = nullptr; - bool _showColor = false; + if (!fdi.IsFilteredListEmpty()) + { + std::string _str; + ImFont* _font = nullptr; + bool _showColor = false; - ImGuiContext& g = *GImGui; - const float itemHeight = ImMax(g.FontSize, DisplayMode_ThumbailsList_ImageHeight) + g.Style.ItemSpacing.y; + ImGuiContext& g = *GImGui; + const float itemHeight = ImMax(g.FontSize, DisplayMode_ThumbailsList_ImageHeight) + g.Style.ItemSpacing.y; - prFileListClipper.Begin((int)fdi.GetFilteredListSize(), itemHeight); - while (prFileListClipper.Step()) - { - for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) - { - if (i < 0) continue; + prFileListClipper.Begin((int)fdi.GetFilteredListSize(), itemHeight); + while (prFileListClipper.Step()) + { + for (int i = prFileListClipper.DisplayStart; i < prFileListClipper.DisplayEnd; i++) + { + if (i < 0) continue; - auto infos = fdi.GetFilteredFileAt((size_t)i); - if (!infos.use_count()) - continue; + auto infos = fdi.GetFilteredFileAt((size_t)i); + if (!infos.use_count()) + continue; - prBeginFileColorIconStyle(infos, _showColor, _str, &_font); + prBeginFileColorIconStyle(infos, _showColor, _str, &_font); - bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found + bool selected = fdi.IsFileNameSelected(infos->fileNameExt); // found - ImGui::TableNextRow(); + ImGui::TableNextRow(); - bool needToBreakTheloop = false; + bool needToBreakTheloop = false; - if (ImGui::TableNextColumn()) // file name - { - needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); - } - if (ImGui::TableNextColumn()) // file type - { - ImGui::Text("%s", infos->fileExt.c_str()); - } - if (ImGui::TableNextColumn()) // file size - { - if (infos->fileType != 'd') - { - ImGui::Text("%s ", infos->formatedFileSize.c_str()); - } - else - { - ImGui::Text(""); - } - } - if (ImGui::TableNextColumn()) // file date + time - { - ImGui::Text("%s", infos->fileModifDate.c_str()); - } - if (ImGui::TableNextColumn()) // file thumbnails - { - auto th = &infos->thumbnailInfo; + if (ImGui::TableNextColumn()) // file name + { + needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); + } + if (ImGui::TableNextColumn()) // file type + { + ImGui::Text("%s", infos->fileExt.c_str()); + } + if (ImGui::TableNextColumn()) // file size + { + if (infos->fileType != 'd') + { + ImGui::Text("%s ", infos->formatedFileSize.c_str()); + } + else + { + ImGui::Text(""); + } + } + if (ImGui::TableNextColumn()) // file date + time + { + ImGui::Text("%s", infos->fileModifDate.c_str()); + } + if (ImGui::TableNextColumn()) // file thumbnails + { + auto th = &infos->thumbnailInfo; - if (!th->isLoadingOrLoaded) - { - prAddThumbnailToLoad(infos); - } - if (th->isReadyToDisplay && - th->textureID) - { - ImGui::Image((ImTextureID)th->textureID, - ImVec2((float)th->textureWidth, - (float)th->textureHeight)); - } - } + if (!th->isLoadingOrLoaded) + { + prAddThumbnailToLoad(infos); + } + if (th->isReadyToDisplay && + th->textureID) + { + ImGui::Image((ImTextureID)th->textureID, + ImVec2((float)th->textureWidth, + (float)th->textureHeight)); + } + } - prEndFileColorIconStyle(_showColor, _font); + prEndFileColorIconStyle(_showColor, _font); - if (needToBreakTheloop) - break; - } - } - prFileListClipper.End(); - } + if (needToBreakTheloop) + break; + } + } + prFileListClipper.End(); + } #ifdef USE_EXPLORATION_BY_KEYS - if (!fdi.puInputPathActivated) - { - prLocateByInputKey(prFileDialogInternal); - prExploreWithkeys(prFileDialogInternal, listViewID); - } + if (!fdi.puInputPathActivated) + { + prLocateByInputKey(prFileDialogInternal); + prExploreWithkeys(prFileDialogInternal, listViewID); + } #endif // USE_EXPLORATION_BY_KEYS - ImGuiContext& g = *GImGui; - if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) - { - prFileDialogInternal.puFileListViewIsActive = true; - } + ImGuiContext& g = *GImGui; + if (g.LastActiveId - 1 == listViewID || g.LastActiveId == listViewID) + { + prFileDialogInternal.puFileListViewIsActive = true; + } - ImGui::EndTable(); - } + ImGui::EndTable(); + } - ImGui::PopID(); - } + ImGui::PopID(); + } - void IGFD::FileDialog::prDrawThumbnailsGridView(ImVec2 vSize) - { - if (ImGui::BeginChild("##thumbnailsGridsFiles", vSize)) - { - // todo - } + void IGFD::FileDialog::prDrawThumbnailsGridView(ImVec2 vSize) + { + if (ImGui::BeginChild("##thumbnailsGridsFiles", vSize)) + { + // todo + } - ImGui::EndChild(); - } + ImGui::EndChild(); + } #endif - void IGFD::FileDialog::prDrawSidePane(float vHeight) - { - ImGui::SameLine(); + void IGFD::FileDialog::prDrawSidePane(float vHeight) + { + ImGui::SameLine(); - ImGui::BeginChild("##FileTypes", ImVec2(0, vHeight)); + ImGui::BeginChild("##FileTypes", ImVec2(0, vHeight)); - prFileDialogInternal.puDLGoptionsPane( - prFileDialogInternal.puFilterManager.GetSelectedFilter().filter.c_str(), - prFileDialogInternal.puDLGuserDatas, &prFileDialogInternal.puCanWeContinue); + prFileDialogInternal.puDLGoptionsPane( + prFileDialogInternal.puFilterManager.GetSelectedFilter().filter.c_str(), + prFileDialogInternal.puDLGuserDatas, &prFileDialogInternal.puCanWeContinue); - ImGui::EndChild(); - } + ImGui::EndChild(); + } - void IGFD::FileDialog::Close() - { - prFileDialogInternal.puDLGkey.clear(); - prFileDialogInternal.puShowDialog = false; - } + void IGFD::FileDialog::Close() + { + prFileDialogInternal.puDLGkey.clear(); + prFileDialogInternal.puShowDialog = false; + } - bool IGFD::FileDialog::WasOpenedThisFrame(const std::string& vKey) const - { - bool res = prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey; - if (res) - { - ImGuiContext& g = *GImGui; - res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame - } - return res; - } + bool IGFD::FileDialog::WasOpenedThisFrame(const std::string& vKey) const + { + bool res = prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey; + if (res) + { + ImGuiContext& g = *GImGui; + res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame + } + return res; + } - bool IGFD::FileDialog::WasOpenedThisFrame() const - { - bool res = prFileDialogInternal.puShowDialog; - if (res) - { - ImGuiContext& g = *GImGui; - res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame - } - return res; - } + bool IGFD::FileDialog::WasOpenedThisFrame() const + { + bool res = prFileDialogInternal.puShowDialog; + if (res) + { + ImGuiContext& g = *GImGui; + res &= prFileDialogInternal.puLastImGuiFrameCount == g.FrameCount; // return true if a dialog was displayed in this frame + } + return res; + } - bool IGFD::FileDialog::IsOpened(const std::string& vKey) const - { - return (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey); - } + bool IGFD::FileDialog::IsOpened(const std::string& vKey) const + { + return (prFileDialogInternal.puShowDialog && prFileDialogInternal.puDLGkey == vKey); + } - bool IGFD::FileDialog::IsOpened() const - { - return prFileDialogInternal.puShowDialog; - } + bool IGFD::FileDialog::IsOpened() const + { + return prFileDialogInternal.puShowDialog; + } - std::string IGFD::FileDialog::GetOpenedKey() const - { - if (prFileDialogInternal.puShowDialog) - return prFileDialogInternal.puDLGkey; - return ""; - } + std::string IGFD::FileDialog::GetOpenedKey() const + { + if (prFileDialogInternal.puShowDialog) + return prFileDialogInternal.puDLGkey; + return ""; + } - std::string IGFD::FileDialog::GetFilePathName() - { - return prFileDialogInternal.puFileManager.GetResultingFilePathName(prFileDialogInternal); - } + std::string IGFD::FileDialog::GetFilePathName() + { + return prFileDialogInternal.puFileManager.GetResultingFilePathName(prFileDialogInternal); + } - std::string IGFD::FileDialog::GetCurrentPath() - { - return prFileDialogInternal.puFileManager.GetResultingPath(); - } + std::string IGFD::FileDialog::GetCurrentPath() + { + return prFileDialogInternal.puFileManager.GetResultingPath(); + } - std::string IGFD::FileDialog::GetCurrentFileName() - { - return prFileDialogInternal.puFileManager.GetResultingFileName(prFileDialogInternal); - } + std::string IGFD::FileDialog::GetCurrentFileName() + { + return prFileDialogInternal.puFileManager.GetResultingFileName(prFileDialogInternal); + } - std::string IGFD::FileDialog::GetCurrentFilter() - { - return prFileDialogInternal.puFilterManager.GetSelectedFilter().filter; - } + std::string IGFD::FileDialog::GetCurrentFilter() + { + return prFileDialogInternal.puFilterManager.GetSelectedFilter().filter; + } - std::map IGFD::FileDialog::GetSelection() - { - return prFileDialogInternal.puFileManager.GetResultingSelection(); - } + std::map IGFD::FileDialog::GetSelection() + { + return prFileDialogInternal.puFileManager.GetResultingSelection(); + } - UserDatas IGFD::FileDialog::GetUserDatas() const - { - return prFileDialogInternal.puDLGuserDatas; - } + UserDatas IGFD::FileDialog::GetUserDatas() const + { + return prFileDialogInternal.puDLGuserDatas; + } - bool IGFD::FileDialog::IsOk() const - { - return prFileDialogInternal.puIsOk; - } + bool IGFD::FileDialog::IsOk() const + { + return prFileDialogInternal.puIsOk; + } - void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) - { - prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vInfos); - } + void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const FileStyle& vInfos) + { + prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vInfos); + } - void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) - { - prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); - } + void IGFD::FileDialog::SetFileStyle(const IGFD_FileStyleFlags& vFlags, const char* vCriteria, const ImVec4& vColor, const std::string& vIcon, ImFont* vFont) + { + prFileDialogInternal.puFilterManager.SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); + } - bool IGFD::FileDialog::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) - { - return prFileDialogInternal.puFilterManager.GetFileStyle(vFlags, vCriteria, vOutColor, vOutIcon, vOutFont); - } + bool IGFD::FileDialog::GetFileStyle(const IGFD_FileStyleFlags& vFlags, const std::string& vCriteria, ImVec4* vOutColor, std::string* vOutIcon, ImFont **vOutFont) + { + return prFileDialogInternal.puFilterManager.GetFileStyle(vFlags, vCriteria, vOutColor, vOutIcon, vOutFont); + } - void IGFD::FileDialog::ClearFilesStyle() - { - prFileDialogInternal.puFilterManager.ClearFilesStyle(); - } + void IGFD::FileDialog::ClearFilesStyle() + { + prFileDialogInternal.puFilterManager.ClearFilesStyle(); + } - void IGFD::FileDialog::SetLocales(const int& vLocaleCategory, const std::string& vLocaleBegin, const std::string& vLocaleEnd) - { - prFileDialogInternal.puUseCustomLocale = true; - prFileDialogInternal.puLocaleBegin = vLocaleBegin; - prFileDialogInternal.puLocaleEnd = vLocaleEnd; - } + void IGFD::FileDialog::SetLocales(const int& vLocaleCategory, const std::string& vLocaleBegin, const std::string& vLocaleEnd) + { + prFileDialogInternal.puUseCustomLocale = true; + prFileDialogInternal.puLocaleBegin = vLocaleBegin; + prFileDialogInternal.puLocaleEnd = vLocaleEnd; + } - ////////////////////////////////////////////////////////////////////////////// - //// OVERWRITE DIALOG //////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + //// OVERWRITE DIALOG //////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// - bool IGFD::FileDialog::prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags) - { - // if confirmation => return true for confirm the overwrite et quit the dialog - // if cancel => return false && set IsOk to false for keep inside the dialog + bool IGFD::FileDialog::prConfirm_Or_OpenOverWriteFileDialog_IfNeeded(bool vLastAction, ImGuiWindowFlags vFlags) + { + // if confirmation => return true for confirm the overwrite et quit the dialog + // if cancel => return false && set IsOk to false for keep inside the dialog - // if IsOk == false => return false for quit the dialog - if (!prFileDialogInternal.puIsOk && vLastAction) - { - QuitFrame(); - return true; - } + // if IsOk == false => return false for quit the dialog + if (!prFileDialogInternal.puIsOk && vLastAction) + { + QuitFrame(); + return true; + } - // if IsOk == true && no check of overwrite => return true for confirm the dialog - if (prFileDialogInternal.puIsOk && vLastAction && !(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) - { - QuitFrame(); - return true; - } + // if IsOk == true && no check of overwrite => return true for confirm the dialog + if (prFileDialogInternal.puIsOk && vLastAction && !(prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) + { + QuitFrame(); + return true; + } - // if IsOk == true && check of overwrite => return false and show confirm to overwrite dialog - if ((prFileDialogInternal.puOkResultToConfirm || (prFileDialogInternal.puIsOk && vLastAction)) && - (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) - { - if (prFileDialogInternal.puIsOk) // catched only one time - { - if (!prFileDialogInternal.puFileManager.IsFileExist(GetFilePathName())) // not existing => quit dialog - { - QuitFrame(); - return true; - } - else // existing => confirm dialog to open - { - prFileDialogInternal.puIsOk = false; - prFileDialogInternal.puOkResultToConfirm = true; - } - } + // if IsOk == true && check of overwrite => return false and show confirm to overwrite dialog + if ((prFileDialogInternal.puOkResultToConfirm || (prFileDialogInternal.puIsOk && vLastAction)) && + (prFileDialogInternal.puDLGflags & ImGuiFileDialogFlags_ConfirmOverwrite)) + { + if (prFileDialogInternal.puIsOk) // catched only one time + { + if (!prFileDialogInternal.puFileManager.IsFileExist(GetFilePathName())) // not existing => quit dialog + { + QuitFrame(); + return true; + } + else // existing => confirm dialog to open + { + prFileDialogInternal.puIsOk = false; + prFileDialogInternal.puOkResultToConfirm = true; + } + } - std::string name = OverWriteDialogTitleString "##" + prFileDialogInternal.puDLGtitle + prFileDialogInternal.puDLGkey + "OverWriteDialog"; + std::string name = OverWriteDialogTitleString "##" + prFileDialogInternal.puDLGtitle + prFileDialogInternal.puDLGkey + "OverWriteDialog"; - bool res = false; + bool res = false; - ImGui::OpenPopup(name.c_str()); - if (ImGui::BeginPopupModal(name.c_str(), (bool*)0, - vFlags | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) - { - ImGui::SetWindowPos(prFileDialogInternal.puDialogCenterPos - ImGui::GetWindowSize() * 0.5f); // next frame needed for GetWindowSize to work + ImGui::OpenPopup(name.c_str()); + if (ImGui::BeginPopupModal(name.c_str(), (bool*)0, + vFlags | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) + { + ImGui::SetWindowPos(prFileDialogInternal.puDialogCenterPos - ImGui::GetWindowSize() * 0.5f); // next frame needed for GetWindowSize to work - ImGui::Text("%s", OverWriteDialogMessageString); + ImGui::Text("%s", OverWriteDialogMessageString); - if (IMGUI_BUTTON(OverWriteDialogConfirmButtonString)) - { - prFileDialogInternal.puOkResultToConfirm = false; - prFileDialogInternal.puIsOk = true; - res = true; - ImGui::CloseCurrentPopup(); - } + if (IMGUI_BUTTON(OverWriteDialogConfirmButtonString)) + { + prFileDialogInternal.puOkResultToConfirm = false; + prFileDialogInternal.puIsOk = true; + res = true; + ImGui::CloseCurrentPopup(); + } - ImGui::SameLine(); + ImGui::SameLine(); - if (IMGUI_BUTTON(OverWriteDialogCancelButtonString)) - { - prFileDialogInternal.puOkResultToConfirm = false; - prFileDialogInternal.puIsOk = false; - res = false; - ImGui::CloseCurrentPopup(); - } + if (IMGUI_BUTTON(OverWriteDialogCancelButtonString)) + { + prFileDialogInternal.puOkResultToConfirm = false; + prFileDialogInternal.puIsOk = false; + res = false; + ImGui::CloseCurrentPopup(); + } - ImGui::EndPopup(); - } + ImGui::EndPopup(); + } - if (res) - { - QuitFrame(); - } - return res; - } + if (res) + { + QuitFrame(); + } + return res; + } - return false; - } + return false; + } } #endif // __cplusplus @@ -4555,574 +4555,574 @@ namespace IGFD // Return an initialized IGFD_Selection_Pair IMGUIFILEDIALOG_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(void) { - IGFD_Selection_Pair res = {}; - res.fileName = nullptr; - res.filePathName = nullptr; - return res; + IGFD_Selection_Pair res = {}; + res.fileName = nullptr; + res.filePathName = nullptr; + return res; } // destroy only the content of vSelection_Pair IMGUIFILEDIALOG_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair) { - if (vSelection_Pair) - { - delete[] vSelection_Pair->fileName; - delete[] vSelection_Pair->filePathName; - } + if (vSelection_Pair) + { + delete[] vSelection_Pair->fileName; + delete[] vSelection_Pair->filePathName; + } } // Return an initialized IGFD_Selection IMGUIFILEDIALOG_API IGFD_Selection IGFD_Selection_Get(void) { - return { nullptr, 0U }; + return { nullptr, 0U }; } // destroy only the content of vSelection IMGUIFILEDIALOG_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection) { - if (vSelection) - { - if (vSelection->table) - { - for (size_t i = 0U; i < vSelection->count; i++) - { - IGFD_Selection_Pair_DestroyContent(&vSelection->table[i]); - } - delete[] vSelection->table; - } - vSelection->count = 0U; - } + if (vSelection) + { + if (vSelection->table) + { + for (size_t i = 0U; i < vSelection->count; i++) + { + IGFD_Selection_Pair_DestroyContent(&vSelection->table[i]); + } + delete[] vSelection->table; + } + vSelection->count = 0U; + } } // create an instance of ImGuiFileDialog IMGUIFILEDIALOG_API ImGuiFileDialog* IGFD_Create(void) { - return new ImGuiFileDialog(); + return new ImGuiFileDialog(); } // destroy the instance of ImGuiFileDialog IMGUIFILEDIALOG_API void IGFD_Destroy(ImGuiFileDialog* vContext) { - if (vContext) - { - delete vContext; - vContext = nullptr; - } + if (vContext) + { + delete vContext; + vContext = nullptr; + } } // standard dialog IMGUIFILEDIALOG_API void IGFD_OpenDialog( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vPath, - const char* vFileName, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenDialog( - vKey, vTitle, vFilters, vPath, vFileName, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, vPath, vFileName, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenDialog2( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vFilePathName, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenDialog( - vKey, vTitle, vFilters, vFilePathName, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, vFilePathName, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vPath, - const char* vFileName, - IGFD_PaneFun vSidePane, - const float vSidePaneWidth, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenDialog( - vKey, vTitle, vFilters, - vPath, vFileName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog2( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vFilePathName, - IGFD_PaneFun vSidePane, - const float vSidePaneWidth, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenDialog( - vKey, vTitle, vFilters, - vFilePathName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenDialog( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } } // modal dialog IMGUIFILEDIALOG_API void IGFD_OpenModal( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vPath, - const char* vFileName, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenModal( - vKey, vTitle, vFilters, vPath, vFileName, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, vPath, vFileName, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenModal2( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vFilePathName, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenModal( - vKey, vTitle, vFilters, vFilePathName, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, vFilePathName, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenPaneModal( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vPath, - const char* vFileName, - IGFD_PaneFun vSidePane, - const float vSidePaneWidth, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vPath, + const char* vFileName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenModal( - vKey, vTitle, vFilters, - vPath, vFileName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, + vPath, vFileName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API void IGFD_OpenPaneModal2( - ImGuiFileDialog* vContext, - const char* vKey, - const char* vTitle, - const char* vFilters, - const char* vFilePathName, - IGFD_PaneFun vSidePane, - const float vSidePaneWidth, - const int vCountSelectionMax, - void* vUserDatas, - ImGuiFileDialogFlags flags) + ImGuiFileDialog* vContext, + const char* vKey, + const char* vTitle, + const char* vFilters, + const char* vFilePathName, + IGFD_PaneFun vSidePane, + const float vSidePaneWidth, + const int vCountSelectionMax, + void* vUserDatas, + ImGuiFileDialogFlags flags) { - if (vContext) - { - vContext->OpenModal( - vKey, vTitle, vFilters, - vFilePathName, - vSidePane, vSidePaneWidth, - vCountSelectionMax, vUserDatas, flags); - } + if (vContext) + { + vContext->OpenModal( + vKey, vTitle, vFilters, + vFilePathName, + vSidePane, vSidePaneWidth, + vCountSelectionMax, vUserDatas, flags); + } } IMGUIFILEDIALOG_API bool IGFD_DisplayDialog(ImGuiFileDialog* vContext, - const char* vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) + const char* vKey, ImGuiWindowFlags vFlags, ImVec2 vMinSize, ImVec2 vMaxSize) { - if (vContext) - { - return vContext->Display(vKey, vFlags, vMinSize, vMaxSize); - } + if (vContext) + { + return vContext->Display(vKey, vFlags, vMinSize, vMaxSize); + } - return false; + return false; } IMGUIFILEDIALOG_API void IGFD_CloseDialog(ImGuiFileDialog* vContext) { - if (vContext) - { - vContext->Close(); - } + if (vContext) + { + vContext->Close(); + } } IMGUIFILEDIALOG_API bool IGFD_IsOk(ImGuiFileDialog* vContext) { - if (vContext) - { - return vContext->IsOk(); - } + if (vContext) + { + return vContext->IsOk(); + } - return false; + return false; } IMGUIFILEDIALOG_API bool IGFD_WasKeyOpenedThisFrame(ImGuiFileDialog* vContext, - const char* vKey) + const char* vKey) { - if (vContext) - { - vContext->WasOpenedThisFrame(vKey); - } + if (vContext) + { + vContext->WasOpenedThisFrame(vKey); + } - return false; + return false; } IMGUIFILEDIALOG_API bool IGFD_WasOpenedThisFrame(ImGuiFileDialog* vContext) { - if (vContext) - { - vContext->WasOpenedThisFrame(); - } + if (vContext) + { + vContext->WasOpenedThisFrame(); + } - return false; + return false; } IMGUIFILEDIALOG_API bool IGFD_IsKeyOpened(ImGuiFileDialog* vContext, - const char* vCurrentOpenedKey) + const char* vCurrentOpenedKey) { - if (vContext) - { - vContext->IsOpened(vCurrentOpenedKey); - } + if (vContext) + { + vContext->IsOpened(vCurrentOpenedKey); + } - return false; + return false; } IMGUIFILEDIALOG_API bool IGFD_IsOpened(ImGuiFileDialog* vContext) { - if (vContext) - { - vContext->IsOpened(); - } + if (vContext) + { + vContext->IsOpened(); + } - return false; + return false; } IMGUIFILEDIALOG_API IGFD_Selection IGFD_GetSelection(ImGuiFileDialog* vContext) { - IGFD_Selection res = IGFD_Selection_Get(); + IGFD_Selection res = IGFD_Selection_Get(); - if (vContext) - { - auto sel = vContext->GetSelection(); - if (!sel.empty()) - { - res.count = sel.size(); - res.table = new IGFD_Selection_Pair[res.count]; + if (vContext) + { + auto sel = vContext->GetSelection(); + if (!sel.empty()) + { + res.count = sel.size(); + res.table = new IGFD_Selection_Pair[res.count]; - size_t idx = 0U; - for (const auto& s : sel) - { - IGFD_Selection_Pair* pair = res.table + idx++; + size_t idx = 0U; + for (const auto& s : sel) + { + IGFD_Selection_Pair* pair = res.table + idx++; - // fileNameExt - if (!s.first.empty()) - { - size_t siz = s.first.size() + 1U; - pair->fileName = new char[siz]; + // fileNameExt + if (!s.first.empty()) + { + size_t siz = s.first.size() + 1U; + pair->fileName = new char[siz]; #ifndef MSVC - strncpy(pair->fileName, s.first.c_str(), siz); + strncpy(pair->fileName, s.first.c_str(), siz); #else - strncpy_s(pair->fileName, siz, s.first.c_str(), siz); + strncpy_s(pair->fileName, siz, s.first.c_str(), siz); #endif - pair->fileName[siz - 1U] = '\0'; - } + pair->fileName[siz - 1U] = '\0'; + } - // filePathName - if (!s.second.empty()) - { - size_t siz = s.first.size() + 1U; - pair->filePathName = new char[siz]; + // filePathName + if (!s.second.empty()) + { + size_t siz = s.first.size() + 1U; + pair->filePathName = new char[siz]; #ifndef MSVC - strncpy(pair->filePathName, s.first.c_str(), siz); + strncpy(pair->filePathName, s.first.c_str(), siz); #else - strncpy_s(pair->filePathName, siz, s.first.c_str(), siz); + strncpy_s(pair->filePathName, siz, s.first.c_str(), siz); #endif - pair->filePathName[siz - 1U] = '\0'; - } - } + pair->filePathName[siz - 1U] = '\0'; + } + } - return res; - } - } + return res; + } + } - return res; + return res; } IMGUIFILEDIALOG_API char* IGFD_GetFilePathName(ImGuiFileDialog* vContext) { - char* res = nullptr; + char* res = nullptr; - if (vContext) - { - auto s = vContext->GetFilePathName(); - if (!s.empty()) - { - size_t siz = s.size() + 1U; - res = new char[siz]; + if (vContext) + { + auto s = vContext->GetFilePathName(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; #ifndef MSVC - strncpy(res, s.c_str(), siz); + strncpy(res, s.c_str(), siz); #else - strncpy_s(res, siz, s.c_str(), siz); + strncpy_s(res, siz, s.c_str(), siz); #endif - res[siz - 1U] = '\0'; - } - } + res[siz - 1U] = '\0'; + } + } - return res; + return res; } IMGUIFILEDIALOG_API char* IGFD_GetCurrentFileName(ImGuiFileDialog* vContext) { - char* res = nullptr; + char* res = nullptr; - if (vContext) - { - auto s = vContext->GetCurrentFileName(); - if (!s.empty()) - { - size_t siz = s.size() + 1U; - res = new char[siz]; + if (vContext) + { + auto s = vContext->GetCurrentFileName(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; #ifndef MSVC - strncpy(res, s.c_str(), siz); + strncpy(res, s.c_str(), siz); #else - strncpy_s(res, siz, s.c_str(), siz); + strncpy_s(res, siz, s.c_str(), siz); #endif - res[siz - 1U] = '\0'; - } - } + res[siz - 1U] = '\0'; + } + } - return res; + return res; } IMGUIFILEDIALOG_API char* IGFD_GetCurrentPath(ImGuiFileDialog* vContext) { - char* res = nullptr; + char* res = nullptr; - if (vContext) - { - auto s = vContext->GetCurrentPath(); - if (!s.empty()) - { - size_t siz = s.size() + 1U; - res = new char[siz]; + if (vContext) + { + auto s = vContext->GetCurrentPath(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; #ifndef MSVC - strncpy(res, s.c_str(), siz); + strncpy(res, s.c_str(), siz); #else - strncpy_s(res, siz, s.c_str(), siz); + strncpy_s(res, siz, s.c_str(), siz); #endif - res[siz - 1U] = '\0'; - } - } + res[siz - 1U] = '\0'; + } + } - return res; + return res; } IMGUIFILEDIALOG_API char* IGFD_GetCurrentFilter(ImGuiFileDialog* vContext) { - char* res = nullptr; + char* res = nullptr; - if (vContext) - { - auto s = vContext->GetCurrentFilter(); - if (!s.empty()) - { - size_t siz = s.size() + 1U; - res = new char[siz]; + if (vContext) + { + auto s = vContext->GetCurrentFilter(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; #ifndef MSVC - strncpy(res, s.c_str(), siz); + strncpy(res, s.c_str(), siz); #else - strncpy_s(res, siz, s.c_str(), siz); + strncpy_s(res, siz, s.c_str(), siz); #endif - res[siz - 1U] = '\0'; - } - } + res[siz - 1U] = '\0'; + } + } - return res; + return res; } IMGUIFILEDIALOG_API void* IGFD_GetUserDatas(ImGuiFileDialog* vContext) { - if (vContext) - { - return vContext->GetUserDatas(); - } + if (vContext) + { + return vContext->GetUserDatas(); + } - return nullptr; + return nullptr; } IMGUIFILEDIALOG_API void IGFD_SetFileStyle(ImGuiFileDialog* vContext, - IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4 vColor, const char* vIcon, ImFont* vFont) //-V813 + IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4 vColor, const char* vIcon, ImFont* vFont) //-V813 { - if (vContext) - { - vContext->SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); - } + if (vContext) + { + vContext->SetFileStyle(vFlags, vCriteria, vColor, vIcon, vFont); + } } IMGUIFILEDIALOG_API void IGFD_SetFileStyle2(ImGuiFileDialog* vContext, - IGFD_FileStyleFlags vFlags, const char* vCriteria, float vR, float vG, float vB, float vA, const char* vIcon, ImFont* vFont) + IGFD_FileStyleFlags vFlags, const char* vCriteria, float vR, float vG, float vB, float vA, const char* vIcon, ImFont* vFont) { - if (vContext) - { - vContext->SetFileStyle(vFlags, vCriteria, ImVec4(vR, vG, vB, vA), vIcon, vFont); - } + if (vContext) + { + vContext->SetFileStyle(vFlags, vCriteria, ImVec4(vR, vG, vB, vA), vIcon, vFont); + } } IMGUIFILEDIALOG_API bool IGFD_GetFileStyle(ImGuiFileDialog* vContext, - IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4* vOutColor, char** vOutIcon, ImFont** vOutFont) + IGFD_FileStyleFlags vFlags, const char* vCriteria, ImVec4* vOutColor, char** vOutIcon, ImFont** vOutFont) { - if (vContext) - { - std::string icon; - bool res = vContext->GetFileStyle(vFlags, vCriteria, vOutColor, &icon, vOutFont); - if (!icon.empty() && vOutIcon) - { - size_t siz = icon.size() + 1U; - *vOutIcon = new char[siz]; + if (vContext) + { + std::string icon; + bool res = vContext->GetFileStyle(vFlags, vCriteria, vOutColor, &icon, vOutFont); + if (!icon.empty() && vOutIcon) + { + size_t siz = icon.size() + 1U; + *vOutIcon = new char[siz]; #ifndef MSVC - strncpy(*vOutIcon, icon.c_str(), siz); + strncpy(*vOutIcon, icon.c_str(), siz); #else - strncpy_s(*vOutIcon, siz, icon.c_str(), siz); + strncpy_s(*vOutIcon, siz, icon.c_str(), siz); #endif - (*vOutIcon)[siz - 1U] = '\0'; - } - return res; - } + (*vOutIcon)[siz - 1U] = '\0'; + } + return res; + } - return false; + return false; } IMGUIFILEDIALOG_API void IGFD_ClearFilesStyle(ImGuiFileDialog* vContext) { - if (vContext) - { - vContext->ClearFilesStyle(); - } + if (vContext) + { + vContext->ClearFilesStyle(); + } } IMGUIFILEDIALOG_API void SetLocales(ImGuiFileDialog* vContext, const int vCategory, const char* vBeginLocale, const char* vEndLocale) { - if (vContext) - { - vContext->SetLocales(vCategory, (vBeginLocale ? vBeginLocale : ""), (vEndLocale ? vEndLocale : "")); - } + if (vContext) + { + vContext->SetLocales(vCategory, (vBeginLocale ? vBeginLocale : ""), (vEndLocale ? vEndLocale : "")); + } } #ifdef USE_EXPLORATION_BY_KEYS IMGUIFILEDIALOG_API void IGFD_SetFlashingAttenuationInSeconds(ImGuiFileDialog* vContext, float vAttenValue) { - if (vContext) - { - vContext->SetFlashingAttenuationInSeconds(vAttenValue); - } + if (vContext) + { + vContext->SetFlashingAttenuationInSeconds(vAttenValue); + } } #endif #ifdef USE_BOOKMARK IMGUIFILEDIALOG_API char* IGFD_SerializeBookmarks(ImGuiFileDialog* vContext) { - char* res = nullptr; + char* res = nullptr; - if (vContext) - { - auto s = vContext->SerializeBookmarks(); - if (!s.empty()) - { - size_t siz = s.size() + 1U; - res = new char[siz]; + if (vContext) + { + auto s = vContext->SerializeBookmarks(); + if (!s.empty()) + { + size_t siz = s.size() + 1U; + res = new char[siz]; #ifndef MSVC - strncpy(res, s.c_str(), siz); + strncpy(res, s.c_str(), siz); #else - strncpy_s(res, siz, s.c_str(), siz); + strncpy_s(res, siz, s.c_str(), siz); #endif - res[siz - 1U] = '\0'; - } - } + res[siz - 1U] = '\0'; + } + } - return res; + return res; } IMGUIFILEDIALOG_API void IGFD_DeserializeBookmarks(ImGuiFileDialog* vContext, const char* vBookmarks) { - if (vContext) - { - vContext->DeserializeBookmarks(vBookmarks); - } + if (vContext) + { + vContext->DeserializeBookmarks(vBookmarks); + } } #endif #ifdef USE_THUMBNAILS IMGUIFILEDIALOG_API void SetCreateThumbnailCallback(ImGuiFileDialog* vContext, const IGFD_CreateThumbnailFun vCreateThumbnailFun) { - if (vContext) - { - vContext->SetCreateThumbnailCallback(vCreateThumbnailFun); - } + if (vContext) + { + vContext->SetCreateThumbnailCallback(vCreateThumbnailFun); + } } IMGUIFILEDIALOG_API void SetDestroyThumbnailCallback(ImGuiFileDialog* vContext, const IGFD_DestroyThumbnailFun vDestroyThumbnailFun) { - if (vContext) - { - vContext->SetDestroyThumbnailCallback(vDestroyThumbnailFun); - } + if (vContext) + { + vContext->SetDestroyThumbnailCallback(vDestroyThumbnailFun); + } } IMGUIFILEDIALOG_API void ManageGPUThumbnails(ImGuiFileDialog* vContext) { - if (vContext) - { - vContext->ManageGPUThumbnails(); - } + if (vContext) + { + vContext->ManageGPUThumbnails(); + } } #endif // USE_THUMBNAILS diff --git a/extern/igfd/ImGuiFileDialog.h b/extern/igfd/ImGuiFileDialog.h index 9c2d246f..5ab7f6c8 100644 --- a/extern/igfd/ImGuiFileDialog.h +++ b/extern/igfd/ImGuiFileDialog.h @@ -85,20 +85,20 @@ void drawGui() { // open Dialog Simple if (ImGui::Button("Open File Dialog")) - ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", "."); + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", "."); // display if (ImGuiFileDialog::Instance()->FileDialog("ChooseFileDlgKey")) { - // action if OK - if (ImGuiFileDialog::Instance()->IsOk == true) - { - std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); - std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); - // action - } - // close - ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); + // action if OK + if (ImGuiFileDialog::Instance()->IsOk == true) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); + // action + } + // close + ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); } } @@ -121,40 +121,40 @@ Example code : static bool canValidateDialog = false; inline void InfosPane(std::string& vFilter, IGFD::UserDatas vUserDatas, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog { - ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane"); - ImGui::Text("Selected Filter : %s", vFilter.c_str()); - if (vUserDatas) - ImGui::Text("UserDatas : %s", vUserDatas); - ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog); - if (vCantContinue) - *vCantContinue = canValidateDialog; + ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane"); + ImGui::Text("Selected Filter : %s", vFilter.c_str()); + if (vUserDatas) + ImGui::Text("UserDatas : %s", vUserDatas); + ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog); + if (vCantContinue) + *vCantContinue = canValidateDialog; } void drawGui() { // open Dialog with Pane if (ImGui::Button("Open File Dialog with a custom pane")) - ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", - ".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, IGFD::UserDatas("InfosPane")); + ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", + ".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, IGFD::UserDatas("InfosPane")); // display and action if ok if (ImGuiFileDialog::Instance()->FileDialog("ChooseFileDlgKey")) { - if (ImGuiFileDialog::Instance()->IsOk == true) - { - std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); - std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); - std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter(); - // here convert from string because a string was passed as a userDatas, but it can be what you want - std::string userDatas; - if (ImGuiFileDialog::Instance()->GetUserDatas()) - userDatas = std::string((const char*)ImGuiFileDialog::Instance()->GetUserDatas()); - auto selection = ImGuiFileDialog::Instance()->GetSelection(); // multiselection + if (ImGuiFileDialog::Instance()->IsOk == true) + { + std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); + std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); + std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter(); + // here convert from string because a string was passed as a userDatas, but it can be what you want + std::string userDatas; + if (ImGuiFileDialog::Instance()->GetUserDatas()) + userDatas = std::string((const char*)ImGuiFileDialog::Instance()->GetUserDatas()); + auto selection = ImGuiFileDialog::Instance()->GetSelection(); // multiselection - // action - } - // close - ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); + // action + } + // close + ImGuiFileDialog::Instance()->CloseDialog("ChooseFileDlgKey"); } } @@ -170,13 +170,13 @@ the general form is : ImGuiFileDialog::Instance()->SetFileStyle(styleType, criteria, color, icon, font); styleType can be thoses : -IGFD_FileStyle_None // define none style -IGFD_FileStyleByTypeFile // define style for all files -IGFD_FileStyleByTypeDir // define style for all dir -IGFD_FileStyleByTypeLink // define style for all link -IGFD_FileStyleByExtention // define style by extention, for files or links -IGFD_FileStyleByFullName // define style for particular file/dir/link full name (filename + extention) -IGFD_FileStyleByContainedInFullName // define style for file/dir/link when criteria is contained in full name +IGFD_FileStyle_None // define none style +IGFD_FileStyleByTypeFile // define style for all files +IGFD_FileStyleByTypeDir // define style for all dir +IGFD_FileStyleByTypeLink // define style for all link +IGFD_FileStyleByExtention // define style by extention, for files or links +IGFD_FileStyleByFullName // define style for particular file/dir/link full name (filename + extention) +IGFD_FileStyleByContainedInFullName // define style for file/dir/link when criteria is contained in full name samples : @@ -341,14 +341,14 @@ define if a dialog will be for Open or Save behavior. (and its wanted :) ) Example code For Standard Dialog : Example code : ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", - ICON_IGFD_SAVE " Choose a File", filters, - ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); + ICON_IGFD_SAVE " Choose a File", filters, + ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); Example code For Modal Dialog : Example code : ImGuiFileDialog::Instance()->OpenModal("ChooseFileDlgKey", - ICON_IGFD_SAVE " Choose a File", filters, - ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); + ICON_IGFD_SAVE " Choose a File", filters, + ".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite); This dialog will only verify the file in the file field. So Not to be used with GetSelection() @@ -372,8 +372,8 @@ Example code : ----------------------------------------------------------------------------------------------------------------- flag must be specified in OpenDialog or OpenModal -* ImGuiFileDialogFlags_ConfirmOverwrite => show confirm to overwrite dialog -* ImGuiFileDialogFlags_DontShowHiddenFiles => dont show hidden file (file starting with a .) +* ImGuiFileDialogFlags_ConfirmOverwrite => show confirm to overwrite dialog +* ImGuiFileDialogFlags_DontShowHiddenFiles => dont show hidden file (file starting with a .) ----------------------------------------------------------------------------------------------------------------- ## Open / Save dialog Behavior : @@ -419,43 +419,43 @@ Example code : // Create thumbnails texture ImGuiFileDialog::Instance()->SetCreateThumbnailCallback([](IGFD_Thumbnail_Info *vThumbnail_Info) -> void { - if (vThumbnail_Info && - vThumbnail_Info->isReadyToUpload && - vThumbnail_Info->textureFileDatas) - { - GLuint textureId = 0; - glGenTextures(1, &textureId); - vThumbnail_Info->textureID = (void*)textureId; + if (vThumbnail_Info && + vThumbnail_Info->isReadyToUpload && + vThumbnail_Info->textureFileDatas) + { + GLuint textureId = 0; + glGenTextures(1, &textureId); + vThumbnail_Info->textureID = (void*)textureId; - glBindTexture(GL_TEXTURE_2D, textureId); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - (GLsizei)vThumbnail_Info->textureWidth, (GLsizei)vThumbnail_Info->textureHeight, - 0, GL_RGBA, GL_UNSIGNED_BYTE, vThumbnail_Info->textureFileDatas); - glFinish(); - glBindTexture(GL_TEXTURE_2D, 0); + glBindTexture(GL_TEXTURE_2D, textureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + (GLsizei)vThumbnail_Info->textureWidth, (GLsizei)vThumbnail_Info->textureHeight, + 0, GL_RGBA, GL_UNSIGNED_BYTE, vThumbnail_Info->textureFileDatas); + glFinish(); + glBindTexture(GL_TEXTURE_2D, 0); - delete[] vThumbnail_Info->textureFileDatas; - vThumbnail_Info->textureFileDatas = nullptr; + delete[] vThumbnail_Info->textureFileDatas; + vThumbnail_Info->textureFileDatas = nullptr; - vThumbnail_Info->isReadyToUpload = false; - vThumbnail_Info->isReadyToDisplay = true; - } + vThumbnail_Info->isReadyToUpload = false; + vThumbnail_Info->isReadyToDisplay = true; + } }); Example code : // Destroy thumbnails texture ImGuiFileDialog::Instance()->SetDestroyThumbnailCallback([](IGFD_Thumbnail_Info* vThumbnail_Info) { - if (vThumbnail_Info) - { - GLuint texID = (GLuint)vThumbnail_Info->textureID; - glDeleteTextures(1, &texID); - glFinish(); - } + if (vThumbnail_Info) + { + GLuint texID = (GLuint)vThumbnail_Info->textureID; + glDeleteTextures(1, &texID); + glFinish(); + } }); Example code : @@ -477,17 +477,17 @@ ImGuiFileDialog *cfileDialog = IGFD_Create(); // open dialog if (igButton("Open File", buttonSize)) { - IGFD_OpenDialog(cfiledialog, - "filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key - "Open a File", // dialog title - "c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc.. - ".", // base directory for files scan - "", // base filename - 0, // a fucntion for display a right pane if you want - 0.0f, // base width of the pane - 0, // count selection : 0 infinite, 1 one file (default), n (n files) - "User data !", // some user datas - ImGuiFileDialogFlags_ConfirmOverwrite); // ImGuiFileDialogFlags + IGFD_OpenDialog(cfiledialog, + "filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key + "Open a File", // dialog title + "c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc.. + ".", // base directory for files scan + "", // base filename + 0, // a fucntion for display a right pane if you want + 0.0f, // base width of the pane + 0, // count selection : 0 infinite, 1 one file (default), n (n files) + "User data !", // some user datas + ImGuiFileDialogFlags_ConfirmOverwrite); // ImGuiFileDialogFlags } ImGuiIO* ioptr = igGetIO(); @@ -501,34 +501,34 @@ minSize.y = maxSize.y * 0.25f; // display dialog if (IGFD_DisplayDialog(cfiledialog, "filedlg", ImGuiWindowFlags_NoCollapse, minSize, maxSize)) { - if (IGFD_IsOk(cfiledialog)) // result ok - { - char* cfilePathName = IGFD_GetFilePathName(cfiledialog); - printf("GetFilePathName : %s\n", cfilePathName); - char* cfilePath = IGFD_GetCurrentPath(cfiledialog); - printf("GetCurrentPath : %s\n", cfilePath); - char* cfilter = IGFD_GetCurrentFilter(cfiledialog); - printf("GetCurrentFilter : %s\n", cfilter); - // here convert from string because a string was passed as a userDatas, but it can be what you want - void* cdatas = IGFD_GetUserDatas(cfiledialog); - if (cdatas) - printf("GetUserDatas : %s\n", (const char*)cdatas); - struct IGFD_Selection csel = IGFD_GetSelection(cfiledialog); // multi selection - printf("Selection :\n"); - for (int i = 0; i < (int)csel.count; i++) - { - printf("(%i) FileName %s => path %s\n", i, csel.table[i].fileName, csel.table[i].filePathName); - } - // action + if (IGFD_IsOk(cfiledialog)) // result ok + { + char* cfilePathName = IGFD_GetFilePathName(cfiledialog); + printf("GetFilePathName : %s\n", cfilePathName); + char* cfilePath = IGFD_GetCurrentPath(cfiledialog); + printf("GetCurrentPath : %s\n", cfilePath); + char* cfilter = IGFD_GetCurrentFilter(cfiledialog); + printf("GetCurrentFilter : %s\n", cfilter); + // here convert from string because a string was passed as a userDatas, but it can be what you want + void* cdatas = IGFD_GetUserDatas(cfiledialog); + if (cdatas) + printf("GetUserDatas : %s\n", (const char*)cdatas); + struct IGFD_Selection csel = IGFD_GetSelection(cfiledialog); // multi selection + printf("Selection :\n"); + for (int i = 0; i < (int)csel.count; i++) + { + printf("(%i) FileName %s => path %s\n", i, csel.table[i].fileName, csel.table[i].filePathName); + } + // action - // destroy - if (cfilePathName) free(cfilePathName); - if (cfilePath) free(cfilePath); - if (cfilter) free(cfilter); + // destroy + if (cfilePathName) free(cfilePathName); + if (cfilePath) free(cfilePath); + if (cfilter) free(cfilter); - IGFD_Selection_DestroyContent(&csel); - } - IGFD_CloseDialog(cfiledialog); + IGFD_Selection_DestroyContent(&csel); + } + IGFD_CloseDialog(cfiledialog); } // destroy ImGuiFileDialog @@ -572,9 +572,9 @@ ImGuiFontStudio is using also ImGuiFileDialog. #define IMGUIFILEDIALOG_H #if defined(__WIN32__) || defined(_WIN32) - #ifndef WIN32 - #define WIN32 - #endif // WIN32 + #ifndef WIN32 + #define WIN32 + #endif // WIN32 #endif // defined(__WIN32__) || defined(_WIN32) #define IMGUIFILEDIALOG_VERSION "v0.6.4" @@ -589,43 +589,43 @@ ImGuiFontStudio is using also ImGuiFileDialog. typedef int IGFD_FileStyleFlags; // -> enum IGFD_FileStyleFlags_ enum IGFD_FileStyleFlags_ // by evaluation / priority order { - IGFD_FileStyle_None = 0, // define none style - IGFD_FileStyleByTypeFile = (1 << 0), // define style for all files - IGFD_FileStyleByTypeDir = (1 << 1), // define style for all dir - IGFD_FileStyleByTypeLink = (1 << 2), // define style for all link - IGFD_FileStyleByExtention = (1 << 3), // define style by extention, for files or links - IGFD_FileStyleByFullName = (1 << 4), // define style for particular file/dir/link full name (filename + extention) - IGFD_FileStyleByContainedInFullName = (1 << 5), // define style for file/dir/link when criteria is contained in full name + IGFD_FileStyle_None = 0, // define none style + IGFD_FileStyleByTypeFile = (1 << 0), // define style for all files + IGFD_FileStyleByTypeDir = (1 << 1), // define style for all dir + IGFD_FileStyleByTypeLink = (1 << 2), // define style for all link + IGFD_FileStyleByExtention = (1 << 3), // define style by extention, for files or links + IGFD_FileStyleByFullName = (1 << 4), // define style for particular file/dir/link full name (filename + extention) + IGFD_FileStyleByContainedInFullName = (1 << 5), // define style for file/dir/link when criteria is contained in full name }; typedef int ImGuiFileDialogFlags; // -> enum ImGuiFileDialogFlags_ enum ImGuiFileDialogFlags_ { - ImGuiFileDialogFlags_None = 0, - ImGuiFileDialogFlags_ConfirmOverwrite = (1 << 0), // show confirm to overwrite dialog - ImGuiFileDialogFlags_DontShowHiddenFiles = (1 << 1), // dont show hidden file (file starting with a .) - ImGuiFileDialogFlags_DisableCreateDirectoryButton = (1 << 2), // disable the create directory button - ImGuiFileDialogFlags_HideColumnType = (1 << 3), // hide column file type - ImGuiFileDialogFlags_HideColumnSize = (1 << 4), // hide column file size - ImGuiFileDialogFlags_HideColumnDate = (1 << 5), // hide column file date + ImGuiFileDialogFlags_None = 0, + ImGuiFileDialogFlags_ConfirmOverwrite = (1 << 0), // show confirm to overwrite dialog + ImGuiFileDialogFlags_DontShowHiddenFiles = (1 << 1), // dont show hidden file (file starting with a .) + ImGuiFileDialogFlags_DisableCreateDirectoryButton = (1 << 2), // disable the create directory button + ImGuiFileDialogFlags_HideColumnType = (1 << 3), // hide column file type + ImGuiFileDialogFlags_HideColumnSize = (1 << 4), // hide column file size + ImGuiFileDialogFlags_HideColumnDate = (1 << 5), // hide column file date #ifdef USE_THUMBNAILS - ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 6), // disable the thumbnail mode + ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 6), // disable the thumbnail mode #endif - ImGuiFileDialogFlags_Default = ImGuiFileDialogFlags_ConfirmOverwrite + ImGuiFileDialogFlags_Default = ImGuiFileDialogFlags_ConfirmOverwrite }; #ifdef USE_THUMBNAILS struct IGFD_Thumbnail_Info { - int isReadyToDisplay = 0; // ready to be rendered, so texture created - int isReadyToUpload = 0; // ready to upload to gpu - int isLoadingOrLoaded = 0; // was sent to laoding or loaded - void* textureID = 0; // 2d texture id (void* is like ImtextureID type) (GL, DX, VK, Etc..) - unsigned char* textureFileDatas = 0; // file texture datas, will be rested to null after gpu upload - int textureWidth = 0; // width of the texture to upload - int textureHeight = 0; // height of the texture to upload - int textureChannels = 0; // count channels of the texture to upload - void* userDatas = 0; // user datas + int isReadyToDisplay = 0; // ready to be rendered, so texture created + int isReadyToUpload = 0; // ready to upload to gpu + int isLoadingOrLoaded = 0; // was sent to laoding or loaded + void* textureID = 0; // 2d texture id (void* is like ImtextureID type) (GL, DX, VK, Etc..) + unsigned char* textureFileDatas = 0; // file texture datas, will be rested to null after gpu upload + int textureWidth = 0; // width of the texture to upload + int textureHeight = 0; // height of the texture to upload + int textureChannels = 0; // count channels of the texture to upload + void* userDatas = 0; // user datas }; #endif // USE_THUMBNAILS @@ -659,680 +659,680 @@ namespace IGFD #define MAX_PATH_BUFFER_SIZE 1024 #endif // MAX_PATH_BUFFER_SIZE - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileDialogInternal; + class FileDialogInternal; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class SearchManager - { - public: - std::string puSearchTag; - char puSearchBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; - bool puSearchInputIsActive = false; + class SearchManager + { + public: + std::string puSearchTag; + char puSearchBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; + bool puSearchInputIsActive = false; - public: - void Clear(); // clear datas - void DrawSearchBar(FileDialogInternal& vFileDialogInternal); // draw the search bar - }; + public: + void Clear(); // clear datas + void DrawSearchBar(FileDialogInternal& vFileDialogInternal); // draw the search bar + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class Utils - { - public: - struct PathStruct - { - std::string path; - std::string name; - std::string ext; - bool isOk = false; - }; + class Utils + { + public: + struct PathStruct + { + std::string path; + std::string name; + std::string ext; + bool isOk = false; + }; - public: - static bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f); - static bool ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr); - static bool IsDirectoryExist(const std::string& name); - static bool CreateDirectoryIfNotExist(const std::string& name); - static PathStruct ParsePathFileName(const std::string& vPathFileName); - static void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); - static void ResetBuffer(char* vBuffer); - static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); + public: + static bool Splitter(bool split_vertically, float thickness, float* size1, float* size2, float min_size1, float min_size2, float splitter_long_axis_size = -1.0f); + static bool ReplaceString(std::string& str, const std::string& oldStr, const std::string& newStr); + static bool IsDirectoryExist(const std::string& name); + static bool CreateDirectoryIfNotExist(const std::string& name); + static PathStruct ParsePathFileName(const std::string& vPathFileName); + static void AppendToBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); + static void ResetBuffer(char* vBuffer); + static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); #ifdef WIN32 - static bool WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr); - static std::vector WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty); - static std::string wstring_to_string(const std::wstring& wstr); - static std::wstring string_to_wstring(const std::string& mbstr); + static bool WReplaceString(std::wstring& str, const std::wstring& oldStr, const std::wstring& newStr); + static std::vector WSplitStringToVector(const std::wstring& text, char delimiter, bool pushEmpty); + static std::string wstring_to_string(const std::wstring& wstr); + static std::wstring string_to_wstring(const std::string& mbstr); #endif - static std::vector SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty); - static std::vector GetDrivesList(); - }; + static std::vector SplitStringToVector(const std::string& text, char delimiter, bool pushEmpty); + static std::vector GetDrivesList(); + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileStyle - { - public: - ImVec4 color = ImVec4(0, 0, 0, 0); - std::string icon; - ImFont* font = nullptr; - IGFD_FileStyleFlags flags = 0; + class FileStyle + { + public: + ImVec4 color = ImVec4(0, 0, 0, 0); + std::string icon; + ImFont* font = nullptr; + IGFD_FileStyleFlags flags = 0; - public: - FileStyle(); - FileStyle(const FileStyle& vStyle); - FileStyle(const ImVec4& vColor, const std::string& vIcon = "", ImFont* vFont = nullptr); - }; + public: + FileStyle(); + FileStyle(const FileStyle& vStyle); + FileStyle(const ImVec4& vColor, const std::string& vIcon = "", ImFont* vFont = nullptr); + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileInfos; - class FilterManager - { - public: - class FilterInfos - { - public: - std::string filter; - std::set collectionfilters; + class FileInfos; + class FilterManager + { + public: + class FilterInfos + { + public: + std::string filter; + std::set collectionfilters; - public: - void clear(); // clear the datas - bool empty() const; // is filter empty - bool exist(const std::string& vFilter) const; // is filter exist - }; + public: + void clear(); // clear the datas + bool empty() const; // is filter empty + bool exist(const std::string& vFilter) const; // is filter exist + }; - private: - std::vector prParsedFilters; - std::unordered_map>> prFilesStyle; // file infos for file extention only - FilterInfos prSelectedFilter; + private: + std::vector prParsedFilters; + std::unordered_map>> prFilesStyle; // file infos for file extention only + FilterInfos prSelectedFilter; - public: - std::string puDLGFilters; - std::string puDLGdefaultExt; + public: + std::string puDLGFilters; + std::string puDLGdefaultExt; - public: - void ParseFilters(const char* vFilters); // Parse filter syntax, detect and parse filter collection - void SetSelectedFilterWithExt(const std::string& vFilter); // Select filter - - bool prFillFileStyle(std::shared_ptr vFileInfos) const; // fill with the good style - - void SetFileStyle( - const IGFD_FileStyleFlags& vFlags, - const char* vCriteria, - const FileStyle& vInfos); // Set FileStyle - void SetFileStyle( - const IGFD_FileStyleFlags& vFlags, - const char* vCriteria, - const ImVec4& vColor, - const std::string& vIcon, - ImFont* vFont); // link file style to Color and Icon and Font - bool GetFileStyle( - const IGFD_FileStyleFlags& vFlags, - const std::string& vCriteria, - ImVec4* vOutColor, - std::string* vOutIcon, - ImFont** vOutFont); // Get Color and Icon for Filter - void ClearFilesStyle(); // clear prFileStyle + public: + void ParseFilters(const char* vFilters); // Parse filter syntax, detect and parse filter collection + void SetSelectedFilterWithExt(const std::string& vFilter); // Select filter + + bool prFillFileStyle(std::shared_ptr vFileInfos) const; // fill with the good style + + void SetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const char* vCriteria, + const FileStyle& vInfos); // Set FileStyle + void SetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const char* vCriteria, + const ImVec4& vColor, + const std::string& vIcon, + ImFont* vFont); // link file style to Color and Icon and Font + bool GetFileStyle( + const IGFD_FileStyleFlags& vFlags, + const std::string& vCriteria, + ImVec4* vOutColor, + std::string* vOutIcon, + ImFont** vOutFont); // Get Color and Icon for Filter + void ClearFilesStyle(); // clear prFileStyle - bool IsCoveredByFilters(const std::string& vTag) const; // check if current file extention (vTag) is covered by current filter - bool DrawFilterComboBox(FileDialogInternal& vFileDialogInternal); // draw the filter combobox - FilterInfos GetSelectedFilter(); // get the current selected filter - std::string ReplaceExtentionWithCurrentFilter(const std::string& vFile) const; // replace the extention of the current file by the selected filter - void SetDefaultFilterIfNotDefined(); // define the first filter if no filter is selected - }; + bool IsCoveredByFilters(const std::string& vTag) const; // check if current file extention (vTag) is covered by current filter + bool DrawFilterComboBox(FileDialogInternal& vFileDialogInternal); // draw the filter combobox + FilterInfos GetSelectedFilter(); // get the current selected filter + std::string ReplaceExtentionWithCurrentFilter(const std::string& vFile) const; // replace the extention of the current file by the selected filter + void SetDefaultFilterIfNotDefined(); // define the first filter if no filter is selected + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileInfos - { - public: - char fileType = ' '; // dirent fileType (f:file, d:directory, l:link) - std::string filePath; // path of the file - std::string fileNameExt; // filename of the file (file name + extention) (but no path) - std::string fileNameExt_optimized; // optimized for search => insensitivecase - std::string fileExt; // extention of the file - size_t fileSize = 0; // for sorting operations - std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go) - std::string fileModifDate; // file user defined format of the date (data + time by default) - std::shared_ptr fileStyle = nullptr; // style of the file + class FileInfos + { + public: + char fileType = ' '; // dirent fileType (f:file, d:directory, l:link) + std::string filePath; // path of the file + std::string fileNameExt; // filename of the file (file name + extention) (but no path) + std::string fileNameExt_optimized; // optimized for search => insensitivecase + std::string fileExt; // extention of the file + size_t fileSize = 0; // for sorting operations + std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go) + std::string fileModifDate; // file user defined format of the date (data + time by default) + std::shared_ptr fileStyle = nullptr; // style of the file #ifdef USE_THUMBNAILS - IGFD_Thumbnail_Info thumbnailInfo; // structre for the display for image file tetxure + IGFD_Thumbnail_Info thumbnailInfo; // structre for the display for image file tetxure #endif // USE_THUMBNAILS - public: - bool IsTagFound(const std::string& vTag) const; - }; + public: + bool IsTagFound(const std::string& vTag) const; + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileManager - { - public: // types - enum class SortingFieldEnum // sorting for filetering of the file lsit - { - FIELD_NONE = 0, // no sorting preference, result indetermined haha.. - FIELD_FILENAME, // sorted by filename - FIELD_TYPE, // sorted by filetype - FIELD_SIZE, // sorted by filesize (formated file size) - FIELD_DATE, // sorted by filedate + class FileManager + { + public: // types + enum class SortingFieldEnum // sorting for filetering of the file lsit + { + FIELD_NONE = 0, // no sorting preference, result indetermined haha.. + FIELD_FILENAME, // sorted by filename + FIELD_TYPE, // sorted by filetype + FIELD_SIZE, // sorted by filesize (formated file size) + FIELD_DATE, // sorted by filedate #ifdef USE_THUMBNAILS - FIELD_THUMBNAILS, // sorted by thumbnails (comparaison by width then by height) + FIELD_THUMBNAILS, // sorted by thumbnails (comparaison by width then by height) #endif // USE_THUMBNAILS - }; + }; - private: - std::string prCurrentPath; // current path (to be decomposed in prCurrentPathDecomposition - std::vector prCurrentPathDecomposition; // part words - std::vector> prFileList; // base container - std::vector> prFilteredFileList; // filtered container (search, sorting, etc..) - std::string prLastSelectedFileName; // for shift multi selection - std::set prSelectedFileNames; // the user selection of FilePathNames - bool prCreateDirectoryMode = false; // for create directory widget + private: + std::string prCurrentPath; // current path (to be decomposed in prCurrentPathDecomposition + std::vector prCurrentPathDecomposition; // part words + std::vector> prFileList; // base container + std::vector> prFilteredFileList; // filtered container (search, sorting, etc..) + std::string prLastSelectedFileName; // for shift multi selection + std::set prSelectedFileNames; // the user selection of FilePathNames + bool prCreateDirectoryMode = false; // for create directory widget - public: - char puVariadicBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // called by prSelectableItem - bool puInputPathActivated = false; // show input for path edition - bool puDrivesClicked = false; // event when a drive button is clicked - bool puPathClicked = false; // event when a path button was clicked - char puInputPathBuffer[MAX_PATH_BUFFER_SIZE] = ""; // input path buffer for imgui widget input text (displayed in palce of composer) - char puFileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // file name buffer in footer for imgui widget input text - char puDirectoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // directory name buffer in footer for imgui widget input text (when is directory mode) - std::string puHeaderFileName; // detail view name of column file - std::string puHeaderFileType; // detail view name of column type - std::string puHeaderFileSize; // detail view name of column size - std::string puHeaderFileDate; // detail view name of column date + time + public: + char puVariadicBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // called by prSelectableItem + bool puInputPathActivated = false; // show input for path edition + bool puDrivesClicked = false; // event when a drive button is clicked + bool puPathClicked = false; // event when a path button was clicked + char puInputPathBuffer[MAX_PATH_BUFFER_SIZE] = ""; // input path buffer for imgui widget input text (displayed in palce of composer) + char puFileNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // file name buffer in footer for imgui widget input text + char puDirectoryNameBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // directory name buffer in footer for imgui widget input text (when is directory mode) + std::string puHeaderFileName; // detail view name of column file + std::string puHeaderFileType; // detail view name of column type + std::string puHeaderFileSize; // detail view name of column size + std::string puHeaderFileDate; // detail view name of column date + time #ifdef USE_THUMBNAILS - std::string puHeaderFileThumbnails; // detail view name of column thumbnails - bool puSortingDirection[5]; // detail view // true => Descending, false => Ascending + std::string puHeaderFileThumbnails; // detail view name of column thumbnails + bool puSortingDirection[5]; // detail view // true => Descending, false => Ascending #else - bool puSortingDirection[4]; // detail view // true => Descending, false => Ascending + bool puSortingDirection[4]; // detail view // true => Descending, false => Ascending #endif - SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column - bool puShowDrives = false; // drives are shown (only on os windows) + SortingFieldEnum puSortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column + bool puShowDrives = false; // drives are shown (only on os windows) bool fileListActuallyEmpty = false; - std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called + std::string puDLGpath; // base path set by user when OpenDialog/OpenModal was called std::string puError; // last error - std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called - size_t puDLGcountSelectionMax = 1U; // 0 for infinite // base max selection count set by user when OpenDialog/OpenModal was called - bool puDLGDirectoryMode = false; // is directory mode (defiend like : puDLGDirectoryMode = (filters.empty())) + std::string puDLGDefaultFileName; // base default file path name set by user when OpenDialog/OpenModal was called + size_t puDLGcountSelectionMax = 1U; // 0 for infinite // base max selection count set by user when OpenDialog/OpenModal was called + bool puDLGDirectoryMode = false; // is directory mode (defiend like : puDLGDirectoryMode = (filters.empty())) - std::string puFsRoot; + std::string puFsRoot; - private: - static std::string prRoundNumber(double vvalue, int n); // custom rounding number - static std::string prFormatFileSize(size_t vByteSize); // format file size field - static std::string prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt); // turn all text in lower case for search facilitie - static void prCompleteFileInfos(const std::shared_ptr& FileInfos); // set time and date infos of a file (detail view mode) - void prRemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name - void prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name - void AddFile(const FileDialogInternal& vFileDialogInternal, - const std::string& vPath, const std::string& vFileName, const char& vFileType); // add file called by scandir + private: + static std::string prRoundNumber(double vvalue, int n); // custom rounding number + static std::string prFormatFileSize(size_t vByteSize); // format file size field + static std::string prOptimizeFilenameForSearchOperations(const std::string& vFileNameExt); // turn all text in lower case for search facilitie + static void prCompleteFileInfos(const std::shared_ptr& FileInfos); // set time and date infos of a file (detail view mode) + void prRemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name + void prAddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name + void AddFile(const FileDialogInternal& vFileDialogInternal, + const std::string& vPath, const std::string& vFileName, const char& vFileType); // add file called by scandir - public: - FileManager(); - bool IsComposerEmpty(); - size_t GetComposerSize(); - bool IsFileListEmpty(); - bool IsFilteredListEmpty(); - size_t GetFullFileListSize(); - std::shared_ptr GetFullFileAt(size_t vIdx); - size_t GetFilteredListSize(); - std::shared_ptr GetFilteredFileAt(size_t vIdx); - bool IsFileNameSelected(const std::string& vFileName); - std::string GetBack(); - void ClearComposer(); - void ClearFileLists(); // clear file list, will destroy thumbnail textures - void ClearAll(); - void ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal); - void OpenCurrentPath(const FileDialogInternal& vFileDialogInternal); // set the path of the dialog, will launch the directory scan for populate the file listview - void SortFields(const FileDialogInternal& vFileDialogInternal, - const SortingFieldEnum& vSortingField, const bool vCanChangeOrder); // will sort a column - bool GetDrives(); // list drives on windows platform - bool CreateDir(const std::string& vPath); // create a directory on the file system - void ComposeNewPath(std::vector::iterator vIter); // compose a path from the compose path widget - bool SetPathOnParentDirectoryIfAny(); // compose paht on parent directory - std::string GetCurrentPath(); // get the current path - void SetCurrentPath(const std::string& vCurrentPath); // set the current path - static bool IsFileExist(const std::string& vFile); - void SetDefaultFileName(const std::string& vFileName); - bool SelectDirectory(const std::shared_ptr& vInfos); // enter directory - void SelectFileName(const FileDialogInternal& vFileDialogInternal, - const std::shared_ptr& vInfos); // select filename - - //depend of dirent.h - void SetCurrentDir(const std::string& vPath); // define current directory for scan - void ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath); // scan the directory for retrieve the file list + public: + FileManager(); + bool IsComposerEmpty(); + size_t GetComposerSize(); + bool IsFileListEmpty(); + bool IsFilteredListEmpty(); + size_t GetFullFileListSize(); + std::shared_ptr GetFullFileAt(size_t vIdx); + size_t GetFilteredListSize(); + std::shared_ptr GetFilteredFileAt(size_t vIdx); + bool IsFileNameSelected(const std::string& vFileName); + std::string GetBack(); + void ClearComposer(); + void ClearFileLists(); // clear file list, will destroy thumbnail textures + void ClearAll(); + void ApplyFilteringOnFileList(const FileDialogInternal& vFileDialogInternal); + void OpenCurrentPath(const FileDialogInternal& vFileDialogInternal); // set the path of the dialog, will launch the directory scan for populate the file listview + void SortFields(const FileDialogInternal& vFileDialogInternal, + const SortingFieldEnum& vSortingField, const bool vCanChangeOrder); // will sort a column + bool GetDrives(); // list drives on windows platform + bool CreateDir(const std::string& vPath); // create a directory on the file system + void ComposeNewPath(std::vector::iterator vIter); // compose a path from the compose path widget + bool SetPathOnParentDirectoryIfAny(); // compose paht on parent directory + std::string GetCurrentPath(); // get the current path + void SetCurrentPath(const std::string& vCurrentPath); // set the current path + static bool IsFileExist(const std::string& vFile); + void SetDefaultFileName(const std::string& vFileName); + bool SelectDirectory(const std::shared_ptr& vInfos); // enter directory + void SelectFileName(const FileDialogInternal& vFileDialogInternal, + const std::shared_ptr& vInfos); // select filename + + //depend of dirent.h + void SetCurrentDir(const std::string& vPath); // define current directory for scan + void ScanDir(const FileDialogInternal& vFileDialogInternal, const std::string& vPath); // scan the directory for retrieve the file list - public: - std::string GetResultingPath(); - std::string GetResultingFileName(FileDialogInternal& vFileDialogInternal); - std::string GetResultingFilePathName(FileDialogInternal& vFileDialogInternal); - std::map GetResultingSelection(); + public: + std::string GetResultingPath(); + std::string GetResultingFileName(FileDialogInternal& vFileDialogInternal); + std::string GetResultingFilePathName(FileDialogInternal& vFileDialogInternal); + std::map GetResultingSelection(); - public: - void DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal); // draw directory creation widget - void DrawPathComposer(const FileDialogInternal& vFileDialogInternal); // draw path composer widget - }; + public: + void DrawDirectoryCreation(const FileDialogInternal& vFileDialogInternal); // draw directory creation widget + void DrawPathComposer(const FileDialogInternal& vFileDialogInternal); // draw path composer widget + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #ifdef USE_THUMBNAILS - typedef std::function CreateThumbnailFun; // texture 2d creation function binding - typedef std::function DestroyThumbnailFun; // texture 2d destroy function binding + typedef std::function CreateThumbnailFun; // texture 2d creation function binding + typedef std::function DestroyThumbnailFun; // texture 2d destroy function binding #endif - class ThumbnailFeature - { - protected: - ThumbnailFeature(); - ~ThumbnailFeature(); + class ThumbnailFeature + { + protected: + ThumbnailFeature(); + ~ThumbnailFeature(); - void NewThumbnailFrame(FileDialogInternal& vFileDialogInternal); - void EndThumbnailFrame(FileDialogInternal& vFileDialogInternal); - void QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void NewThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void EndThumbnailFrame(FileDialogInternal& vFileDialogInternal); + void QuitThumbnailFrame(FileDialogInternal& vFileDialogInternal); #ifdef USE_THUMBNAILS - protected: - enum class DisplayModeEnum - { - FILE_LIST = 0, - THUMBNAILS_LIST, - THUMBNAILS_GRID - }; + protected: + enum class DisplayModeEnum + { + FILE_LIST = 0, + THUMBNAILS_LIST, + THUMBNAILS_GRID + }; - private: - uint32_t prCountFiles = 0U; - bool prIsWorking = false; - std::shared_ptr prThumbnailGenerationThread = nullptr; - std::list> prThumbnailFileDatasToGet; // base container - std::mutex prThumbnailFileDatasToGetMutex; - std::list> prThumbnailToCreate; // base container - std::mutex prThumbnailToCreateMutex; - std::list prThumbnailToDestroy; // base container - std::mutex prThumbnailToDestroyMutex; + private: + uint32_t prCountFiles = 0U; + bool prIsWorking = false; + std::shared_ptr prThumbnailGenerationThread = nullptr; + std::list> prThumbnailFileDatasToGet; // base container + std::mutex prThumbnailFileDatasToGetMutex; + std::list> prThumbnailToCreate; // base container + std::mutex prThumbnailToCreateMutex; + std::list prThumbnailToDestroy; // base container + std::mutex prThumbnailToDestroyMutex; - CreateThumbnailFun prCreateThumbnailFun = nullptr; - DestroyThumbnailFun prDestroyThumbnailFun = nullptr; + CreateThumbnailFun prCreateThumbnailFun = nullptr; + DestroyThumbnailFun prDestroyThumbnailFun = nullptr; - protected: - DisplayModeEnum prDisplayMode = DisplayModeEnum::FILE_LIST; + protected: + DisplayModeEnum prDisplayMode = DisplayModeEnum::FILE_LIST; - protected: - // will be call in cpu zone (imgui computations, will call a texture file retrieval thread) - void prStartThumbnailFileDatasExtraction(); // start the thread who will get byte buffer from image files - bool prStopThumbnailFileDatasExtraction(); // stop the thread who will get byte buffer from image files - void prThreadThumbnailFileDatasExtractionFunc(); // the thread who will get byte buffer from image files - void prDrawThumbnailGenerationProgress(); // a little progressbar who will display the texture gen status - void prAddThumbnailToLoad(const std::shared_ptr& vFileInfos); // add texture to load in the thread - void prAddThumbnailToCreate(const std::shared_ptr& vFileInfos); - void prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info); - void prDrawDisplayModeToolBar(); // draw display mode toolbar (file list, thumbnails list, small thumbnails grid, big thumbnails grid) - void prClearThumbnails(FileDialogInternal& vFileDialogInternal); + protected: + // will be call in cpu zone (imgui computations, will call a texture file retrieval thread) + void prStartThumbnailFileDatasExtraction(); // start the thread who will get byte buffer from image files + bool prStopThumbnailFileDatasExtraction(); // stop the thread who will get byte buffer from image files + void prThreadThumbnailFileDatasExtractionFunc(); // the thread who will get byte buffer from image files + void prDrawThumbnailGenerationProgress(); // a little progressbar who will display the texture gen status + void prAddThumbnailToLoad(const std::shared_ptr& vFileInfos); // add texture to load in the thread + void prAddThumbnailToCreate(const std::shared_ptr& vFileInfos); + void prAddThumbnailToDestroy(const IGFD_Thumbnail_Info& vIGFD_Thumbnail_Info); + void prDrawDisplayModeToolBar(); // draw display mode toolbar (file list, thumbnails list, small thumbnails grid, big thumbnails grid) + void prClearThumbnails(FileDialogInternal& vFileDialogInternal); - public: - void SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun); - void SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun); - - // must be call in gpu zone (rendering, possibly one rendering thread) - void ManageGPUThumbnails(); // in gpu rendering zone, whill create or destroy texture + public: + void SetCreateThumbnailCallback(const CreateThumbnailFun& vCreateThumbnailFun); + void SetDestroyThumbnailCallback(const DestroyThumbnailFun& vCreateThumbnailFun); + + // must be call in gpu zone (rendering, possibly one rendering thread) + void ManageGPUThumbnails(); // in gpu rendering zone, whill create or destroy texture #endif - }; + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class BookMarkFeature - { - protected: - BookMarkFeature(); + class BookMarkFeature + { + protected: + BookMarkFeature(); #ifdef USE_BOOKMARK - private: - struct BookmarkStruct - { - std::string name; // name of the bookmark - - // todo: the path could be relative, better if the app is movedn but bookmarked path can be outside of the app - std::string path; // absolute path of the bookmarked directory - }; + private: + struct BookmarkStruct + { + std::string name; // name of the bookmark + + // todo: the path could be relative, better if the app is movedn but bookmarked path can be outside of the app + std::string path; // absolute path of the bookmarked directory + }; - private: - ImGuiListClipper prBookmarkClipper; - std::vector prBookmarks; - char prBookmarkEditBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; + private: + ImGuiListClipper prBookmarkClipper; + std::vector prBookmarks; + char prBookmarkEditBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; - protected: - float prBookmarkWidth = 200.0f; - bool prBookmarkPaneShown = false; - - protected: - void prDrawBookmarkButton(); // draw bookmark button - bool prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw bookmark Pane + protected: + float prBookmarkWidth = 200.0f; + bool prBookmarkPaneShown = false; + + protected: + void prDrawBookmarkButton(); // draw bookmark button + bool prDrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw bookmark Pane - public: - std::string SerializeBookmarks(); // serialize bookmarks : return bookmark buffer to save in a file - void DeserializeBookmarks( // deserialize bookmarks : load bookmark buffer to load in the dialog (saved from previous use with SerializeBookmarks()) - const std::string& vBookmarks); // bookmark buffer to load + public: + std::string SerializeBookmarks(); // serialize bookmarks : return bookmark buffer to save in a file + void DeserializeBookmarks( // deserialize bookmarks : load bookmark buffer to load in the dialog (saved from previous use with SerializeBookmarks()) + const std::string& vBookmarks); // bookmark buffer to load #endif // USE_BOOKMARK - }; + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // file localization by input chat // widget flashing - class KeyExplorerFeature - { - protected: - KeyExplorerFeature(); + // file localization by input chat // widget flashing + class KeyExplorerFeature + { + protected: + KeyExplorerFeature(); #ifdef USE_EXPLORATION_BY_KEYS - private: - size_t prFlashedItem = 0; // flash when select by char - float prFlashAlpha = 0.0f; // flash when select by char - float prFlashAlphaAttenInSecs = 1.0f; // fps display dependant - size_t prLocateFileByInputChar_lastFileIdx = 0; - ImWchar prLocateFileByInputChar_lastChar = 0; - int prLocateFileByInputChar_InputQueueCharactersSize = 0; - bool prLocateFileByInputChar_lastFound = false; + private: + size_t prFlashedItem = 0; // flash when select by char + float prFlashAlpha = 0.0f; // flash when select by char + float prFlashAlphaAttenInSecs = 1.0f; // fps display dependant + size_t prLocateFileByInputChar_lastFileIdx = 0; + ImWchar prLocateFileByInputChar_lastChar = 0; + int prLocateFileByInputChar_InputQueueCharactersSize = 0; + bool prLocateFileByInputChar_lastFound = false; - protected: - void prLocateByInputKey(FileDialogInternal& vFileDialogInternal); // select a file line in listview according to char key - bool prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC); // restrat for start of list view if not found a corresponding file - void prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID); // select file/directory line in listview accroding to up/down enter/backspace keys - static bool prFlashableSelectable( // custom flashing selectable widgets, for flash the selected line in a short time - const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, - bool vFlashing = false, const ImVec2& size = ImVec2(0, 0)); - void prStartFlashItem(size_t vIdx); // define than an item must be flashed - bool prBeginFlashItem(size_t vIdx); // start the flashing of a line in lsit view - static void prEndFlashItem(); // end the fleshing accrdoin to var prFlashAlphaAttenInSecs + protected: + void prLocateByInputKey(FileDialogInternal& vFileDialogInternal); // select a file line in listview according to char key + bool prLocateItem_Loop(FileDialogInternal& vFileDialogInternal, ImWchar vC); // restrat for start of list view if not found a corresponding file + void prExploreWithkeys(FileDialogInternal& vFileDialogInternal, ImGuiID vListViewID); // select file/directory line in listview accroding to up/down enter/backspace keys + static bool prFlashableSelectable( // custom flashing selectable widgets, for flash the selected line in a short time + const char* label, bool selected = false, ImGuiSelectableFlags flags = 0, + bool vFlashing = false, const ImVec2& size = ImVec2(0, 0)); + void prStartFlashItem(size_t vIdx); // define than an item must be flashed + bool prBeginFlashItem(size_t vIdx); // start the flashing of a line in lsit view + static void prEndFlashItem(); // end the fleshing accrdoin to var prFlashAlphaAttenInSecs - public: - void SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys - float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds + public: + void SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds #endif // USE_EXPLORATION_BY_KEYS - }; + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - typedef void* UserDatas; - typedef std::function PaneFun; // side pane function binding + typedef void* UserDatas; + typedef std::function PaneFun; // side pane function binding typedef std::function SelectFun; // click on file function binding - class FileDialogInternal - { - public: - FileManager puFileManager; - FilterManager puFilterManager; - SearchManager puSearchManager; + class FileDialogInternal + { + public: + FileManager puFileManager; + FilterManager puFilterManager; + SearchManager puSearchManager; - public: - std::string puName; - bool puShowDialog = false; - ImVec2 puDialogCenterPos = ImVec2(0, 0); // center pos for display the confirm overwrite dialog - int puLastImGuiFrameCount = 0; // to be sure than only one dialog displayed per frame - float puFooterHeight = 0.0f; - bool puCanWeContinue = true; // events - bool puOkResultToConfirm = false; // to confim if ok for OverWrite - bool puIsOk = false; - bool puFileInputIsActive = false; // when input text for file or directory is active - bool puFileListViewIsActive = false; // when list view is active - std::string puDLGkey; - std::string puDLGtitle; - ImGuiFileDialogFlags puDLGflags = ImGuiFileDialogFlags_None; - UserDatas puDLGuserDatas = nullptr; - PaneFun puDLGoptionsPane = nullptr; + public: + std::string puName; + bool puShowDialog = false; + ImVec2 puDialogCenterPos = ImVec2(0, 0); // center pos for display the confirm overwrite dialog + int puLastImGuiFrameCount = 0; // to be sure than only one dialog displayed per frame + float puFooterHeight = 0.0f; + bool puCanWeContinue = true; // events + bool puOkResultToConfirm = false; // to confim if ok for OverWrite + bool puIsOk = false; + bool puFileInputIsActive = false; // when input text for file or directory is active + bool puFileListViewIsActive = false; // when list view is active + std::string puDLGkey; + std::string puDLGtitle; + ImGuiFileDialogFlags puDLGflags = ImGuiFileDialogFlags_None; + UserDatas puDLGuserDatas = nullptr; + PaneFun puDLGoptionsPane = nullptr; SelectFun puDLGselFun = nullptr; - float puDLGoptionsPaneWidth = 0.0f; - bool puDLGmodal = false; - bool puNeedToExitDialog = false; + float puDLGoptionsPaneWidth = 0.0f; + bool puDLGmodal = false; + bool puNeedToExitDialog = false; - bool puUseCustomLocale = false; - int puLocaleCategory = LC_ALL; // locale category to use - std::string puLocaleBegin; // the locale who will be applied at start of the display dialog - std::string puLocaleEnd; // the locale who will be applaied at end of the display dialog + bool puUseCustomLocale = false; + int puLocaleCategory = LC_ALL; // locale category to use + std::string puLocaleBegin; // the locale who will be applied at start of the display dialog + std::string puLocaleEnd; // the locale who will be applaied at end of the display dialog - public: - void NewFrame(); // new frame, so maybe neded to do somethings, like reset events - void EndFrame(); // end frame, so maybe neded to do somethings fater all - void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog - }; + public: + void NewFrame(); // new frame, so maybe neded to do somethings, like reset events + void EndFrame(); // end frame, so maybe neded to do somethings fater all + void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog + }; - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - class FileDialog : - public BookMarkFeature, - public KeyExplorerFeature, - public ThumbnailFeature - { - private: - FileDialogInternal prFileDialogInternal; - ImGuiListClipper prFileListClipper; + class FileDialog : + public BookMarkFeature, + public KeyExplorerFeature, + public ThumbnailFeature + { + private: + FileDialogInternal prFileDialogInternal; + ImGuiListClipper prFileListClipper; - public: - bool puAnyWindowsHovered = false; // not remember why haha :) todo : to check if we can remove + public: + bool puAnyWindowsHovered = false; // not remember why haha :) todo : to check if we can remove double DpiScale; bool singleClickSel; bool mobileMode; std::string homePath; - public: - static FileDialog* Instance() // Singleton for easier accces form anywhere but only one dialog at a time - { - static FileDialog _instance; - return &_instance; - } + public: + static FileDialog* Instance() // Singleton for easier accces form anywhere but only one dialog at a time + { + static FileDialog _instance; + return &_instance; + } - public: - FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same tiem (not possible with singleton) - virtual ~FileDialog(); // ImGuiFileDialog Destructor + public: + FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same tiem (not possible with singleton) + virtual ~FileDialog(); // ImGuiFileDialog Destructor - // standard dialog - void OpenDialog( // open simple dialog (path and fileName can be specified) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vPath, // path - const std::string& vFileName, // defaut file name - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + // standard dialog + void OpenDialog( // open simple dialog (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - void OpenDialog( // open simple dialog (path and filename are obtained from filePathName) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + void OpenDialog( // open simple dialog (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - // with pane - void OpenDialog( // open dialog with custom right pane (path and fileName can be specified) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vPath, // path - const std::string& vFileName, // defaut file name - const PaneFun& vSidePane, // side pane - const float& vSidePaneWidth = 250.0f, // side pane width - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + // with pane + void OpenDialog( // open dialog with custom right pane (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - void OpenDialog( // open dialog with custom right pane (path and filename are obtained from filePathName) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) - const PaneFun& vSidePane, // side pane - const float& vSidePaneWidth = 250.0f, // side pane width - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + void OpenDialog( // open dialog with custom right pane (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - // modal dialog - void OpenModal( // open simple modal (path and fileName can be specified) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vPath, // path - const std::string& vFileName, // defaut file name - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + // modal dialog + void OpenModal( // open simple modal (path and fileName can be specified) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - void OpenModal( // open simple modal (path and fielname are obtained from filePathName) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + void OpenModal( // open simple modal (path and fielname are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - // with pane - void OpenModal( // open modal with custom right pane (path and filename are obtained from filePathName) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vPath, // path - const std::string& vFileName, // defaut file name - const PaneFun& vSidePane, // side pane - const float& vSidePaneWidth = 250.0f, // side pane width - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + // with pane + void OpenModal( // open modal with custom right pane (path and filename are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vPath, // path + const std::string& vFileName, // defaut file name + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - void OpenModal( // open modal with custom right pane (path and fielname are obtained from filePathName) - const std::string& vKey, // key dialog - const std::string& vTitle, // title - const char* vFilters, // filters - const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) - const PaneFun& vSidePane, // side pane - const float& vSidePaneWidth = 250.0f, // side pane width - const int& vCountSelectionMax = 1, // count selection max - UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags - SelectFun vSelectFun = nullptr); // function to be called on file click + void OpenModal( // open modal with custom right pane (path and fielname are obtained from filePathName) + const std::string& vKey, // key dialog + const std::string& vTitle, // title + const char* vFilters, // filters + const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName) + const PaneFun& vSidePane, // side pane + const float& vSidePaneWidth = 250.0f, // side pane width + const int& vCountSelectionMax = 1, // count selection max + UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags = 0, // ImGuiFileDialogFlags + SelectFun vSelectFun = nullptr); // function to be called on file click - // Display / Close dialog form - bool Display( // Display the dialog. return true if a result was obtained (Ok or not) - const std::string& vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) - ImGuiWindowFlags vFlags = ImGuiWindowFlags_NoCollapse, // ImGuiWindowFlags - ImVec2 vMinSize = ImVec2(0, 0), // mininmal size contraint for the ImGuiWindow - ImVec2 vMaxSize = ImVec2(FLT_MAX, FLT_MAX)); // maximal size contraint for the ImGuiWindow - void Close(); // close dialog + // Display / Close dialog form + bool Display( // Display the dialog. return true if a result was obtained (Ok or not) + const std::string& vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) + ImGuiWindowFlags vFlags = ImGuiWindowFlags_NoCollapse, // ImGuiWindowFlags + ImVec2 vMinSize = ImVec2(0, 0), // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize = ImVec2(FLT_MAX, FLT_MAX)); // maximal size contraint for the ImGuiWindow + void Close(); // close dialog - // queries - bool WasOpenedThisFrame(const std::string& vKey) const; // say if the dialog key was already opened this frame - bool WasOpenedThisFrame() const; // say if the dialog was already opened this frame - bool IsOpened(const std::string& vKey) const; // say if the key is opened - bool IsOpened() const; // say if the dialog is opened somewhere - std::string GetOpenedKey() const; // return the dialog key who is opened, return nothing if not opened + // queries + bool WasOpenedThisFrame(const std::string& vKey) const; // say if the dialog key was already opened this frame + bool WasOpenedThisFrame() const; // say if the dialog was already opened this frame + bool IsOpened(const std::string& vKey) const; // say if the key is opened + bool IsOpened() const; // say if the dialog is opened somewhere + std::string GetOpenedKey() const; // return the dialog key who is opened, return nothing if not opened - // get result - bool IsOk() const; // true => Dialog Closed with Ok result / false : Dialog closed with cancel result - std::map GetSelection(); // Open File behavior : will return selection via a map - std::string GetFilePathName(); // Save File behavior : will always return the content of the field with current filter extention and current path - std::string GetCurrentFileName(); // Save File behavior : will always return the content of the field with current filter extention - std::string GetCurrentPath(); // will return current path - std::string GetCurrentFilter(); // will return selected filter - UserDatas GetUserDatas() const; // will return user datas send with Open Dialog/Modal + // get result + bool IsOk() const; // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + std::map GetSelection(); // Open File behavior : will return selection via a map + std::string GetFilePathName(); // Save File behavior : will always return the content of the field with current filter extention and current path + std::string GetCurrentFileName(); // Save File behavior : will always return the content of the field with current filter extention + std::string GetCurrentPath(); // will return current path + std::string GetCurrentFilter(); // will return selected filter + UserDatas GetUserDatas() const; // will return user datas send with Open Dialog/Modal - // file style by extentions - void SetFileStyle( // SetExtention datas for have custom display of particular file type - const IGFD_FileStyleFlags& vFlags, // file style - const char* vCriteria, // extention filter to tune - const FileStyle& vInfos); // Filter Extention Struct who contain Color and Icon/Text for the display of the file with extention filter - void SetFileStyle( // SetExtention datas for have custom display of particular file type - const IGFD_FileStyleFlags& vFlags, // file style - const char* vCriteria, // extention filter to tune - const ImVec4& vColor, // wanted color for the display of the file with extention filter - const std::string& vIcon = "", // wanted text or icon of the file with extention filter - ImFont *vFont = nullptr); // wantes font - bool GetFileStyle( // GetExtention datas. return true is extention exist - const IGFD_FileStyleFlags& vFlags, // file style - const std::string& vCriteria, // extention filter (same as used in SetExtentionInfos) - ImVec4* vOutColor, // color to retrieve - std::string* vOutIcon = nullptr, // icon or text to retrieve + // file style by extentions + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const FileStyle& vInfos); // Filter Extention Struct who contain Color and Icon/Text for the display of the file with extention filter + void SetFileStyle( // SetExtention datas for have custom display of particular file type + const IGFD_FileStyleFlags& vFlags, // file style + const char* vCriteria, // extention filter to tune + const ImVec4& vColor, // wanted color for the display of the file with extention filter + const std::string& vIcon = "", // wanted text or icon of the file with extention filter + ImFont *vFont = nullptr); // wantes font + bool GetFileStyle( // GetExtention datas. return true is extention exist + const IGFD_FileStyleFlags& vFlags, // file style + const std::string& vCriteria, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + std::string* vOutIcon = nullptr, // icon or text to retrieve ImFont** vOutFont = nullptr); // font to retreive - void ClearFilesStyle(); // clear extentions setttings + void ClearFilesStyle(); // clear extentions setttings - void SetLocales( // set locales to use before and after the dialog display - const int& vLocaleCategory, // set local category - const std::string& vLocaleBegin, // locale to use at begining of the dialog display - const std::string& vLocaleEnd); // locale to use at the end of the dialog display + void SetLocales( // set locales to use before and after the dialog display + const int& vLocaleCategory, // set local category + const std::string& vLocaleBegin, // locale to use at begining of the dialog display + const std::string& vLocaleEnd); // locale to use at the end of the dialog display - protected: - void NewFrame(); // new frame just at begining of display - void EndFrame(); // end frame just at end of display - void QuitFrame(); // quit frame when qui quit the dialog + protected: + void NewFrame(); // new frame just at begining of display + void EndFrame(); // end frame just at end of display + void QuitFrame(); // quit frame when qui quit the dialog - // others - bool prConfirm_Or_OpenOverWriteFileDialog_IfNeeded( - bool vLastAction, ImGuiWindowFlags vFlags); // treatment of the result, start the confirm to overwrite dialog if needed (if defined with flag) - - public: - // dialog parts - virtual void prDrawHeader(); // draw header part of the dialog (bookmark btn, dir creation, path composer, search bar) - virtual bool prDrawContent(); // draw content part of the dialog (bookmark pane, file list, side pane) - virtual bool prDrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's) + // others + bool prConfirm_Or_OpenOverWriteFileDialog_IfNeeded( + bool vLastAction, ImGuiWindowFlags vFlags); // treatment of the result, start the confirm to overwrite dialog if needed (if defined with flag) + + public: + // dialog parts + virtual void prDrawHeader(); // draw header part of the dialog (bookmark btn, dir creation, path composer, search bar) + virtual bool prDrawContent(); // draw content part of the dialog (bookmark pane, file list, side pane) + virtual bool prDrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's) - // widgets components - virtual void prDrawSidePane(float vHeight); // draw side pane - virtual int prSelectableItem(int vidx, - std::shared_ptr vInfos, - bool vSelected, const char* vFmt, ...); // draw a custom selectable behavior item - virtual bool prDrawFileListView(ImVec2 vSize); // draw file list view (default mode) + // widgets components + virtual void prDrawSidePane(float vHeight); // draw side pane + virtual int prSelectableItem(int vidx, + std::shared_ptr vInfos, + bool vSelected, const char* vFmt, ...); // draw a custom selectable behavior item + virtual bool prDrawFileListView(ImVec2 vSize); // draw file list view (default mode) #ifdef USE_THUMBNAILS - virtual void prDrawThumbnailsListView(ImVec2 vSize); // draw file list view with small thumbnails on the same line - virtual void prDrawThumbnailsGridView(ImVec2 vSize); // draw a grid of small thumbnails + virtual void prDrawThumbnailsListView(ImVec2 vSize); // draw file list view with small thumbnails on the same line + virtual void prDrawThumbnailsGridView(ImVec2 vSize); // draw a grid of small thumbnails #endif - // to be called only by these function and theirs overrides - // - prDrawFileListView - // - prDrawThumbnailsListView - // - prDrawThumbnailsGridView - void prBeginFileColorIconStyle( - std::shared_ptr vFileInfos, - bool& vOutShowColor, - std::string& vOutStr, - ImFont** vOutFont); // begin style apply of filter with color an icon if any - void prEndFileColorIconStyle( - const bool& vShowColor, - ImFont* vFont); // end style apply of filter - }; + // to be called only by these function and theirs overrides + // - prDrawFileListView + // - prDrawThumbnailsListView + // - prDrawThumbnailsGridView + void prBeginFileColorIconStyle( + std::shared_ptr vFileInfos, + bool& vOutShowColor, + std::string& vOutStr, + ImFont** vOutFont); // begin style apply of filter with color an icon if any + void prEndFileColorIconStyle( + const bool& vShowColor, + ImFont* vFont); // end style apply of filter + }; } typedef IGFD::UserDatas IGFDUserDatas; @@ -1374,229 +1374,229 @@ typedef struct IGFD_Selection IGFD_Selection; struct IGFD_Selection_Pair { - char* fileName; - char* filePathName; + char* fileName; + char* filePathName; }; -IMGUIFILEDIALOG_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(); // return an initialized IGFD_Selection_Pair -IMGUIFILEDIALOG_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair); // destroy the content of a IGFD_Selection_Pair +IMGUIFILEDIALOG_API IGFD_Selection_Pair IGFD_Selection_Pair_Get(); // return an initialized IGFD_Selection_Pair +IMGUIFILEDIALOG_API void IGFD_Selection_Pair_DestroyContent(IGFD_Selection_Pair* vSelection_Pair); // destroy the content of a IGFD_Selection_Pair struct IGFD_Selection { - IGFD_Selection_Pair* table; // 0 - size_t count; // 0U + IGFD_Selection_Pair* table; // 0 + size_t count; // 0U }; -IMGUIFILEDIALOG_API IGFD_Selection IGFD_Selection_Get(); // return an initialized IGFD_Selection -IMGUIFILEDIALOG_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection); // destroy the content of a IGFD_Selection +IMGUIFILEDIALOG_API IGFD_Selection IGFD_Selection_Get(); // return an initialized IGFD_Selection +IMGUIFILEDIALOG_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection); // destroy the content of a IGFD_Selection // constructor / destructor -IMGUIFILEDIALOG_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context -IMGUIFILEDIALOG_API void IGFD_Destroy(ImGuiFileDialog* vContext); // destroy the filedialog context +IMGUIFILEDIALOG_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context +IMGUIFILEDIALOG_API void IGFD_Destroy(ImGuiFileDialog* vContext); // destroy the filedialog context -typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane +typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane #ifdef USE_THUMBNAILS -typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture -typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture +typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture +typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture #endif // USE_THUMBNAILS -IMGUIFILEDIALOG_API void IGFD_OpenDialog( // open a standard dialog - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vPath, // path - const char* vFileName, // defaut file name - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenDialog( // open a standard dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenDialog2( // open a standard dialog - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vFilePathName, // defaut file path name (path and filename witl be extracted from it) - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenDialog2( // open a standard dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file path name (path and filename witl be extracted from it) + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog( // open a standard dialog with pane - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vPath, // path - const char* vFileName, // defaut file name - const IGFD_PaneFun vSidePane, // side pane - const float vSidePaneWidth, // side pane base width - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog( // open a standard dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog2( // open a standard dialog with pane - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) - const IGFD_PaneFun vSidePane, // side pane - const float vSidePaneWidth, // side pane base width - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenPaneDialog2( // open a standard dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenModal( // open a modal dialog - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vPath, // path - const char* vFileName, // defaut file name - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenModal( // open a modal dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenModal2( // open a modal dialog - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenModal2( // open a modal dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenPaneModal( // open a modal dialog with pane - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vPath, // path - const char* vFileName, // defaut file name - const IGFD_PaneFun vSidePane, // side pane - const float vSidePaneWidth, // side pane base width - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal( // open a modal dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vPath, // path + const char* vFileName, // defaut file name + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API void IGFD_OpenPaneModal2( // open a modal dialog with pane - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog - const char* vTitle, // title - const char* vFilters, // filters/filter collections. set it to null for directory mode - const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) - const IGFD_PaneFun vSidePane, // side pane - const float vSidePaneWidth, // side pane base width - const int vCountSelectionMax, // count selection max - void* vUserDatas, // user datas (can be retrieved in pane) - ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags +IMGUIFILEDIALOG_API void IGFD_OpenPaneModal2( // open a modal dialog with pane + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog + const char* vTitle, // title + const char* vFilters, // filters/filter collections. set it to null for directory mode + const char* vFilePathName, // defaut file name (path and filename witl be extracted from it) + const IGFD_PaneFun vSidePane, // side pane + const float vSidePaneWidth, // side pane base width + const int vCountSelectionMax, // count selection max + void* vUserDatas, // user datas (can be retrieved in pane) + ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags -IMGUIFILEDIALOG_API bool IGFD_DisplayDialog( // Display the dialog - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) - ImGuiWindowFlags vFlags, // ImGuiWindowFlags - ImVec2 vMinSize, // mininmal size contraint for the ImGuiWindow - ImVec2 vMaxSize); // maximal size contraint for the ImGuiWindow +IMGUIFILEDIALOG_API bool IGFD_DisplayDialog( // Display the dialog + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey, // key dialog to display (if not the same key as defined by OpenDialog/Modal => no opening) + ImGuiWindowFlags vFlags, // ImGuiWindowFlags + ImVec2 vMinSize, // mininmal size contraint for the ImGuiWindow + ImVec2 vMaxSize); // maximal size contraint for the ImGuiWindow -IMGUIFILEDIALOG_API void IGFD_CloseDialog( // Close the dialog - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API void IGFD_CloseDialog( // Close the dialog + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API bool IGFD_IsOk( // true => Dialog Closed with Ok result / false : Dialog closed with cancel result - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API bool IGFD_IsOk( // true => Dialog Closed with Ok result / false : Dialog closed with cancel result + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API bool IGFD_WasKeyOpenedThisFrame( // say if the dialog key was already opened this frame - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vKey); +IMGUIFILEDIALOG_API bool IGFD_WasKeyOpenedThisFrame( // say if the dialog key was already opened this frame + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vKey); -IMGUIFILEDIALOG_API bool IGFD_WasOpenedThisFrame( // say if the dialog was already opened this frame - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API bool IGFD_WasOpenedThisFrame( // say if the dialog was already opened this frame + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API bool IGFD_IsKeyOpened( // say if the dialog key is opened - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vCurrentOpenedKey); // the dialog key +IMGUIFILEDIALOG_API bool IGFD_IsKeyOpened( // say if the dialog key is opened + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vCurrentOpenedKey); // the dialog key -IMGUIFILEDIALOG_API bool IGFD_IsOpened( // say if the dialog is opened somewhere - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API bool IGFD_IsOpened( // say if the dialog is opened somewhere + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API IGFD_Selection IGFD_GetSelection( // Open File behavior : will return selection via a map - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API IGFD_Selection IGFD_GetSelection( // Open File behavior : will return selection via a map + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API char* IGFD_GetFilePathName( // Save File behavior : will always return the content of the field with current filter extention and current path - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API char* IGFD_GetFilePathName( // Save File behavior : will always return the content of the field with current filter extention and current path + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API char* IGFD_GetCurrentFileName( // Save File behavior : will always return the content of the field with current filter extention - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFileName( // Save File behavior : will always return the content of the field with current filter extention + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API char* IGFD_GetCurrentPath( // will return current path - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API char* IGFD_GetCurrentPath( // will return current path + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API char* IGFD_GetCurrentFilter( // will return selected filter - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API char* IGFD_GetCurrentFilter( // will return selected filter + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API void* IGFD_GetUserDatas( // will return user datas send with Open Dialog/Modal - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API void* IGFD_GetUserDatas( // will return user datas send with Open Dialog/Modal + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API void IGFD_SetFileStyle( // SetExtention datas for have custom display of particular file type - ImGuiFileDialog* vContext, // ImGuiFileDialog context - IGFD_FileStyleFlags vFileStyleFlags, // file style type - const char* vFilter, // extention filter to tune - ImVec4 vColor, // wanted color for the display of the file with extention filter - const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) - ImFont* vFont); // wanted font pointer +IMGUIFILEDIALOG_API void IGFD_SetFileStyle( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + ImVec4 vColor, // wanted color for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer -IMGUIFILEDIALOG_API void IGFD_SetFileStyle2( // SetExtention datas for have custom display of particular file type - ImGuiFileDialog* vContext, // ImGuiFileDialog context - IGFD_FileStyleFlags vFileStyleFlags, // file style type - const char* vFilter, // extention filter to tune - float vR, float vG, float vB, float vA, // wanted color channels RGBA for the display of the file with extention filter - const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) - ImFont* vFont); // wanted font pointer +IMGUIFILEDIALOG_API void IGFD_SetFileStyle2( // SetExtention datas for have custom display of particular file type + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter to tune + float vR, float vG, float vB, float vA, // wanted color channels RGBA for the display of the file with extention filter + const char* vIconText, // wanted text or icon of the file with extention filter (can be sued with font icon) + ImFont* vFont); // wanted font pointer IMGUIFILEDIALOG_API bool IGFD_GetFileStyle( - ImGuiFileDialog* vContext, // ImGuiFileDialog context - IGFD_FileStyleFlags vFileStyleFlags, // file style type - const char* vFilter, // extention filter (same as used in SetExtentionInfos) - ImVec4* vOutColor, // color to retrieve - char** vOutIconText, // icon or text to retrieve - ImFont** vOutFont); // font pointer to retrived + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_FileStyleFlags vFileStyleFlags, // file style type + const char* vFilter, // extention filter (same as used in SetExtentionInfos) + ImVec4* vOutColor, // color to retrieve + char** vOutIconText, // icon or text to retrieve + ImFont** vOutFont); // font pointer to retrived -IMGUIFILEDIALOG_API void IGFD_ClearFilesStyle( // clear extentions setttings - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API void IGFD_ClearFilesStyle( // clear extentions setttings + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API void SetLocales( // set locales to use before and after display - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const int vCategory, // set local category - const char* vBeginLocale, // locale to use at begining of the dialog display - const char* vEndLocale); // locale to set at end of the dialog display +IMGUIFILEDIALOG_API void SetLocales( // set locales to use before and after display + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const int vCategory, // set local category + const char* vBeginLocale, // locale to use at begining of the dialog display + const char* vEndLocale); // locale to set at end of the dialog display #ifdef USE_EXPLORATION_BY_KEYS -IMGUIFILEDIALOG_API void IGFD_SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys - ImGuiFileDialog* vContext, // ImGuiFileDialog context - float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds +IMGUIFILEDIALOG_API void IGFD_SetFlashingAttenuationInSeconds( // set the flashing time of the line in file list when use exploration keys + ImGuiFileDialog* vContext, // ImGuiFileDialog context + float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds #endif #ifdef USE_BOOKMARK -IMGUIFILEDIALOG_API char* IGFD_SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API char* IGFD_SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file + ImGuiFileDialog* vContext); // ImGuiFileDialog context -IMGUIFILEDIALOG_API void IGFD_DeserializeBookmarks( // deserialize bookmarks : load bookmar buffer to load in the dialog (saved from previous use with SerializeBookmarks()) - ImGuiFileDialog* vContext, // ImGuiFileDialog context - const char* vBookmarks); // bookmark buffer to load +IMGUIFILEDIALOG_API void IGFD_DeserializeBookmarks( // deserialize bookmarks : load bookmar buffer to load in the dialog (saved from previous use with SerializeBookmarks()) + ImGuiFileDialog* vContext, // ImGuiFileDialog context + const char* vBookmarks); // bookmark buffer to load #endif #ifdef USE_THUMBNAILS -IMGUIFILEDIALOG_API void SetCreateThumbnailCallback( // define the callback for create the thumbnails texture - ImGuiFileDialog* vContext, // ImGuiFileDialog context - IGFD_CreateThumbnailFun vCreateThumbnailFun); // the callback for create the thumbnails texture +IMGUIFILEDIALOG_API void SetCreateThumbnailCallback( // define the callback for create the thumbnails texture + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_CreateThumbnailFun vCreateThumbnailFun); // the callback for create the thumbnails texture -IMGUIFILEDIALOG_API void SetDestroyThumbnailCallback( // define the callback for destroy the thumbnails texture - ImGuiFileDialog* vContext, // ImGuiFileDialog context - IGFD_DestroyThumbnailFun vDestroyThumbnailFun); // the callback for destroy the thumbnails texture +IMGUIFILEDIALOG_API void SetDestroyThumbnailCallback( // define the callback for destroy the thumbnails texture + ImGuiFileDialog* vContext, // ImGuiFileDialog context + IGFD_DestroyThumbnailFun vDestroyThumbnailFun); // the callback for destroy the thumbnails texture -IMGUIFILEDIALOG_API void ManageGPUThumbnails( // must be call in gpu zone, possibly a thread, will call the callback for create / destroy the textures - ImGuiFileDialog* vContext); // ImGuiFileDialog context +IMGUIFILEDIALOG_API void ManageGPUThumbnails( // must be call in gpu zone, possibly a thread, will call the callback for create / destroy the textures + ImGuiFileDialog* vContext); // ImGuiFileDialog context #endif // USE_THUMBNAILS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 29dfeccfe725e31bde3d9a9a1ecb143dd4064a93 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 16 Jun 2023 17:30:11 -0500 Subject: [PATCH 39/46] change chan osc range - PLEASE READ as of now the range is ~32768, either from -16384 to 16383, or 0 to 32767. it previously was -32768 to 32767 (~65536). this change was made to better suit chips that only output a positive value. if you are working on a new chip, update your code and shift right by one if necessary. --- extern/SAASound/src/SAADevice.cpp | 14 +++++++------- src/engine/platform/amiga.cpp | 2 +- src/engine/platform/arcade.cpp | 4 ++-- src/engine/platform/ay.cpp | 12 ++++++------ src/engine/platform/ay8930.cpp | 6 +++--- src/engine/platform/bubsyswsg.cpp | 2 +- src/engine/platform/c64.cpp | 12 ++++++------ src/engine/platform/dummy.cpp | 2 +- src/engine/platform/es5506.cpp | 2 +- src/engine/platform/fds.cpp | 4 ++-- src/engine/platform/ga20.cpp | 2 +- src/engine/platform/gb.cpp | 2 +- src/engine/platform/genesis.cpp | 18 +++++++++--------- src/engine/platform/k007232.cpp | 4 ++-- src/engine/platform/mmc5.cpp | 6 +++--- src/engine/platform/msm5232.cpp | 2 +- src/engine/platform/msm6258.cpp | 2 +- src/engine/platform/msm6295.cpp | 3 +-- src/engine/platform/n163.cpp | 2 +- src/engine/platform/namcowsg.cpp | 2 +- src/engine/platform/nes.cpp | 20 ++++++++++---------- src/engine/platform/opl.cpp | 12 +++++------- src/engine/platform/opll.cpp | 4 ++-- src/engine/platform/pce.cpp | 2 +- src/engine/platform/pcmdac.cpp | 2 +- src/engine/platform/pokey.cpp | 8 ++++---- src/engine/platform/qsound.cpp | 2 +- src/engine/platform/rf5c68.cpp | 2 +- src/engine/platform/scc.cpp | 2 +- src/engine/platform/segapcm.cpp | 2 +- src/engine/platform/sm8521.cpp | 4 ++-- src/engine/platform/snes.cpp | 2 +- src/engine/platform/sound/lynx/Mikey.cpp | 2 +- src/engine/platform/sound/pokey/AltASAP.cpp | 2 +- src/engine/platform/swan.cpp | 2 +- src/engine/platform/tia.cpp | 4 ++-- src/engine/platform/tx81z.cpp | 2 +- src/engine/platform/vb.cpp | 2 +- src/engine/platform/vera.cpp | 4 ++-- src/engine/platform/vic20.cpp | 2 +- src/engine/platform/vrc6.cpp | 4 ++-- src/engine/platform/x1_010.cpp | 2 +- src/engine/platform/ym2203.cpp | 8 ++++---- src/engine/platform/ym2608.cpp | 16 ++++++++-------- src/engine/platform/ym2610.cpp | 16 ++++++++-------- src/engine/platform/ym2610b.cpp | 16 ++++++++-------- src/engine/platform/ymz280b.cpp | 2 +- src/gui/chanOsc.cpp | 6 +++--- 48 files changed, 126 insertions(+), 129 deletions(-) diff --git a/extern/SAASound/src/SAADevice.cpp b/extern/SAASound/src/SAADevice.cpp index 9064dcab..78b6e4a4 100644 --- a/extern/SAASound/src/SAADevice.cpp +++ b/extern/SAASound/src/SAADevice.cpp @@ -316,27 +316,27 @@ void CSAADevice::_TickAndOutputStereo(unsigned int& left_mixed, unsigned int& ri m_Noise0.Tick(); m_Noise1.Tick(); m_Amp0.TickAndOutputStereo(temp_left, temp_right); - oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<4; + oscBuf[0]->data[oscBuf[0]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; m_Amp1.TickAndOutputStereo(temp_left, temp_right); - oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<4; + oscBuf[1]->data[oscBuf[1]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; m_Amp2.TickAndOutputStereo(temp_left, temp_right); - oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<4; + oscBuf[2]->data[oscBuf[2]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; m_Amp3.TickAndOutputStereo(temp_left, temp_right); - oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<4; + oscBuf[3]->data[oscBuf[3]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; m_Amp4.TickAndOutputStereo(temp_left, temp_right); - oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<4; + oscBuf[4]->data[oscBuf[4]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; m_Amp5.TickAndOutputStereo(temp_left, temp_right); - oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<4; + oscBuf[5]->data[oscBuf[5]->needle++]=(temp_left+temp_right)<<3; accum_left += temp_left; accum_right += temp_right; } @@ -394,4 +394,4 @@ void CSAADevice::_TickAndOutputSeparate(unsigned int& left_mixed, unsigned int& } left_mixed = accum_left; right_mixed = accum_right; -} \ No newline at end of file +} diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 7c0b8c24..48128bc6 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -168,7 +168,7 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) { outL+=(output*sep2)>>7; outR+=(output*sep1)>>7; } - oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<2; + oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<1; } else { oscBuf[i]->data[oscBuf[i]->needle++]=0; } diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index e617fc0d..8de0fa3b 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -76,7 +76,7 @@ void DivPlatformArcade::acquire_nuked(short** buf, size_t len) { } for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]>>1; } if (o[0]<-32768) o[0]=-32768; @@ -111,7 +111,7 @@ void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; } os[0]=out_ymfm.data[0]; diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 8eaea3fe..d31f0683 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -187,9 +187,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) { buf[0][i]=ayBuf[0][0]; buf[1][i]=buf[0][i]; - oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<3; - oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<3; - oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<3; + oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<2; } } else { for (size_t i=0; idata[oscBuf[0]->needle++]=ayBuf[0][0]<<2; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; } } } diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 8561548d..51560975 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -186,9 +186,9 @@ void DivPlatformAY8930::acquire(short** buf, size_t len) { buf[1][i]=buf[0][i]; } - oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; } } diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 94202de1..bfb1e365 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -55,7 +55,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) { chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf); out+=chanOut; if (writeOscBuf==0) { - oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<6; } } } diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 977e3951..43cfbdf0 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -80,18 +80,18 @@ void DivPlatformC64::acquire(short** buf, size_t len) { sid_fp.clock(4,&buf[0][i]); if (++writeOscBuf>=4) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>6; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>6; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>6; } } else { sid.clock(); buf[0][i]=sid.output(); if (++writeOscBuf>=16) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>6; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>6; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>6; } } } diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 541630de..04763898 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -32,7 +32,7 @@ void DivPlatformDummy::acquire(short** buf, size_t len) { if (chan[j].active) { if (!isMuted[j]) { chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12; - oscBuf[j]->data[oscBuf[j]->needle++]=chanOut; + oscBuf[j]->data[oscBuf[j]->needle++]=chanOut>>1; out+=chanOut; } else { oscBuf[j]->data[oscBuf[j]->needle++]=0; diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 5f6e2829..f388e860 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -168,7 +168,7 @@ void DivPlatformES5506::acquire(short** buf, size_t len) { buf[(o<<1)|1][h]=es5506.rout(o); } for (int i=chanMax; i>=0; i--) { - oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>5; + oscBuf[i]->data[oscBuf[i]->needle++]=(es5506.voice_lout(i)+es5506.voice_rout(i))>>6; } } } diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 420f3e3a..2674e954 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -64,7 +64,7 @@ void DivPlatformFDS::acquire_puNES(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample<<1; + oscBuf->data[oscBuf->needle++]=sample; } } } @@ -80,7 +80,7 @@ void DivPlatformFDS::acquire_NSFPlay(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample<<1; + oscBuf->data[oscBuf->needle++]=sample; } } } diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 7b9d86a1..60764585 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -75,7 +75,7 @@ void DivPlatformGA20::acquire(short** buf, size_t len) { ga20.sound_stream_update(buffer, 1); buf[0][h]=(signed int)(ga20Buf[0][h]+ga20Buf[1][h]+ga20Buf[2][h]+ga20Buf[3][h])>>2; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h]; + oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h]>>1; } } } diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 9a5d6d7d..6a3b92d0 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -74,7 +74,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) { buf[1][i]=gb->apu_output.final_sample.right; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<5; } } } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 8e92c40f..072e8bc0 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -184,18 +184,18 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { if (i==5) { if (fm.dacen) { if (softPCM) { - oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<7; - oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7; + oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; + oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { - oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?1:6),-32768,32767); oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { - oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?2:7),-32768,32767); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fm.ch_out[i]<<(chipType==2?1:6),-32768,32767); } } @@ -243,16 +243,16 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { //OPN2_Write(&fm,0,0); for (int i=0; i<6; i++) { - int chOut=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<6; + int chOut=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))<<5; if (chOut<-32768) chOut=-32768; if (chOut>32767) chOut=32767; if (i==5) { if (fm_ymfm->debug_dac_enable()) { if (softPCM) { - oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<7; - oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<7; + oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; + oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index 0b11d45f..bb0bf7a9 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -79,14 +79,14 @@ void DivPlatformK007232::acquire(short** buf, size_t len) { buf[0][h]=(lout[0]+lout[1])<<4; buf[1][h]=(rout[0]+rout[1])<<4; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<4; + oscBuf[i]->data[oscBuf[i]->needle++]=(lout[i]+rout[i])<<3; } } else { const unsigned char vol=regPool[0xc]; const signed int out[2]={(k007232.output(0)*(vol&0xf)),(k007232.output(1)*((vol>>4)&0xf))}; buf[0][h]=(out[0]+out[1])<<4; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=out[i]<<4; } } } diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 11b04e81..0edc83b2 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -85,9 +85,9 @@ void DivPlatformMMC5::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<7); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<7); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<6); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<6); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<6); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<5); } } } diff --git a/src/engine/platform/msm5232.cpp b/src/engine/platform/msm5232.cpp index eb8d5cbf..55df6128 100644 --- a/src/engine/platform/msm5232.cpp +++ b/src/engine/platform/msm5232.cpp @@ -60,7 +60,7 @@ void DivPlatformMSM5232::acquire(short** buf, size_t len) { ((regPool[12+(i>>4)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ ((regPool[12+(i>>4)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ ((regPool[12+(i>>4)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) - )<<3; + )<<2; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767); } diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index 6591eda5..31002a9c 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -84,7 +84,7 @@ void DivPlatformMSM6258::acquire(short** buf, size_t len) { } else { buf[0][h]=(msmPan&2)?msmOut:0; buf[1][h]=(msmPan&1)?msmOut:0; - oscBuf[0]->data[oscBuf[0]->needle++]=msmPan?msmOut:0; + oscBuf[0]->data[oscBuf[0]->needle++]=msmPan?(msmOut>>1):0; } } } diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index de7fedd5..2aff0006 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -79,9 +79,8 @@ void DivPlatformMSM6295::acquire(short** buf, size_t len) { if (++updateOsc>=22) { updateOsc=0; - // TODO: per-channel osc for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=msm.voice_out(i)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=msm.voice_out(i)<<5; } } } diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index ead44cc1..eee73b99 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -118,7 +118,7 @@ void DivPlatformN163::acquire(short** buf, size_t len) { buf[0][i]=out; if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<6; } // command queue diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index 87721eb8..088f1e63 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -177,7 +177,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) { }; namco->sound_stream_update(bufC,1); for (int i=0; idata[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans; + oscBuf[i]->data[oscBuf[i]->needle++]=(namco->m_channel_list[i].last_out*chans)>>1; } } } diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index a25568d0..07f9c5ae 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -115,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11); - oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11); - oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<10); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<10); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<10); + oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<10); + oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<7); } } } @@ -142,11 +142,11 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11; - oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11; - oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11; - oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11; - oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8; + oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<10; + oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<10; + oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<10; + oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<10; + oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<7; } } } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index a54667a7..b39fb8f6 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -211,7 +211,7 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (!isMuted[adpcmChan]) { os[0]-=aOut.data[0]>>3; os[1]-=aOut.data[0]>>3; - oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]; + oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]>>1; } else { oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0; } @@ -234,14 +234,13 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (fm.channel[i].out[3]!=NULL) { oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; } - oscBuf[i]->data[oscBuf[i]->needle]<<=1; oscBuf[i]->needle++; } // special - oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*6; - oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*6; - oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*6; - oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*6; + oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*3; + oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*3; + oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*3; + oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*3; } else { for (int i=0; idata[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; } - oscBuf[i]->data[oscBuf[i]->needle]<<=1; oscBuf[i]->needle++; } } diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index d0effd15..e7486b94 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -68,7 +68,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { unsigned char nextOut=cycleMapOPLL[fm.cycles]; if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { os+=(o[0]+o[1]); - if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; + if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<5; } else { if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; } @@ -76,7 +76,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) { unsigned char ch=visMapOPLL[i]; if ((i>=6 && properDrums) || !isMuted[ch]) { - oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6; + oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<5; } else { oscBuf[ch]->data[oscBuf[ch]->needle++]=0; } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index a920a4d0..47e5bbcd 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -101,7 +101,7 @@ void DivPlatformPCE::acquire(short** buf, size_t len) { pce->ResetTS(0); for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1],-32768,32767); } tempL[0]=(tempL[0]>>1)+(tempL[0]>>2); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index 283b6e24..3a00e431 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -229,7 +229,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) { } else { output=output*chan[0].vol*chan[0].envVol/16384; } - oscBuf->data[oscBuf->needle++]=output; + oscBuf->data[oscBuf->needle++]=output>>1; if (outStereo) { buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<>(depthScale+8))<=14) { oscBufDelay=0; - oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<11; - oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<11; - oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<11; - oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<11; + oscBuf[0]->data[oscBuf[0]->needle++]=pokey.outvol_0<<10; + oscBuf[1]->data[oscBuf[1]->needle++]=pokey.outvol_1<<10; + oscBuf[2]->data[oscBuf[2]->needle++]=pokey.outvol_2<<10; + oscBuf[3]->data[oscBuf[3]->needle++]=pokey.outvol_3<<10; } } } diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index b1aeab3e..d7f908f5 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -272,7 +272,7 @@ void DivPlatformQSound::acquire(short** buf, size_t len) { buf[1][h]=chip.out[1]; for (int i=0; i<19; i++) { - int data=chip.voice_output[i]<<2; + int data=chip.voice_output[i]<<1; if (data<-32768) data=-32768; if (data>32767) data=32767; oscBuf[i]->data[oscBuf[i]->needle++]=data; diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 7be43801..84522c74 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -74,7 +74,7 @@ void DivPlatformRF5C68::acquire(short** buf, size_t len) { rf5c68.sound_stream_update(bufPtrs,chBufPtrs,blockLen); for (int i=0; i<8; i++) { for (size_t j=0; jdata[oscBuf[i]->needle++]=bufC[i*2][j]+bufC[i*2+1][j]; + oscBuf[i]->data[oscBuf[i]->needle++]=(bufC[i*2][j]+bufC[i*2+1][j])>>1; } } pos+=blockLen; diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index cd97ff30..e7ad162f 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -87,7 +87,7 @@ void DivPlatformSCC::acquire(short** buf, size_t len) { buf[0][h]=out; for (int i=0; i<5; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7; + oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<6; } } } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index ce24e2fb..47411496 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -49,7 +49,7 @@ void DivPlatformSegaPCM::acquire(short** buf, size_t len) { buf[1][h]=os[1]; for (int i=0; i<16; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=pcm.lastOut[i][0]+pcm.lastOut[i][1]; + oscBuf[i]->data[oscBuf[i]->needle++]=(pcm.lastOut[i][0]+pcm.lastOut[i][1])>>1; } } } diff --git a/src/engine/platform/sm8521.cpp b/src/engine/platform/sm8521.cpp index e1f359c0..1c669a64 100644 --- a/src/engine/platform/sm8521.cpp +++ b/src/engine/platform/sm8521.cpp @@ -58,9 +58,9 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) { sm8521_sound_tick(&sm8521,8); buf[0][h]=sm8521.out<<6; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<5; } - oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<6; + oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<5; } } diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 0c9734c2..43401ebc 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -91,7 +91,7 @@ void DivPlatformSNES::acquire(short** buf, size_t len) { next=(next*254)/MAX(1,globalVolL+globalVolR); if (next<-32768) next=-32768; if (next>32767) next=32767; - oscBuf[i]->data[oscBuf[i]->needle++]=next; + oscBuf[i]->data[oscBuf[i]->needle++]=next>>1; } } } diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index 791336c1..baf376f5 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -509,7 +509,7 @@ public: } if (oscb!=NULL) { - oscb[i]->data[oscb[i]->needle++]=oscbWrite; + oscb[i]->data[oscb[i]->needle++]=oscbWrite>>1; } } diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 21bc31a2..9ed2f207 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -39,7 +39,7 @@ static constexpr int MuteInit = 2; static constexpr int MuteSerialInput = 8; //just some magick value to match the audio level of mzpokeysnd static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; -static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 4; +static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 2; struct PokeyBase { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 209e2fc2..46edfd7f 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -87,7 +87,7 @@ void DivPlatformSwan::acquire(short** buf, size_t len) { buf[0][h]=samp[0]; buf[1][h]=samp[1]; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<5; } } } diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 1836bee1..cdd11621 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -51,8 +51,8 @@ void DivPlatformTIA::acquire(short** buf, size_t len) { } if (++chanOscCounter>=114) { chanOscCounter=0; - oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; - oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]>>1; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]>>1; } } } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 935390bc..c4d921fd 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -78,7 +78,7 @@ void DivPlatformTX81Z::acquire(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; } os[0]=out_ymfm.data[0]; diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index 3701b5aa..6732bded 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -107,7 +107,7 @@ void DivPlatformVB::acquire(short** buf, size_t len) { tempL=0; tempR=0; for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*8; + oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*4; tempL+=vb->last_output[i][0]; tempR+=vb->last_output[i][1]; } diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 57e6f29f..8357f756 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -107,9 +107,9 @@ void DivPlatformVERA::acquire(short** buf, size_t len) { pos++; for (int i=0; i<16; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=psg->channels[i].lastOut<<4; + oscBuf[i]->data[oscBuf[i]->needle++]=psg->channels[i].lastOut<<3; } - int pcmOut=whyCallItBuf[2][i]+whyCallItBuf[3][i]; + int pcmOut=(whyCallItBuf[2][i]+whyCallItBuf[3][i])>>1; if (pcmOut<-32768) pcmOut=-32768; if (pcmOut>32767) pcmOut=32767; oscBuf[16]->data[oscBuf[16]->needle++]=pcmOut; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index bd25b528..cb78e9b4 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -69,7 +69,7 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) { vic_sound_machine_calculate_samples(vic,&samp,1,1,0,SAMP_DIVIDER); buf[0][h]=samp; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<11):0; + oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<10):0; } } } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 2aeb3897..7834ecbe 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -87,9 +87,9 @@ void DivPlatformVRC6::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<10; + oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<9; } - oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10; + oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<9; } // Command part diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 44f94d99..29601ae9 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -222,7 +222,7 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) { if (stereo) buf[1][h]=tempR; for (int i=0; i<16; i++) { - int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3; + int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<2; oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767); } } diff --git a/src/engine/platform/ym2203.cpp b/src/engine/platform/ym2203.cpp index cc2d1f3f..69d1ca8a 100644 --- a/src/engine/platform/ym2203.cpp +++ b/src/engine/platform/ym2203.cpp @@ -231,11 +231,11 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) { buf[0][h]=os; for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; } for (int i=3; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1; } } } @@ -282,11 +282,11 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) { for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; } for (int i=3; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]; + oscBuf[i]->data[oscBuf[i]->needle++]=fmout.data[i-2]>>1; } } } diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 7c10e76c..73280bcb 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -402,19 +402,19 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1; } - oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } @@ -471,19 +471,19 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) { buf[1][h]=os[1]; for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; } ssge->get_last_out(ssgOut); for (int i=6; i<9; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-6]>>1; } for (int i=9; i<15; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-9]->get_last_out(0)+adpcmAChan[i-9]->get_last_out(1))>>1; } - oscBuf[15]->data[oscBuf[15]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[15]->data[oscBuf[15]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index e9cb021d..303604d6 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -333,19 +333,19 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[bchOffs[i]]>>1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1; } - oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } @@ -404,19 +404,19 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) { buf[1][h]=os[1]; for (int i=0; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1; } - oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 7d28a801..b4292a14 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -401,19 +401,19 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=fm_nuked.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=fm_nuked.ch_out[i]>>1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1; } - oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } @@ -471,19 +471,19 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) { for (int i=0; idata[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1)); + oscBuf[i]->data[oscBuf[i]->needle++]=(fmChan[i]->debug_output(0)+fmChan[i]->debug_output(1))>>1; } ssge->get_last_out(ssgOut); for (int i=psgChanOffs; idata[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]; + oscBuf[i]->data[oscBuf[i]->needle++]=ssgOut.data[i-psgChanOffs]>>1; } for (int i=adpcmAChanOffs; idata[oscBuf[i]->needle++]=adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1); + oscBuf[i]->data[oscBuf[i]->needle++]=(adpcmAChan[i-adpcmAChanOffs]->get_last_out(0)+adpcmAChan[i-adpcmAChanOffs]->get_last_out(1))>>1; } - oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=abe->get_last_out(0)+abe->get_last_out(1); + oscBuf[adpcmBChanOffs]->data[oscBuf[adpcmBChanOffs]->needle++]=(abe->get_last_out(0)+abe->get_last_out(1))>>1; } } diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index a8838ae9..496f3568 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -76,7 +76,7 @@ void DivPlatformYMZ280B::acquire(short** buf, size_t len) { for (int j=0; j<8; j++) { dataL+=why[j*2][i]; dataR+=why[j*2+1][i]; - oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)why[j*2][i]+why[j*2+1][i])/2); + oscBuf[j]->data[oscBuf[j]->needle++]=(short)(((int)why[j*2][i]+why[j*2+1][i])/4); } buf[0][pos]=(short)(dataL/8); buf[1][pos]=(short)(dataR/8); diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index 3e580524..c278463e 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -93,7 +93,7 @@ void FurnaceGUI::calcChanOsc() { unsigned short needlePos=buf->needle; needlePos-=displaySize; for (unsigned short i=0; i<512; i++) { - float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f; + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/32768.0f; if (minLevel>y) minLevel=y; if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/65536.0f; + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; if (minLevel>y) minLevel=y; if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/65536.0f; + float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; y-=dcOff; if (y<-0.5f) y=-0.5f; if (y>0.5f) y=0.5f; From d35fa6f1bc02971c95eedcfcb9af2d7c7b3b5fe6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 16 Jun 2023 18:43:33 -0500 Subject: [PATCH 40/46] new demo song by Xan --- demos/snes/changeyourheart.fur | Bin 0 -> 216906 bytes src/gui/about.cpp | 1 + 2 files changed, 1 insertion(+) create mode 100644 demos/snes/changeyourheart.fur diff --git a/demos/snes/changeyourheart.fur b/demos/snes/changeyourheart.fur new file mode 100644 index 0000000000000000000000000000000000000000..7b671c3a26e21f15b0e94c7e225c574c5c2679ae GIT binary patch literal 216906 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

P{(H5euaP;|$t%H=Ai3G!e_Zri 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

# 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%0Df9Cxj?a1lc36y!IicKDRe%%7FUaN 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

w*c~w#=kJ4e>M6E?lt>8 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 literal 0 HcmV?d00001 diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 449a2714..4076dbc6 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -124,6 +124,7 @@ const char* aboutLine[]={ "Ultraprogramer", "UserSniper", "Weeppiko", + "Xan", "Yuzu4K", "Zaxolotl", "ZoomTen (Zumi)", From e246501b3a68b4e4c9e5126efe0e708f84bcb601 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 02:55:45 -0500 Subject: [PATCH 41/46] YM2612: fix DAC chan osc --- src/engine/platform/genesis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 072e8bc0..f33ce4a7 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -187,7 +187,7 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.dacdata<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(fm.dacdata^0x100)<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { From b9414de49d01fac9e579bb39fcc4a6669dd7d518 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 03:14:06 -0500 Subject: [PATCH 42/46] OPLL: fix vol macro for drums --- src/engine/platform/opll.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index e7486b94..596735a4 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -107,7 +107,6 @@ void DivPlatformOPLL::tick(bool sysTick) { rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); - break; } else if (i<6 || !drums) { if (i<9) { rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); From 4776020d05049c8c0fdfdadad24de96efa0e7501 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 04:27:22 -0500 Subject: [PATCH 43/46] GUI: per-chan osc improvements --- src/engine/platform/gb.cpp | 2 +- src/engine/platform/sm8521.cpp | 4 +- src/gui/chanOsc.cpp | 360 +++++++++++++++++++++------------ src/gui/gui.cpp | 24 +++ src/gui/gui.h | 7 +- 5 files changed, 265 insertions(+), 132 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 6a3b92d0..9a5d6d7d 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -74,7 +74,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) { buf[1][i]=gb->apu_output.final_sample.right; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=(gb->apu_output.current_sample[i].left+gb->apu_output.current_sample[i].right)<<6; } } } diff --git a/src/engine/platform/sm8521.cpp b/src/engine/platform/sm8521.cpp index 1c669a64..e72616bd 100644 --- a/src/engine/platform/sm8521.cpp +++ b/src/engine/platform/sm8521.cpp @@ -58,9 +58,9 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) { sm8521_sound_tick(&sm8521,8); buf[0][h]=sm8521.out<<6; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<7; } - oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<5; + oscBuf[2]->data[oscBuf[2]->needle++]=sm8521.noise.base.out<<7; } } diff --git a/src/gui/chanOsc.cpp b/src/gui/chanOsc.cpp index c278463e..e6306db2 100644 --- a/src/gui/chanOsc.cpp +++ b/src/gui/chanOsc.cpp @@ -22,6 +22,7 @@ #include "../ta-log.h" #include "imgui.h" #include "imgui_internal.h" +#include "misc/cpp/imgui_stdlib.h" #define FURNACE_FFT_SIZE 4096 #define FURNACE_FFT_RATE 80.0 @@ -284,159 +285,266 @@ void FurnaceGUI::drawChanOsc() { ImGui::ColorPicker4("Color",(float*)&chanOscColor); } + ImGui::Text("Text format:"); + ImGui::SameLine(); + ImGui::InputText("##TextFormat",&chanOscTextFormat); + if (ImGui::IsItemHovered()) { + if (ImGui::BeginTooltip()) { + ImGui::TextUnformatted( + "format guide:\n" + "- %c: channel name\n" + "- %C: channel short name\n" + "- %d: channel number (starting from 0)\n" + "- %D: channel number (starting from 1)\n" + "- %i: instrument name\n" + "- %I: instrument number (decimal)\n" + "- %x: instrument number (hex)\n" + "- %s: chip name\n" + "- %S: chip ID\n" + "- %v: volume (decimal)\n" + "- %V: volume (percentage)\n" + "- %b: volume (hex)\n" + "- %%: percent sign" + ); + ImGui::EndTooltip(); + } + } + + ImGui::ColorEdit4("Text color",(float*)&chanOscTextColor); + if (ImGui::Button("OK")) { chanOscOptions=false; } - } + } else { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); + float availY=ImGui::GetContentRegionAvail().y; + if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { + std::vector oscBufs; + std::vector oscFFTs; + std::vector oscChans; + int chans=e->getTotalChannelCount(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[512]; - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f)); - float availY=ImGui::GetContentRegionAvail().y; - if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) { - std::vector oscBufs; - std::vector oscFFTs; - std::vector oscChans; - int chans=e->getTotalChannelCount(); - ImGuiWindow* window=ImGui::GetCurrentWindow(); - ImVec2 waveform[512]; + ImGuiStyle& style=ImGui::GetStyle(); - ImGuiStyle& style=ImGui::GetStyle(); - - for (int i=0; igetOscBuffer(i); - if (buf!=NULL && e->curSubSong->chanShow[i]) { - oscBufs.push_back(buf); - oscFFTs.push_back(&chanOscChan[i]); - oscChans.push_back(i); + for (int i=0; igetOscBuffer(i); + if (buf!=NULL && e->curSubSong->chanShow[i]) { + oscBufs.push_back(buf); + oscFFTs.push_back(&chanOscChan[i]); + oscChans.push_back(i); + } } - } - int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; + int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; - for (size_t i=0; ireadNeedle=buf->needle; - } + if (centerSettingReset) { + buf->readNeedle=buf->needle; + } - // check FFT status existence - if (fft->plan==NULL) { - logD("creating FFT plan for channel %d",ch); - fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); - fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); - fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); - } + // check FFT status existence + if (fft->plan==NULL) { + logD("creating FFT plan for channel %d",ch); + fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); + fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); + fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE); + } - int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); + int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f); - ImVec2 minArea=window->DC.CursorPos; - ImVec2 maxArea=ImVec2( - minArea.x+size.x, - minArea.y+size.y - ); - ImRect rect=ImRect(minArea,maxArea); - ImRect inRect=rect; - inRect.Min.x+=dpiScale; - inRect.Min.y+=2.0*dpiScale; - inRect.Max.x-=dpiScale; - inRect.Max.y-=2.0*dpiScale; + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImRect inRect=rect; + inRect.Min.x+=dpiScale; + inRect.Min.y+=2.0*dpiScale; + inRect.Max.x-=dpiScale; + inRect.Max.y-=2.0*dpiScale; - int precision=inRect.Max.x-inRect.Min.x; - if (precision<1) precision=1; - if (precision>512) precision=512; + int precision=inRect.Max.x-inRect.Min.x; + if (precision<1) precision=1; + if (precision>512) precision=512; - ImGui::ItemSize(size,style.FramePadding.y); - if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { - if (!e->isRunning()) { - for (unsigned short i=0; ineedle; - for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; - } - fftw_execute(fft->plan); - - // find origin frequency - int point=1; - double candAmp=0.0; - for (unsigned short i=1; i<512; i++) { - fftw_complex& f=fft->outBuf[i]; - // AMPLITUDE - double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); - if (amp>candAmp) { - point=i; - candAmp=amp; + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { + if (!e->isRunning()) { + for (unsigned short i=0; ineedle; + for (int i=0; iinBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0; + } + fftw_execute(fft->plan); + + // find origin frequency + int point=1; + double candAmp=0.0; + for (unsigned short i=1; i<512; i++) { + fftw_complex& f=fft->outBuf[i]; + // AMPLITUDE + double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); + if (amp>candAmp) { + point=i; + candAmp=amp; + } + } + + // PHASE + fftw_complex& candPoint=fft->outBuf[point]; + double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + + if (chanOscWaveCorr) { + needlePos-=phase; + } + chanOscPitch[ch]=(float)point/32.0f; + + /* + String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); + dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); + */ + + needlePos-=displaySize; + for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; + if (minLevel>y) minLevel=y; + if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; + y-=dcOff; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + y*=chanOscAmplify; + waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); } } + ImU32 color=ImGui::GetColorU32(chanOscColor); + if (chanOscUseGrad) { + float xVal=computeGradPos(chanOscColorX,ch); + float yVal=computeGradPos(chanOscColorY,ch); - // PHASE - fftw_complex& candPoint=fft->outBuf[point]; - double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2))); + xVal=CLAMP(xVal,0.0f,1.0f); + yVal=CLAMP(yVal,0.0f,1.0f); - if (chanOscWaveCorr) { - needlePos-=phase; + color=chanOscGrad.get(xVal,1.0f-yVal); } - chanOscPitch[ch]=(float)point/32.0f; - - /* - String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]); - dl->AddText(inRect.Min,0xffffffff,cPhase.c_str()); - */ + ImGui::PushClipRect(inRect.Min,inRect.Max,false); - needlePos-=displaySize; - for (unsigned short i=0; idata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; - if (minLevel>y) minLevel=y; - if (maxLeveldata[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f; - y-=dcOff; - if (y<-0.5f) y=-0.5f; - if (y>0.5f) y=0.5f; - waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); + dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); + + if (!chanOscTextFormat.empty()) { + String text; + bool inFormat=false; + + for (char i: chanOscTextFormat) { + if (inFormat) { + switch (i) { + case 'c': + text+=e->getChannelName(ch); + break; + case 'C': + text+=e->getChannelShortName(ch); + break; + case 'd': + text+=fmt::sprintf("%d",ch); + break; + case 'D': + text+=fmt::sprintf("%d",ch+1); + break; + case 'i': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + DivInstrument* ins=e->getIns(chanState->lastIns); + text+=ins->name; + break; + } + case 'I': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + text+=fmt::sprintf("%d",chanState->lastIns); + break; + } + case 'x': { + DivChannelState* chanState=e->getChanState(ch); + if (chanState==NULL) break; + if (chanState->lastIns<0) { + text+="??"; + } else { + text+=fmt::sprintf("%.2X",chanState->lastIns); + } + break; + } + case 's': { + text+=e->getSystemName(e->sysOfChan[ch]); + break; + } + case 'S': { + text+=fmt::sprintf("%d",e->dispatchOfChan[ch]); + break; + } + case 'v': + break; + case 'V': + break; + case 'b': + break; + case '%': + text+='%'; + break; + default: + text+='%'; + text+=i; + break; + } + inFormat=false; + } else { + if (i=='%') { + inFormat=true; + } else { + text+=i; + } + } + } + + dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str()); } + + ImGui::PopClipRect(); } - ImU32 color=ImGui::GetColorU32(chanOscColor); - if (chanOscUseGrad) { - float xVal=computeGradPos(chanOscColorX,ch); - float yVal=computeGradPos(chanOscColorY,ch); - - xVal=CLAMP(xVal,0.0f,1.0f); - yVal=CLAMP(yVal,0.0f,1.0f); - - color=chanOscGrad.get(xVal,1.0f-yVal); - } - ImGui::PushClipRect(inRect.Min,inRect.Max,false); - dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); - ImGui::PopClipRect(); } } - } - ImGui::EndTable(); + ImGui::EndTable(); - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - chanOscOptions=!chanOscOptions; + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + chanOscOptions=!chanOscOptions; + } } + ImGui::PopStyleVar(); } - ImGui::PopStyleVar(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC; ImGui::End(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8c14f977..a51f9ab8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5994,13 +5994,22 @@ bool FurnaceGUI::init() { chanOscCols=e->getConfInt("chanOscCols",3); chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER); chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER); + chanOscTextX=e->getConfFloat("chanOscTextX",0.0f); + chanOscTextY=e->getConfFloat("chanOscTextY",0.0f); + chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f); chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f); chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true); chanOscOptions=e->getConfBool("chanOscOptions",false); + chanOscNormalize=e->getConfBool("chanOscNormalize",false); + chanOscTextFormat=e->getConfString("chanOscTextFormat","%c"); chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f); chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f); chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f); chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f); + chanOscTextColor.x=e->getConfFloat("chanOscTextColorR",1.0f); + chanOscTextColor.y=e->getConfFloat("chanOscTextColorG",1.0f); + chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f); + chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f); chanOscUseGrad=e->getConfBool("chanOscUseGrad",false); chanOscGrad.fromString(e->getConfString("chanOscGrad","")); chanOscGrad.render(); @@ -6487,13 +6496,22 @@ void FurnaceGUI::commitState() { e->setConf("chanOscCols",chanOscCols); e->setConf("chanOscColorX",chanOscColorX); e->setConf("chanOscColorY",chanOscColorY); + e->setConf("chanOscTextX",chanOscTextX); + e->setConf("chanOscTextY",chanOscTextY); + e->setConf("chanOscAmplify",chanOscAmplify); e->setConf("chanOscWindowSize",chanOscWindowSize); e->setConf("chanOscWaveCorr",chanOscWaveCorr); e->setConf("chanOscOptions",chanOscOptions); + e->setConf("chanOscNormalize",chanOscNormalize); + e->setConf("chanOscTextFormat",chanOscTextFormat); e->setConf("chanOscColorR",chanOscColor.x); e->setConf("chanOscColorG",chanOscColor.y); e->setConf("chanOscColorB",chanOscColor.z); e->setConf("chanOscColorA",chanOscColor.w); + e->setConf("chanOscTextColorR",chanOscTextColor.x); + e->setConf("chanOscTextColorG",chanOscTextColor.y); + e->setConf("chanOscTextColorB",chanOscTextColor.z); + e->setConf("chanOscTextColorA",chanOscTextColor.w); e->setConf("chanOscUseGrad",chanOscUseGrad); e->setConf("chanOscGrad",chanOscGrad.toString()); @@ -6915,11 +6933,17 @@ FurnaceGUI::FurnaceGUI(): chanOscColorX(GUI_OSCREF_CENTER), chanOscColorY(GUI_OSCREF_CENTER), chanOscWindowSize(20.0f), + chanOscTextX(0.0f), + chanOscTextY(0.0f), + chanOscAmplify(0.95f), chanOscWaveCorr(true), chanOscOptions(false), updateChanOscGradTex(true), chanOscUseGrad(false), + chanOscNormalize(false), + chanOscTextFormat("%c"), chanOscColor(1.0f,1.0f,1.0f,1.0f), + chanOscTextColor(1.0f,1.0f,1.0f,0.75f), chanOscGrad(64,64), chanOscGradTex(NULL), followLog(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index 3d91e405..87c2a7fa 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1947,9 +1947,10 @@ class FurnaceGUI { // per-channel oscilloscope int chanOscCols, chanOscColorX, chanOscColorY; - float chanOscWindowSize; - bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad; - ImVec4 chanOscColor; + float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify; + bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize; + String chanOscTextFormat; + ImVec4 chanOscColor, chanOscTextColor; Gradient2D chanOscGrad; void* chanOscGradTex; float chanOscLP0[DIV_MAX_CHANS]; From 0e029def516331936f3d82db541d2379e1547ee8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 13:40:29 -0500 Subject: [PATCH 44/46] YM2612: fix chan osc again --- src/engine/platform/genesis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index f33ce4a7..a4b0f7b1 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -252,7 +252,7 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { oscBuf[5]->data[oscBuf[5]->needle++]=chan[5].dacOutput<<6; oscBuf[6]->data[oscBuf[6]->needle++]=chan[6].dacOutput<<6; } else { - oscBuf[i]->data[oscBuf[i]->needle++]=fm_ymfm->debug_dac_data()<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(fm_ymfm->debug_dac_data()^0x100)<<6; oscBuf[6]->data[oscBuf[6]->needle++]=0; } } else { From e7b69b27e64bebc0dce0ebb1ac02c08253e1827d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 13:55:55 -0500 Subject: [PATCH 45/46] YM2151: fix Nuked chan osc --- src/engine/platform/arcade.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 8de0fa3b..0e38bf96 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -76,7 +76,8 @@ void DivPlatformArcade::acquire_nuked(short** buf, size_t len) { } for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i]>>1; + int chOut=(int16_t)fm.ch_out[i]; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767); } if (o[0]<-32768) o[0]=-32768; @@ -111,7 +112,8 @@ void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; + int chOut=fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1); + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut,-32768,32767); } os[0]=out_ymfm.data[0]; From 9cb1fbcc45c4b368dab55d262553efd59861d60a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 18 Jun 2023 20:00:20 -0500 Subject: [PATCH 46/46] chan osc volume balancing, part 1 --- src/engine/platform/ay.cpp | 12 ++++---- src/engine/platform/ay8930.cpp | 6 ++-- src/engine/platform/bubsyswsg.cpp | 2 +- src/engine/platform/c64.cpp | 12 ++++---- src/engine/platform/dummy.cpp | 2 +- src/engine/platform/fds.cpp | 4 +-- src/engine/platform/ga20.cpp | 9 ++++-- src/engine/platform/mmc5.cpp | 6 ++-- src/engine/platform/n163.cpp | 2 +- src/engine/platform/nes.cpp | 20 ++++++------- src/engine/platform/opl.cpp | 32 ++++++++++----------- src/engine/platform/opll.cpp | 4 +-- src/engine/platform/pv1000.cpp | 2 +- src/engine/platform/scc.cpp | 2 +- src/engine/platform/sound/lynx/Mikey.cpp | 2 +- src/engine/platform/sound/pokey/AltASAP.cpp | 2 +- src/engine/platform/swan.cpp | 2 +- src/engine/platform/t6w28.cpp | 2 +- src/engine/platform/tia.cpp | 4 +-- src/engine/platform/tx81z.cpp | 2 +- src/engine/platform/vb.cpp | 2 +- src/engine/platform/vic20.cpp | 2 +- src/engine/platform/vrc6.cpp | 4 +-- 23 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index d31f0683..c32a4ee2 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -187,9 +187,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) { buf[0][i]=ayBuf[0][0]; buf[1][i]=buf[0][i]; - oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]<<2; - oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<2; - oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<2; + oscBuf[0]->data[oscBuf[0]->needle++]=CLAMP(sunsoftVolTable[31-(ay->lastIndx&31)]<<3,-32768,32767); + oscBuf[1]->data[oscBuf[1]->needle++]=CLAMP(sunsoftVolTable[31-((ay->lastIndx>>5)&31)]<<3,-32768,32767); + oscBuf[2]->data[oscBuf[2]->needle++]=CLAMP(sunsoftVolTable[31-((ay->lastIndx>>10)&31)]<<3,-32768,32767); } } else { for (size_t i=0; idata[oscBuf[0]->needle++]=ayBuf[0][0]<<1; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } } diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 51560975..8561548d 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -186,9 +186,9 @@ void DivPlatformAY8930::acquire(short** buf, size_t len) { buf[1][i]=buf[0][i]; } - oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<1; - oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<1; - oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<1; + oscBuf[0]->data[oscBuf[0]->needle++]=ayBuf[0][0]<<2; + oscBuf[1]->data[oscBuf[1]->needle++]=ayBuf[1][0]<<2; + oscBuf[2]->data[oscBuf[2]->needle++]=ayBuf[2][0]<<2; } } diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index bfb1e365..94202de1 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -55,7 +55,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) { chanOut=chan[i].waveROM[k005289.addr(i)]*(regPool[2+i]&0xf); out+=chanOut; if (writeOscBuf==0) { - oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=chanOut<<7; } } } diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 43cfbdf0..977e3951 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -80,18 +80,18 @@ void DivPlatformC64::acquire(short** buf, size_t len) { sid_fp.clock(4,&buf[0][i]); if (++writeOscBuf>=4) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>6; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>6; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>6; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5; } } else { sid.clock(); buf[0][i]=sid.output(); if (++writeOscBuf>=16) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>6; - oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>6; - oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>6; + oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5; + oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5; + oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5; } } } diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp index 04763898..18fa5ca9 100644 --- a/src/engine/platform/dummy.cpp +++ b/src/engine/platform/dummy.cpp @@ -32,7 +32,7 @@ void DivPlatformDummy::acquire(short** buf, size_t len) { if (chan[j].active) { if (!isMuted[j]) { chanOut=(((signed short)chan[j].pos)*chan[j].amp*chan[j].vol)>>12; - oscBuf[j]->data[oscBuf[j]->needle++]=chanOut>>1; + oscBuf[j]->data[oscBuf[j]->needle++]=chanOut<<1; out+=chanOut; } else { oscBuf[j]->data[oscBuf[j]->needle++]=0; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 2674e954..ea903ced 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -64,7 +64,7 @@ void DivPlatformFDS::acquire_puNES(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample; + oscBuf->data[oscBuf->needle++]=sample*3; } } } @@ -80,7 +80,7 @@ void DivPlatformFDS::acquire_NSFPlay(short* buf, size_t len) { buf[i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf->data[oscBuf->needle++]=sample; + oscBuf->data[oscBuf->needle++]=sample*3; } } } diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 60764585..7794d61a 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -71,8 +71,13 @@ void DivPlatformGA20::acquire(short** buf, size_t len) { delay=w.delay; } } - short *buffer[4] = {&ga20Buf[0][h],&ga20Buf[1][h],&ga20Buf[2][h],&ga20Buf[3][h]}; - ga20.sound_stream_update(buffer, 1); + short *buffer[4]={ + &ga20Buf[0][h], + &ga20Buf[1][h], + &ga20Buf[2][h], + &ga20Buf[3][h] + }; + ga20.sound_stream_update(buffer,1); buf[0][h]=(signed int)(ga20Buf[0][h]+ga20Buf[1][h]+ga20Buf[2][h]+ga20Buf[3][h])>>2; for (int i=0; i<4; i++) { oscBuf[i]->data[oscBuf[i]->needle++]=ga20Buf[i][h]>>1; diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 0edc83b2..e9ac6be9 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -85,9 +85,9 @@ void DivPlatformMMC5::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output*10)<<6); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output*10)<<6); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output*2)<<5); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:((mmc5->S3.output)<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:((mmc5->S4.output)<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:((mmc5->pcm.output)<<7); } } } diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index eee73b99..ead44cc1 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -118,7 +118,7 @@ void DivPlatformN163::acquire(short** buf, size_t len) { buf[0][i]=out; if (n163.voice_cycle()==0x78) for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=n163.voice_out(i)<<7; } // command queue diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 07f9c5ae..99131ac3 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -115,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<10); - oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<10); - oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<10); - oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<10); - oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<7); + oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11); + oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11); + oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11); + oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11); + oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8); } } } @@ -142,11 +142,11 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) { buf[0][i]=sample; if (++writeOscBuf>=32) { writeOscBuf=0; - oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<10; - oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<10; - oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<10; - oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<10; - oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<7; + oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11; + oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11; + oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11; + oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<12; + oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8; } } } diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b39fb8f6..d340aa31 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -220,45 +220,45 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { if (fm.rhy&0x20) { for (int i=0; idata[oscBuf[i]->needle]=0; if (fm.channel[i].out[0]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + chOut+=*fm.channel[ch].out[0]; } if (fm.channel[i].out[1]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + chOut+=*fm.channel[ch].out[1]; } if (fm.channel[i].out[2]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[2]; + chOut+=*fm.channel[ch].out[2]; } if (fm.channel[i].out[3]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; + chOut+=*fm.channel[ch].out[3]; } - oscBuf[i]->needle++; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<(i==melodicChans?1:2),-32768,32767); } // special - oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*3; - oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*3; - oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*3; - oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*3; + oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*4; + oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*4; + oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*4; + oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*4; } else { for (int i=0; idata[oscBuf[i]->needle]=0; if (fm.channel[i].out[0]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0]; + chOut+=*fm.channel[ch].out[0]; } if (fm.channel[i].out[1]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1]; + chOut+=*fm.channel[ch].out[1]; } if (fm.channel[i].out[2]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[2]; + chOut+=*fm.channel[ch].out[2]; } if (fm.channel[i].out[3]!=NULL) { - oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[3]; + chOut+=*fm.channel[ch].out[3]; } - oscBuf[i]->needle++; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<2,-32768,32767); } } diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 596735a4..38b892e8 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -68,7 +68,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { unsigned char nextOut=cycleMapOPLL[fm.cycles]; if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { os+=(o[0]+o[1]); - if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<5; + if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6; } else { if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0; } @@ -76,7 +76,7 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) { unsigned char ch=visMapOPLL[i]; if ((i>=6 && properDrums) || !isMuted[ch]) { - oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<5; + oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6; } else { oscBuf[ch]->data[oscBuf[ch]->needle++]=0; } diff --git a/src/engine/platform/pv1000.cpp b/src/engine/platform/pv1000.cpp index 4d32fc5a..bf03df40 100644 --- a/src/engine/platform/pv1000.cpp +++ b/src/engine/platform/pv1000.cpp @@ -42,7 +42,7 @@ void DivPlatformPV1000::acquire(short** buf, size_t len) { short samp=d65010g031_sound_tick(&d65010g031,1); buf[0][h]=samp; for (int i=0; i<3; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]); + oscBuf[i]->data[oscBuf[i]->needle++]=d65010g031.out[i]<<1; } } } diff --git a/src/engine/platform/scc.cpp b/src/engine/platform/scc.cpp index e7ad162f..cd97ff30 100644 --- a/src/engine/platform/scc.cpp +++ b/src/engine/platform/scc.cpp @@ -87,7 +87,7 @@ void DivPlatformSCC::acquire(short** buf, size_t len) { buf[0][h]=out; for (int i=0; i<5; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=scc->voice_out(i)<<7; } } } diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index baf376f5..791336c1 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -509,7 +509,7 @@ public: } if (oscb!=NULL) { - oscb[i]->data[oscb[i]->needle++]=oscbWrite>>1; + oscb[i]->data[oscb[i]->needle++]=oscbWrite; } } diff --git a/src/engine/platform/sound/pokey/AltASAP.cpp b/src/engine/platform/sound/pokey/AltASAP.cpp index 9ed2f207..e6675027 100644 --- a/src/engine/platform/sound/pokey/AltASAP.cpp +++ b/src/engine/platform/sound/pokey/AltASAP.cpp @@ -39,7 +39,7 @@ static constexpr int MuteInit = 2; static constexpr int MuteSerialInput = 8; //just some magick value to match the audio level of mzpokeysnd static constexpr int16_t MAGICK_VOLUME_BOOSTER = 160; -static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 2; +static constexpr int16_t MAGICK_OSC_VOLUME_BOOSTER = 6; struct PokeyBase { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 46edfd7f..209e2fc2 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -87,7 +87,7 @@ void DivPlatformSwan::acquire(short** buf, size_t len) { buf[0][h]=samp[0]; buf[1][h]=samp[1]; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<5; + oscBuf[i]->data[oscBuf[i]->needle++]=(ws->sample_cache[i][0]+ws->sample_cache[i][1])<<6; } } } diff --git a/src/engine/platform/t6w28.cpp b/src/engine/platform/t6w28.cpp index 0d7b9223..5d21e1ad 100644 --- a/src/engine/platform/t6w28.cpp +++ b/src/engine/platform/t6w28.cpp @@ -54,7 +54,7 @@ void DivPlatformT6W28::acquire(short** buf, size_t len) { tempL=0; tempR=0; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(out[i][1].curValue+out[i][2].curValue)<<6; + oscBuf[i]->data[oscBuf[i]->needle++]=(out[i][1].curValue+out[i][2].curValue)<<7; tempL+=out[i][1].curValue<<7; tempR+=out[i][2].curValue<<7; } diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index cdd11621..1836bee1 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -51,8 +51,8 @@ void DivPlatformTIA::acquire(short** buf, size_t len) { } if (++chanOscCounter>=114) { chanOscCounter=0; - oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]>>1; - oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]>>1; + oscBuf[0]->data[oscBuf[0]->needle++]=tia.myChannelOut[0]; + oscBuf[1]->data[oscBuf[1]->needle++]=tia.myChannelOut[1]; } } } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index c4d921fd..86128340 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -78,7 +78,7 @@ void DivPlatformTX81Z::acquire(short** buf, size_t len) { fm_ymfm->generate(&out_ymfm); for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1))>>1; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1),-32768,32767); } os[0]=out_ymfm.data[0]; diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp index 6732bded..3701b5aa 100644 --- a/src/engine/platform/vb.cpp +++ b/src/engine/platform/vb.cpp @@ -107,7 +107,7 @@ void DivPlatformVB::acquire(short** buf, size_t len) { tempL=0; tempR=0; for (int i=0; i<6; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*4; + oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*8; tempL+=vb->last_output[i][0]; tempR+=vb->last_output[i][1]; } diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index cb78e9b4..bd25b528 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -69,7 +69,7 @@ void DivPlatformVIC20::acquire(short** buf, size_t len) { vic_sound_machine_calculate_samples(vic,&samp,1,1,0,SAMP_DIVIDER); buf[0][h]=samp; for (int i=0; i<4; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<10):0; + oscBuf[i]->data[oscBuf[i]->needle++]=vic->ch[i].out?(vic->volume<<11):0; } } } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 7834ecbe..b52bc106 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -87,9 +87,9 @@ void DivPlatformVRC6::acquire(short** buf, size_t len) { if (++writeOscBuf>=32) { writeOscBuf=0; for (int i=0; i<2; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<9; + oscBuf[i]->data[oscBuf[i]->needle++]=vrc6.pulse_out(i)<<11; } - oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<9; + oscBuf[2]->data[oscBuf[2]->needle++]=vrc6.sawtooth_out()<<10; } // Command part