diff --git a/dub.sdl b/dub.sdl index 24a6ad9..54acb99 100644 --- a/dub.sdl +++ b/dub.sdl @@ -30,6 +30,8 @@ stringImportPaths "res" configuration "barebones" { platforms "linux" targetType "executable" + + dependency "dportals" version="~>0.1.0" } diff --git a/source/session/io/package.d b/source/session/io/package.d new file mode 100644 index 0000000..6b0a832 --- /dev/null +++ b/source/session/io/package.d @@ -0,0 +1,86 @@ +/* + Copyright © 2020-2023, Inochi2D Project + Distributed under the 2-Clause BSD License, see LICENSE file. + + Authors: Luna Nielsen +*/ +module session.io; + +import tinyfiledialogs; +public import tinyfiledialogs : TFD_Filter; +import std.string; +import i18n; + +version (linux) { + import dportals.filechooser; + import dportals.promise; +} + +private { + version (linux) { + string uriFromPromise(Promise promise) { + if (promise.success) { + import std.array : replace; + + string uri = promise.value["uris"].data.array[0].str; + uri = uri.replace("%20", " "); + return uri[7 .. $]; + } + return null; + } + + FileFilter[] tfdToFileFilter(const(TFD_Filter)[] filters) { + FileFilter[] out_; + + foreach (filter; filters) { + auto of = FileFilter( + cast(string) filter.description.fromStringz, + [] + ); + + foreach (i, pattern; filter.patterns) { + of.items ~= FileFilterItem( + cast(uint) i, + cast(string) pattern.fromStringz + ); + } + + out_ ~= of; + } + + return out_; + } + } +} + +/** + Call a file dialog to open a file. +*/ +string insShowOpenDialog(const(TFD_Filter)[] filters, string title = "Open...", string parentWindow = "") { + version (linux) { + try { + FileOpenOptions op; + op.filters = tfdToFileFilter(filters); + auto promise = dpFileChooserOpenFile(parentWindow, title, op); + promise.await(); + return promise.uriFromPromise(); + } catch (Throwable ex) { + + // FALLBACK: If xdg-desktop-portal is not available then try tinyfiledialogs. + c_str filename = tinyfd_openFileDialog(title.toStringz, "", filters, false); + if (filename !is null) { + string file = cast(string) filename.fromStringz; + return file; + } + return null; + } + } else { + c_str filename = tinyfd_openFileDialog(title.toStringz, "", filters, false); + if (filename !is null) { + string file = cast(string) filename.fromStringz; + return file; + } + return null; + } +} + diff --git a/source/session/windows/main.d b/source/session/windows/main.d index 3055cb2..519fbd5 100644 --- a/source/session/windows/main.d +++ b/source/session/windows/main.d @@ -10,6 +10,7 @@ import session.scene; import session.log; import session.framesend; import session.plugins; +import session.io; import inui; import inui.widgets; import inui.toolwindow; @@ -22,6 +23,8 @@ import inui.utils.link; import std.format; import session.ver; +version(linux) import dportals; + private { struct InochiWindowSettings { int width; @@ -78,6 +81,23 @@ protected: if (uiImBeginMenu(__("File"))) { + if (uiImMenuItem(__("Open"))) { + const TFD_Filter[] filters = [ + { ["*.inp"], "Inochi2d Puppet (*.inp)" } + ]; + + string parentWindow = ""; + version(linux) { + static if (is(typeof(&getWindowHandle))) { + parentWindow = getWindowHandle(); + } + } + string file = insShowOpenDialog(filters, _("Open..."), parentWindow); + if (file) loadModels([file]); + } + + uiImSeperator(); + if (uiImMenuItem(__("Exit"))) { this.close(); } @@ -170,6 +190,8 @@ protected: } uiImEndMainMenuBar(); } + + version(linux) dpUpdate(); } override @@ -212,5 +234,7 @@ public: version (InBranding) { logo = new Texture(ShallowTexture(cast(ubyte[])import("tex/logo.png"))); } + + version(linux) dpInit(); } } \ No newline at end of file