diff --git a/code/nel/tools/3d/mesh_editor/main_window.cpp b/code/nel/tools/3d/mesh_editor/main_window.cpp
index e9ec2d430..f7e1502ec 100644
--- a/code/nel/tools/3d/mesh_editor/main_window.cpp
+++ b/code/nel/tools/3d/mesh_editor/main_window.cpp
@@ -43,6 +43,7 @@
#include "../shared_widgets/error_list.h"
#include "graphics_viewport.h"
#include "graphics_config.h"
+#include "texture_browser.h"
using namespace std;
using namespace NLMISC;
@@ -103,6 +104,12 @@ CMainWindow::CMainWindow(QWidget *parent, Qt::WindowFlags flags)
}
}
}
+
+ QDockWidget *dock = new QDockWidget(this);
+ dock->setFloating(true);
+ CTextureBrowser *browser = new CTextureBrowser(dock);
+ browser->resize(800, 800);
+ dock->setWidget(browser);
}
CMainWindow::~CMainWindow()
diff --git a/code/nel/tools/3d/mesh_editor/texture_browser.cpp b/code/nel/tools/3d/mesh_editor/texture_browser.cpp
new file mode 100644
index 000000000..6f4418cdb
--- /dev/null
+++ b/code/nel/tools/3d/mesh_editor/texture_browser.cpp
@@ -0,0 +1,138 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2016 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include
+#include "texture_browser.h"
+
+// STL includes
+#include
+
+// Qt includes
+#include
+#include
+#include
+#include
+#include
+#include
+
+// NeL includes
+// #include
+#include
+#include
+#include
+#include
+
+// Project includes
+#include "../shared_widgets/event_loop.h"
+
+// See also: studio/.../gui_editor/texture_chooser.cpp
+
+// UTILITY ->
+QPixmap qPixmapFromCBitmap(NLMISC::CBitmap &bitmap, bool alpha)
+{
+ QImage img(bitmap.getWidth(), bitmap.getHeight(), alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32);
+ NLMISC::CObjectVector &pixels = bitmap.getPixels();
+
+ uint height = bitmap.getHeight();
+ uint stride = bitmap.getWidth() * sizeof(NLMISC::CRGBA);
+ for (uint y = 0; y < height; ++y)
+ {
+ // memcpy(img.scanLine(y), &pixels[y * stride], stride);
+ // Convert from ABGR to ARGB
+ uint8 *dst = img.scanLine(y);
+ uint8 *src = &pixels[y * stride];
+ for (uint x = 0; x < stride; x += 4)
+ {
+ dst[x] = src[x + 2];
+ dst[x + 1] = src[x + 1];
+ dst[x + 2] = src[x];
+ dst[x + 3] = src[x + 3];
+ }
+ }
+
+ return QPixmap::fromImage(img);
+}
+// <- UTILITY
+
+CTextureBrowser::CTextureBrowser(QWidget *parent) : QListWidget(parent)
+{
+ qRegisterMetaType("CStdFunctionVoid");
+ m_Thread = new CEventLoop();
+ m_Thread->run();
+
+ setViewMode(QListWidget::IconMode);
+ setIconSize(QSize(200, 200));
+ setResizeMode(QListWidget::Adjust);
+
+ setDirectory("W:/database/stuff/fyros/agents/_textures/actors/");
+}
+
+CTextureBrowser::~CTextureBrowser()
+{
+ m_Thread->clear();
+ delete m_Thread;
+}
+
+void CTextureBrowser::setDirectory(const QString &dir)
+{
+ // Remove any pending stuff
+ m_Thread->clear();
+
+ // Sync up, clear, and start processing
+ m_Thread->immediate([this, dir]() -> void {
+ invokeStdFunction([this, dir]() -> void {
+ // m_listeWidget->addItem(new QListWidgetItem(QIcon("../earth.jpg"), "Earth"));
+ clear();
+ std::vector files;
+ NLMISC::CPath::getPathContent(dir.toUtf8().data(), false, false, true, files);
+ for (size_t i = 0; i < files.size(); ++i)
+ {
+ std::string &file = files[i];
+ m_Thread->immediate([this, file]() -> void {
+ std::string ext = NLMISC::toLower(NLMISC::CFile::getExtension(file));
+ if (ext == "dds" || ext == "tga" || ext == "png" || ext == "jpg" || ext == "jpeg")
+ {
+ NLMISC::CIFile f;
+ if (f.open(file))
+ {
+ NLMISC::CBitmap bitmap;
+ bitmap.load(f);
+ bitmap.resample(128, 128);
+ QPixmap pixmap = qPixmapFromCBitmap(bitmap, false);
+ QString fileName = QFileInfo(QString::fromUtf8(file.c_str())).fileName();
+ invokeStdFunction([this, pixmap, fileName]() -> void {
+ addItem(new QListWidgetItem(QIcon(pixmap), fileName));
+ });
+ }
+ }
+ });
+ }
+ });
+ });
+}
+
+void CTextureBrowser::invokeStdFunction(CStdFunctionVoid f)
+{
+ QMetaObject::invokeMethod(this, "callStdFunction", Qt::QueuedConnection, Q_ARG(CStdFunctionVoid, f));
+}
+
+void CTextureBrowser::callStdFunction(CStdFunctionVoid f)
+{
+ f();
+}
+
+/* end of file */
diff --git a/code/nel/tools/3d/mesh_editor/texture_browser.h b/code/nel/tools/3d/mesh_editor/texture_browser.h
new file mode 100644
index 000000000..9df6333e6
--- /dev/null
+++ b/code/nel/tools/3d/mesh_editor/texture_browser.h
@@ -0,0 +1,70 @@
+// NeL - MMORPG Framework
+// Copyright (C) 2016 Winch Gate Property Limited
+// Author: Jan Boon
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef NL_TEXTURE_BROWSER_H
+#define NL_TEXTURE_BROWSER_H
+#include
+
+// STL includes
+#include
+
+// Qt includes
+#include
+
+// NeL includes
+// ...
+
+class CEventLoop;
+typedef std::function CStdFunctionVoid;
+
+/**
+ * CTextureBrowser
+ * \brief CTextureBrowser
+ * \date 2016-02-18 14:06GMT
+ * \author Jan Boon
+ */
+class CTextureBrowser : public QListWidget
+{
+ Q_OBJECT
+
+public:
+ CTextureBrowser(QWidget *parent = NULL);
+ virtual ~CTextureBrowser();
+
+
+private:
+ void setDirectory(const QString &dir);
+
+ // STD INVOKE ->
+public:
+ void invokeStdFunction(CStdFunctionVoid f);
+private slots:
+ void callStdFunction(CStdFunctionVoid f);
+ // <- STD INVOKE
+
+private:
+ CEventLoop *m_Thread;
+
+private:
+ CTextureBrowser(const CTextureBrowser &);
+ CTextureBrowser &operator=(const CTextureBrowser &);
+
+}; /* class CTextureBrowser */
+
+#endif /* #ifndef NL_TEXTURE_BROWSER_H */
+
+/* end of file */
diff --git a/code/nel/tools/3d/shared_widgets/event_loop.h b/code/nel/tools/3d/shared_widgets/event_loop.h
new file mode 100644
index 000000000..1f6661c74
--- /dev/null
+++ b/code/nel/tools/3d/shared_widgets/event_loop.h
@@ -0,0 +1,281 @@
+/*
+
+Copyright (C) 2016 by authors
+Author: Jan Boon
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+* Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+// Source: https://github.com/kaetemi/threadutil/blob/master/threadutil/eventloop.h
+
+#ifndef NL_EVENT_LOOP_H
+#define NL_EVENT_LOOP_H
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+class CEventLoop
+{
+public:
+ CEventLoop() : m_Running(false), m_Handle(0)
+ {
+
+ }
+
+ ~CEventLoop()
+ {
+ stop();
+ clear();
+ }
+
+ void run()
+ {
+ stop();
+ m_Running = true;
+ m_Thread = std::move(std::thread(&CEventLoop::loop, this));
+ }
+
+ void runSync()
+ {
+ stop();
+ m_Running = true;
+ loop();
+ }
+
+ void stop() // thread-safe
+ {
+ m_Running = false;
+ poke();
+ if (m_Thread.joinable())
+ m_Thread.join();
+ }
+
+ void clear() // thread-safe
+ {
+ std::unique_lock lock(m_QueueLock);
+ std::unique_lock tlock(m_QueueTimeoutLock);
+ m_Immediate = std::move(std::queue>());
+ m_Timeout = std::move(std::priority_queue());
+ }
+
+ void clear(int handle) // thread-safe, relatively slow, not recommended nor reliable to do this for timeouts, only reliable for intervals
+ {
+ std::unique_lock lock(m_QueueTimeoutLock);
+ std::priority_queue timeout;
+ while (m_Timeout.size())
+ {
+ if (m_Timeout.top().handle != handle)
+ timeout.push(m_Timeout.top());
+ m_Timeout.pop();
+ }
+ m_Timeout = std::move(timeout);
+ }
+
+ void join() // thread-safe
+ {
+ std::mutex syncLock;
+ std::condition_variable syncCond;
+ std::unique_lock lock(syncLock);
+ immediate([this, &syncCond]() -> void {
+ syncCond.notify_one();
+ });
+ m_PokeCond.wait(lock);
+ }
+
+public:
+ void immediate(std::function f) // thread-safe
+ {
+ std::unique_lock lock(m_QueueLock);
+ m_Immediate.push(f);
+ poke();
+ }
+
+ template
+ int timeout(std::function f, const std::chrono::duration& delta) // thread-safe
+ {
+ timeout_func tf;
+ tf.f = f;
+ tf.time = std::chrono::steady_clock::now() + delta;
+ tf.interval = std::chrono::nanoseconds::zero();
+ tf.handle = ++m_Handle;
+ ; {
+ std::unique_lock lock(m_QueueTimeoutLock);
+ m_Timeout.push(tf);
+ poke();
+ }
+ return tf.handle;
+ }
+
+ template
+ int interval(std::function f, const std::chrono::duration& interval) // thread-safe
+ {
+ timeout_func tf;
+ tf.f = f;
+ tf.time = std::chrono::steady_clock::now() + interval;
+ tf.interval = interval;
+ tf.handle = ++m_Handle;
+ ; {
+ std::unique_lock lock(m_QueueTimeoutLock);
+ m_Timeout.push(tf);
+ poke();
+ }
+ return tf.handle;
+ }
+
+ int timed(std::function f, const std::chrono::steady_clock::time_point &point) // thread-safe
+ {
+ timeout_func tf;
+ tf.f = f;
+ tf.time = point;
+ tf.interval = std::chrono::steady_clock::duration::zero();
+ tf.handle = ++m_Handle;
+ ; {
+ std::unique_lock lock(m_QueueTimeoutLock);
+ m_Timeout.push(tf);
+ poke();
+ }
+ return tf.handle;
+ }
+
+public:
+ void thread(std::function f, std::function callback)
+ {
+ std::thread t([this, f, callback]() -> void {
+ f();
+ immediate(callback);
+ });
+ t.detach();
+ }
+
+private:
+ void loop()
+ {
+ while (m_Running)
+ {
+ m_Poked = false;
+
+ for (;;)
+ {
+ m_QueueLock.lock();
+ if (!m_Immediate.size())
+ {
+ m_QueueLock.unlock();
+ break;
+ }
+ std::function f = m_Immediate.front();
+ m_Immediate.pop();
+ m_QueueLock.unlock();
+ f();
+ }
+
+ bool poked = false;
+ for (;;)
+ {
+ m_QueueTimeoutLock.lock();
+ if (!m_Timeout.size())
+ {
+ m_QueueTimeoutLock.unlock();
+ break;
+ }
+ const timeout_func &tfr = m_Timeout.top();
+ if (tfr.time > std::chrono::steady_clock::now()) // wait
+ {
+ m_QueueTimeoutLock.unlock();
+ ; {
+ std::unique_lock lock(m_PokeLock);
+ if (!m_Poked)
+ m_PokeCond.wait_until(lock, tfr.time);
+ }
+ poked = true;
+ break;
+ }
+ timeout_func tf = tfr;
+ m_Timeout.pop();
+ m_QueueTimeoutLock.unlock();
+ tf.f(); // call
+ if (tf.interval > std::chrono::nanoseconds::zero()) // repeat
+ {
+ tf.time += tf.interval;
+ ; {
+ std::unique_lock lock(m_QueueTimeoutLock);
+ m_Timeout.push(tf);
+ poke();
+ }
+ }
+ }
+
+ if (!poked)
+ {
+ std::unique_lock lock(m_PokeLock);
+ if (!m_Poked)
+ m_PokeCond.wait(lock);
+ }
+ }
+ }
+
+ void poke() // private
+ {
+ std::unique_lock lock(m_PokeLock);
+ m_PokeCond.notify_one();
+ m_Poked = true;
+ }
+
+private:
+ struct timeout_func
+ {
+ std::function f;
+ std::chrono::steady_clock::time_point time;
+ std::chrono::steady_clock::duration interval;
+ int handle;
+
+ bool operator <(const timeout_func &o) const
+ {
+ return time > o.time;
+ }
+
+ };
+
+private:
+ volatile bool m_Running;
+ volatile bool m_Poked;
+ std::thread m_Thread;
+ std::mutex m_PokeLock;
+ std::condition_variable m_PokeCond;
+
+ std::mutex m_QueueLock;
+ std::queue> m_Immediate;
+ std::mutex m_QueueTimeoutLock;
+ std::priority_queue m_Timeout;
+ int m_Handle;
+
+};
+
+#endif /* NL_EVENT_LOOP_H */
+
+/* end of file */