Initial commit

This commit is contained in:
Skarn
2020-10-09 20:16:53 +03:00
commit 940e8dbb43
698 changed files with 302556 additions and 0 deletions

1
src/external/qt-color-widgets.repo vendored Normal file
View File

@@ -0,0 +1 @@
git@github.com:bloerwald/Qt-Color-Widgets.git

View File

@@ -0,0 +1,71 @@
#
# Copyright (C) 2013-2017 Mattia Basaglia
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
set(SOURCES
src/abstract_widget_list.cpp
src/alphaback.png
src/bound_color_selector.cpp
src/color_2d_slider.cpp
src/color_delegate.cpp
src/color_dialog.cpp
src/color_line_edit.cpp
src/color_list_widget.cpp
src/color_names.cpp
src/color_palette.cpp
src/color_palette_model.cpp
src/color_palette_widget.cpp
src/color_preview.cpp
src/color_selector.cpp
src/color_utils.cpp
src/color_wheel.cpp
src/color_widgets.qrc
src/gradient_slider.cpp
src/hue_slider.cpp
src/swatch.cpp
)
set(HEADERS
qt-color-widgets/abstract_widget_list.hpp
qt-color-widgets/bound_color_selector.hpp
qt-color-widgets/color_2d_slider.hpp
qt-color-widgets/color_delegate.hpp
qt-color-widgets/color_dialog.hpp
qt-color-widgets/color_line_edit.hpp
qt-color-widgets/color_list_widget.hpp
qt-color-widgets/color_palette.hpp
qt-color-widgets/color_palette_model.hpp
qt-color-widgets/color_palette_widget.hpp
qt-color-widgets/color_preview.hpp
qt-color-widgets/color_selector.hpp
qt-color-widgets/color_wheel.hpp
qt-color-widgets/gradient_slider.hpp
qt-color-widgets/hue_slider.hpp
qt-color-widgets/swatch.hpp
)
qt5_wrap_cpp(SOURCES ${HEADERS})
qt5_wrap_ui(SOURCES src/color_dialog.ui src/color_palette_widget.ui)
qt5_add_resources(SOURCES src/color_widgets.qrc)
# Library
add_library(ColorWidgets-qt5 ${SOURCES})
target_link_libraries(ColorWidgets-qt5 Qt5::Widgets)
target_include_directories (ColorWidgets-qt5 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories (ColorWidgets-qt5 PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_compiler_flag_if_supported (ColorWidgets_CXX_FLAGS "-Wno-error=deprecated-declarations")
target_compile_options (ColorWidgets-qt5 PRIVATE ${ColorWidgets_CXX_FLAGS})

165
src/external/qt-color-widgets/COPYING vendored Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -0,0 +1,108 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ABSTRACT_WIDGET_LIST_HPP
#define ABSTRACT_WIDGET_LIST_HPP
#include <QSignalMapper>
#include <QTableWidget>
class Q_DECL_EXPORT AbstractWidgetList : public QWidget
{
Q_OBJECT
public:
explicit AbstractWidgetList(QWidget *parent = 0);
~AbstractWidgetList();
/**
* \brief Get the number of items
*/
int count() const;
/**
* \brief Swap row a and row b
*/
virtual void swap(int a, int b) = 0;
/// Whether the given row index is valid
bool isValidRow(int i) const { return i >= 0 && i < count(); }
void setRowHeight(int row, int height);
public Q_SLOTS:
/**
* \brief Remove row i
*/
void remove(int i);
/**
* \brief append a default row
*/
virtual void append() = 0;
Q_SIGNALS:
void removed(int i);
protected:
/**
* \brief Create a new row with the given widget
*
* Must be caled by implementations of append()
*/
void appendWidget(QWidget* w);
/**
* \brief get the widget found at the given row
*/
QWidget* widget(int i);
/**
* \brief get the widget found at the given row
*/
template<class T>
T* widget_cast(int i) { return qobject_cast<T*>(widget(i)); }
/**
* \brief clear all rows without emitting signals
*
* May be useful when implementation want to set all values at once
*/
void clear();
private Q_SLOTS:
void remove_clicked(QWidget* w);
void up_clicked(QWidget* w);
void down_clicked(QWidget* w);
private:
class Private;
Private * const p;
QWidget* create_button(QWidget* data, QSignalMapper*mapper,
QIcon icon, QString text,
QString tooltip = QString()) const;
};
#endif // ABSTRACT_WIDGET_LIST_HPP

View File

@@ -0,0 +1,44 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef BOUND_COLOR_SELECTOR_HPP
#define BOUND_COLOR_SELECTOR_HPP
#include "qt-color-widgets/color_selector.hpp"
namespace color_widgets {
/**
* \brief A color selector bound to a color reference
* \todo Maybe this can be removed
*/
class Q_DECL_EXPORT BoundColorSelector : public ColorSelector
{
Q_OBJECT
private:
QColor* ref;
public:
explicit BoundColorSelector(QColor* reference, QWidget *parent = 0);
private Q_SLOTS:
void update_reference(QColor);
};
} // namespace color_widgets
#endif // BOUND_COLOR_SELECTOR_HPP

View File

@@ -0,0 +1,127 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_2D_SLIDER_HPP
#define COLOR_WIDGETS_COLOR_2D_SLIDER_HPP
#include <QWidget>
namespace color_widgets {
/**
* \brief A 2D slider that edits 2 color components
*/
class Q_DECL_EXPORT Color2DSlider : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged DESIGNABLE true STORED false )
Q_PROPERTY(qreal hue READ hue WRITE setHue DESIGNABLE false )
Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation DESIGNABLE false )
Q_PROPERTY(qreal value READ value WRITE setValue DESIGNABLE false )
/**
* \brief Which color component is used on the x axis
*/
Q_PROPERTY(Component componentX READ componentX WRITE setComponentX NOTIFY componentXChanged)
/**
* \brief Which color component is used on the y axis
*/
Q_PROPERTY(Component componentY READ componentY WRITE setComponentY NOTIFY componentYChanged)
public:
enum Component {
Hue, Saturation, Value
};
Q_ENUMS(Component)
explicit Color2DSlider(QWidget *parent = nullptr);
~Color2DSlider();
/// Get current color
QColor color() const;
QSize sizeHint() const Q_DECL_OVERRIDE;
/// Get current hue in the range [0-1]
qreal hue() const;
/// Get current saturation in the range [0-1]
qreal saturation() const;
/// Get current value in the range [0-1]
qreal value() const;
Component componentX() const;
Component componentY() const;
public Q_SLOTS:
/// Set current color
void setColor(const QColor& c);
/**
* @param h Hue [0-1]
*/
void setHue(qreal h);
/**
* @param s Saturation [0-1]
*/
void setSaturation(qreal s);
/**
* @param v Value [0-1]
*/
void setValue(qreal v);
void setComponentX(Component componentX);
void setComponentY(Component componentY);
Q_SIGNALS:
/**
* Emitted when the user selects a color or setColor is called
*/
void colorChanged(QColor);
/**
* Emitted when the user selects a color
*/
void colorSelected(QColor);
void componentXChanged(Component componentX);
void componentYChanged(Component componentY);
protected:
void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent* event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent* event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent* event) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE;
private:
class Private;
Private * const p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_2D_SLIDER_HPP

View File

@@ -0,0 +1,52 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_DELEGATE_HPP
#define COLOR_DELEGATE_HPP
#include <QAbstractItemDelegate>
namespace color_widgets {
/**
Delegate to use a ColorSelector in a color list
*/
class Q_DECL_EXPORT ColorDelegate : public QAbstractItemDelegate
{
Q_OBJECT
public:
explicit ColorDelegate(QWidget *parent = 0);
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
bool editorEvent(QEvent* event,
QAbstractItemModel* model,
const QStyleOptionViewItem & option,
const QModelIndex & index) override;
virtual QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
};
} // namespace color_widgets
#endif // COLOR_DELEGATE_HPP

View File

@@ -0,0 +1,151 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_DIALOG_HPP
#define COLOR_DIALOG_HPP
#include "qt-color-widgets/color_preview.hpp"
#include "qt-color-widgets/color_wheel.hpp"
#include <QDialog>
class QAbstractButton;
namespace color_widgets {
class Q_DECL_EXPORT ColorDialog : public QDialog
{
Q_OBJECT
Q_ENUMS(ButtonMode)
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged DESIGNABLE true)
Q_PROPERTY(ColorWheel::DisplayFlags wheelFlags READ wheelFlags WRITE setWheelFlags NOTIFY wheelFlagsChanged)
/**
* \brief whether the color alpha channel can be edited.
*
* If alpha is disabled, the selected color's alpha will always be 255.
*/
Q_PROPERTY(bool alphaEnabled READ alphaEnabled WRITE setAlphaEnabled NOTIFY alphaEnabledChanged)
public:
enum ButtonMode {
OkCancel,
OkApplyCancel,
Close
};
explicit ColorDialog(QWidget *parent = 0, Qt::WindowFlags f = 0);
/**
* Get currently selected color
*/
QColor color() const;
/**
* Set the display mode for the color preview
*/
void setPreviewDisplayMode(ColorPreview::DisplayMode mode);
/**
* Get the color preview diplay mode
*/
ColorPreview::DisplayMode previewDisplayMode() const;
bool alphaEnabled() const;
/**
* Select which dialog buttons to show
*
* There are three predefined modes:
* OkCancel - this is useful when the dialog is modal and we just want to return a color
* OkCancelApply - this is for non-modal dialogs
* Close - for non-modal dialogs with direct color updates via colorChanged signal
*/
void setButtonMode(ButtonMode mode);
ButtonMode buttonMode() const;
QSize sizeHint() const;
ColorWheel::DisplayFlags wheelFlags() const;
public Q_SLOTS:
/**
* Change color
*/
void setColor(const QColor &c);
/**
* Set the current color and show the dialog
*/
void showColor(const QColor &oldcolor);
void setWheelFlags(ColorWheel::DisplayFlags flags);
/**
* Set whether the color alpha channel can be edited.
* If alpha is disabled, the selected color's alpha will always be 255.
*/
void setAlphaEnabled(bool a);
Q_SIGNALS:
/**
* The current color was changed
*/
void colorChanged(QColor);
/**
* The user selected the new color by pressing Ok/Apply
*/
void colorSelected(QColor);
void wheelFlagsChanged(ColorWheel::DisplayFlags flags);
void alphaEnabledChanged(bool alphaEnabled);
private Q_SLOTS:
/// Update all the Ui elements to match the selected color
void update_widgets();
/// Update from HSV sliders
void set_hsv();
/// Update from RGB sliders
void set_rgb();
void on_edit_hex_colorChanged(const QColor& color);
void on_edit_hex_colorEditingFinished(const QColor& color);
void on_buttonBox_clicked(QAbstractButton*);
private:
void setColorInternal(const QColor &color);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent * event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
class Private;
Private * const p;
};
} // namespace color_widgets
#endif // COLOR_DIALOG_HPP

View File

@@ -0,0 +1,97 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_LINE_EDIT_HPP
#define COLOR_WIDGETS_COLOR_LINE_EDIT_HPP
#include <QLineEdit>
#include <QColor>
namespace color_widgets {
/**
* \brief A line edit used to define a color name
*
* Supported string formats:
* * Short hex strings #f00
* * Long hex strings #ff0000
* * Color names red
* * Function-like rgb(255,0,0)
*
* Additional string formats supported when showAlpha is true:
* * Long hex strings #ff0000ff
* * Function like rgba(255,0,0,255)
*/
class Q_DECL_EXPORT ColorLineEdit : public QLineEdit
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
/**
* \brief Whether the widget displays and edits the alpha channel
*/
Q_PROPERTY(bool showAlpha READ showAlpha WRITE setShowAlpha NOTIFY showAlphaChanged)
/**
* \brief If \b true, the background of the widget is changed to show the color
*/
Q_PROPERTY(bool previewColor READ previewColor WRITE setPreviewColor NOTIFY previewColorChanged)
public:
explicit ColorLineEdit(QWidget* parent = nullptr);
~ColorLineEdit();
QColor color() const;
bool showAlpha() const;
bool previewColor() const;
public Q_SLOTS:
void setColor(const QColor& color);
void setShowAlpha(bool showAlpha);
void setPreviewColor(bool previewColor);
Q_SIGNALS:
/**
* \brief Emitted when the color is changed by any means
*/
void colorChanged(const QColor& color);
/**
* \brief Emitted when the user is typing a color but has not finished yet
*/
void colorEdited(const QColor& color);
/**
* \brief Emitted when the user finished to edit a string
*/
void colorEditingFinished(const QColor& color);
void showAlphaChanged(bool showAlpha);
void previewColorChanged(bool previewColor);
protected:
void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent * event) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE;
private:
class Private;
Private* p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_LINE_EDIT_HPP

View File

@@ -0,0 +1,73 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_LIST_WIDGET_HPP
#define COLOR_LIST_WIDGET_HPP
#include "qt-color-widgets/abstract_widget_list.hpp"
#include "qt-color-widgets/color_wheel.hpp"
namespace color_widgets {
class Q_DECL_EXPORT ColorListWidget : public AbstractWidgetList
{
Q_OBJECT
Q_PROPERTY(QList<QColor> colors READ colors WRITE setColors NOTIFY colorsChanged )
Q_PROPERTY(ColorWheel::DisplayFlags wheelFlags READ wheelFlags WRITE setWheelFlags NOTIFY wheelFlagsChanged)
public:
explicit ColorListWidget(QWidget *parent = 0);
~ColorListWidget();
QList<QColor> colors() const;
void setColors(const QList<QColor>& colors);
void swap(int a, int b);
void append();
void setColorAt(int i, QColor const& color);
ColorWheel::DisplayFlags wheelFlags() const;
Q_SIGNALS:
void color_added();
void colorsChanged(const QList<QColor>&);
void wheelFlagsChanged(ColorWheel::DisplayFlags flags);
public Q_SLOTS:
void setWheelFlags(ColorWheel::DisplayFlags flags);
private Q_SLOTS:
void emit_changed();
void handle_removed(int);
void color_changed(int row);
private:
class Private;
Private * const p;
void append_widget(int col);
};
} // namespace color_widgets
#endif // COLOR_LIST_WIDGET_HPP

View File

@@ -0,0 +1,55 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_NAMES_HPP
#define COLOR_WIDGETS_COLOR_NAMES_HPP
#include <QColor>
#include <QString>
namespace color_widgets {
/**
* \brief Convert a string into a color
*
* Supported string formats:
* * Short hex strings #f00
* * Long hex strings #ff0000
* * Color names red
* * Function-like rgb(255,0,0)
*
* Additional string formats supported only when \p alpha is true:
* * Long hex strings #ff0000ff
* * Function like rgba(255,0,0,255)
*/
QColor colorFromString(const QString& string, bool alpha = true);
/**
* \brief Convert a color into a string
*
* Format:
* * If the color has full alpha: #ff0000
* * If alpha is true and the color has non-full alpha: #ff000088
*/
QString stringFromColor(const QColor& color, bool alpha = true);
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_NAMES_HPP

View File

@@ -0,0 +1,228 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_PALETTE_HPP
#define COLOR_WIDGETS_COLOR_PALETTE_HPP
#include <QColor>
#include <QString>
#include <QVector>
#include <QObject>
#include <QPair>
#include <QPixmap>
namespace color_widgets {
class Q_DECL_EXPORT ColorPalette : public QObject
{
Q_OBJECT
/**
* \brief The list of colors
*/
Q_PROPERTY(QVector<value_type> colors READ colors WRITE setColors NOTIFY colorsChanged)
/**
* \brief Name of the palette
*/
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
/**
* \brief Number of colors to display in a row, if 0 unspecified
*/
Q_PROPERTY(int columns READ columns WRITE setColumns NOTIFY columnsChanged)
/**
* \brief Number of colors
*/
Q_PROPERTY(int count READ count)
/**
* \brief Name of the file the palette has been read from
*/
Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged)
/**
* \brief Whether it has been modified and it might be advisable to save it
*/
Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged)
public:
typedef QPair<QColor,QString> value_type;
ColorPalette(const QVector<QColor>& colors, const QString& name = QString(), int columns = 0);
ColorPalette(const QVector<QPair<QColor,QString> >& colors, const QString& name = QString(), int columns = 0);
explicit ColorPalette(const QString& name = QString());
ColorPalette(const ColorPalette& other);
ColorPalette& operator=(const ColorPalette& other);
~ColorPalette();
ColorPalette(ColorPalette&& other);
ColorPalette& operator=(ColorPalette&& other);
/**
* \brief Color at the given index
*/
Q_INVOKABLE QColor colorAt(int index) const;
/**
* \brief Color name at the given index
*/
Q_INVOKABLE QString nameAt(int index) const;
QVector<QPair<QColor,QString> > colors() const;
QVector<QColor> onlyColors() const;
int count() const;
int columns();
QString name() const;
/**
* \brief Use a color table to set the colors
*/
Q_INVOKABLE void loadColorTable(const QVector<QRgb>& color_table);
/**
* \brief Convert to a color table
*/
Q_INVOKABLE QVector<QRgb> colorTable() const;
/**
* \brief Creates a ColorPalette from a color table
*/
static ColorPalette fromColorTable(const QVector<QRgb>& table);
/**
* \brief Use the pixels on an image to set the palette colors
*/
Q_INVOKABLE bool loadImage(const QImage& image);
/**
* \brief Creates a ColorPalette from a Gimp palette (gpl) file
*/
static ColorPalette fromImage(const QImage& image);
/**
* \brief Load contents from a Gimp palette (gpl) file
* \returns \b true On Success
* \note If this function returns \b false, the palette will become empty
*/
Q_INVOKABLE bool load(const QString& name);
/**
* \brief Creates a ColorPalette from a Gimp palette (gpl) file
*/
static ColorPalette fromFile(const QString& name);
QString fileName() const;
bool dirty() const;
/**
* \brief Returns a preview image of the colors in the palette
*/
QPixmap preview(const QSize& size, const QColor& background=Qt::transparent) const;
public Q_SLOTS:
void setColumns(int columns);
void setColors(const QVector<QColor>& colors);
void setColors(const QVector<QPair<QColor,QString> >& colors);
/**
* \brief Change the color at the given index
*/
void setColorAt(int index, const QColor& color);
/**
* \brief Change the color at the given index
*/
void setColorAt(int index, const QColor& color, const QString& name);
/**
* \brief Change the name of a color
*/
void setNameAt(int index, const QString& name = QString());
/**
* \brief Append a color at the end
*/
void appendColor(const QColor& color, const QString& name = QString());
/**
* \brief Insert a color in an arbitrary location
*/
void insertColor(int index, const QColor& color, const QString& name = QString());
/**
* \brief Remove the color at the given index
*/
void eraseColor(int index);
/**
* \brief Change file name and save
* \returns \b true on success
*/
bool save(const QString& filename);
/**
* \brief save to file, the filename is \c fileName or determined automatically
* \returns \b true on success
*/
bool save();
void setName(const QString& name);
void setFileName(const QString& name);
void setDirty(bool dirty);
Q_SIGNALS:
/**
* \brief Emitted when all the colors have changed
*/
void colorsChanged(const QVector<QPair<QColor,QString> >&);
void columnsChanged(int);
void nameChanged(const QString&);
void fileNameChanged(const QString&);
void dirtyChanged(bool);
/**
* \brief Emitted when the color or the name at the given index has been modified
*/
void colorChanged(int index);
/**
* \brief Emitted when the color at the given index has been removed
*/
void colorRemoved(int index);
/**
* \brief Emitted when a single color has been added
*/
void colorAdded(int index);
/**
* \brief Emitted when the colors have been modified with a simple operation (set, append etc.)
*/
void colorsUpdated(const QVector<QPair<QColor,QString>>&);
private:
/**
* \brief Returns \c name if it isn't null, otherwise a default value
*/
QString unnamed(const QString& name = QString()) const;
/**
* \brief Emit all the necessary signals when the palette has been completely overwritten
*/
void emitUpdate();
class Private;
Private *p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_PALETTE_HPP

View File

@@ -0,0 +1,140 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_PALETTE_MODEL_HPP
#define COLOR_WIDGETS_COLOR_PALETTE_MODEL_HPP
#include <QAbstractListModel>
#include "qt-color-widgets/color_palette.hpp"
namespace color_widgets {
class ColorPaletteModel : public QAbstractListModel
{
Q_OBJECT
/**
* \brief List of directories to be scanned for palette files
*/
Q_PROPERTY(QStringList searchPaths READ searchPaths WRITE setSearchPaths NOTIFY searchPathsChanged)
/**
* \brief Default directory to be used when saving a palette
*/
Q_PROPERTY(QString savePath READ savePath WRITE setSavePath NOTIFY savePathChanged)
/**
* \brief Size of the icon used for the palette previews
*/
Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize NOTIFY iconSizeChanged)
public:
ColorPaletteModel();
~ColorPaletteModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
bool removeRows(int row, int count, const QModelIndex & parent = QModelIndex()) Q_DECL_OVERRIDE;
QString savePath() const;
QStringList searchPaths() const;
QSize iconSize() const;
/**
* \brief Number of palettes
*/
int count() const;
/**
* \brief Returns a reference to the first palette with the given name
* \pre hasPalette(name)
*/
const ColorPalette& palette(const QString& name) const;
/**
* \brief Whether a palette with the given name exists in the model
*/
bool hasPalette(const QString& name) const;
/**
* \brief Get the palette at the given index (row)
* \pre 0 <= index < count()
*/
const ColorPalette& palette(int index) const;
/**
* \brief Updates an existing palette
* \param index Palette index
* \param palette New palette
* \param save Whether to save the palette to the filesystem
*
* Saving will try: (in this order)
* * To overwrite the file pointed by the old palette
* * To write to the new palette file name
* * To create a file in the save path
* If all of the above fail, the palette will be replaced interally
* but not on the filesystem
*
* \returns \b true if the palette has been successfully updated (and saved)
*/
bool updatePalette(int index, const ColorPalette& palette, bool save = true);
/**
* \brief Remove a palette from the model and optionally from the filesystem
* \returns \b true if the palette has been successfully removed
*/
bool removePalette(int index, bool remove_file = true);
/**
* \brief Remove a palette to the model and optionally to the filesystem
* \returns \b true if the palette has been successfully added
*/
bool addPalette(const ColorPalette& palette, bool save = true);
/**
* \brief The index of the palette with the given file name
* \returns -1 if none is found
*/
int indexFromFile(const QString& filename) const;
public Q_SLOTS:
void setSavePath(const QString& savePath);
void setSearchPaths(const QStringList& searchPaths);
void addSearchPath(const QString& path);
void setIconSize(const QSize& iconSize);
/**
* \brief Load palettes files found in the search paths
*/
void load();
Q_SIGNALS:
void savePathChanged(const QString& savePath);
void searchPathsChanged(const QStringList& searchPaths);
void iconSizeChanged(const QSize& iconSize);
private:
class Private;
Private* p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_PALETTE_MODEL_HPP

View File

@@ -0,0 +1,181 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_COLOR_PALETTE_WIDGET_HPP
#define COLOR_WIDGETS_COLOR_PALETTE_WIDGET_HPP
#include <memory>
#include <QWidget>
#include "qt-color-widgets/color_palette_model.hpp"
#include "qt-color-widgets/swatch.hpp"
namespace color_widgets {
/**
* \brief A widget to use and modify palettes
*/
class ColorPaletteWidget : public QWidget
{
Q_OBJECT
/**
* \brief Model used to store the palettes
*/
Q_PROPERTY(ColorPaletteModel* model READ model WRITE setModel NOTIFY modelChanged)
/**
* \brief Size of a single color in the swatch widget
*/
Q_PROPERTY(QSize colorSize READ colorSize WRITE setColorSize NOTIFY colorSizeChanged)
/**
* \brief Policy for colorSize
**/
Q_PROPERTY(color_widgets::Swatch::ColorSizePolicy colorSizePolicy READ colorSizePolicy WRITE setColorSizePolicy NOTIFY colorSizePolicyChanged)
/**
* \brief Border around the colors
*/
Q_PROPERTY(QPen border READ border WRITE setBorder NOTIFY borderChanged)
/**
* \brief Forces the Swatch to display that many rows of colors
*
* If there are too few elements, the widget will display less than this
* many rows.
*
* A value of0 means that the number of rows is automatic.
*
* \note Conflicts with forcedColumns
*/
Q_PROPERTY(int forcedRows READ forcedRows WRITE setForcedRows NOTIFY forcedRowsChanged)
/**
* \brief Forces the Swatch to display that many columns of colors
*
* If there are too few elements, the widget will display less than this
* many columns.
*
* A value of 0 means that the number of columns is automatic.
*
* \note Conflicts with forcedRows
*/
Q_PROPERTY(int forcedColumns READ forcedColumns WRITE setForcedColumns NOTIFY forcedColumnsChanged)
/**
* \brief Whether the palettes can be modified via user interaction
*/
Q_PROPERTY(bool readOnly READ readOnly WRITE setReadOnly NOTIFY readOnlyChanged)
/**
* \brief Currently selected color
*/
Q_PROPERTY(QColor currentColor READ currentColor WRITE setCurrentColor NOTIFY currentColorChanged)
/**
* \brief Currently selected model row
*/
Q_PROPERTY(int currentRow READ currentRow WRITE setCurrentRow NOTIFY currentRowChanged)
public:
ColorPaletteWidget(QWidget* parent = nullptr);
~ColorPaletteWidget();
ColorPaletteModel* model() const;
/**
* \brief Currently selected palette
* \pre model() != nullptr and there is a selected palette
*/
const ColorPalette& currentPalette() const;
QSize colorSize() const;
Swatch::ColorSizePolicy colorSizePolicy() const;
QPen border() const;
int forcedRows() const;
int forcedColumns() const;
bool readOnly() const;
QColor currentColor() const;
int currentRow() const;
public Q_SLOTS:
void setModel(ColorPaletteModel* model);
void setColorSize(const QSize& colorSize);
void setColorSizePolicy(Swatch::ColorSizePolicy colorSizePolicy);
void setBorder(const QPen& border);
void setForcedRows(int forcedRows);
void setForcedColumns(int forcedColumns);
void setReadOnly(bool readOnly);
/**
* \brief Clear the selected color
*/
void clearCurrentColor();
/**
* \brief Attempt to select a color
*
* If the given color is available in the current palete, it will be selected
* \return \b true on success
*/
bool setCurrentColor(const QColor& color);
/**
* \brief Attempt to select a color by name
*
* If the given color is available in the current palete, it will be selected
* \return \b true on success
*/
bool setCurrentColor(const QString& name);
/**
* \brief Attempt to select a color by index
*
* If the given color is available in the current palete, it will be selected
* \return \b true on success
*/
bool setCurrentColor(int index);
/**
* \brief Set the selected row in the model
*/
void setCurrentRow(int currentRow);
Q_SIGNALS:
void modelChanged(ColorPaletteModel* model);
void colorSizeChanged(const QSize& colorSize);
void colorSizePolicyChanged(Swatch::ColorSizePolicy colorSizePolicy);
void forcedRowsChanged(int forcedRows);
void forcedColumnsChanged(int forcedColumns);
void readOnlyChanged(bool readOnly);
void currentColorChanged(const QColor& currentColor);
void currentColorChanged(int index);
void borderChanged(const QPen& border);
void currentRowChanged(int currentRow);
private Q_SLOTS:
void on_palette_list_currentIndexChanged(int index);
void on_swatch_doubleClicked(int index);
private:
class Private;
std::unique_ptr<Private> p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_COLOR_PALETTE_WIDGET_HPP

View File

@@ -0,0 +1,104 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
* \copyright Copyright (C) 2014 Calle Laakkonen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_PREVIEW_HPP
#define COLOR_PREVIEW_HPP
#include <QWidget>
namespace color_widgets {
/**
* Simple widget that shows a preview of a color
*/
class Q_DECL_EXPORT ColorPreview : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged DESIGNABLE true)
Q_PROPERTY(QColor comparisonColor READ comparisonColor WRITE setComparisonColor DESIGNABLE true)
Q_PROPERTY(DisplayMode display_mode READ displayMode WRITE setDisplayMode DESIGNABLE true)
Q_PROPERTY(QBrush background READ background WRITE setBackground DESIGNABLE true)
Q_ENUMS(DisplayMode)
public:
enum DisplayMode
{
NoAlpha, ///< Show current color with no transparency
AllAlpha, ///< show current color with transparency
SplitAlpha, ///< Show both solid and transparent side by side
SplitColor ///< Show current and comparison colors side by side
};
Q_ENUMS(DisplayMode)
explicit ColorPreview(QWidget *parent = 0);
~ColorPreview();
/// Get the background visible under transparent colors
QBrush background() const;
/// Change the background visible under transparent colors
void setBackground(const QBrush &bk);
/// Get color display mode
DisplayMode displayMode() const;
/// Set how transparent colors are handled
void setDisplayMode(DisplayMode dm);
/// Get current color
QColor color() const;
/// Get the comparison color
QColor comparisonColor() const;
QSize sizeHint () const;
void paint(QPainter &painter, QRect rect) const;
public Q_SLOTS:
/// Set current color
void setColor(const QColor &c);
/// Set the comparison color
void setComparisonColor(const QColor &c);
Q_SIGNALS:
/// Emitted when the user clicks on the widget
void clicked();
/// Emitted on setColor
void colorChanged(QColor);
protected:
void paintEvent(QPaintEvent *);
void resizeEvent(QResizeEvent *);
void mouseReleaseEvent(QMouseEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
private:
class Private;
Private * const p;
};
} // namespace color_widgets
Q_DECLARE_METATYPE(color_widgets::ColorPreview::DisplayMode)
#endif // COLOR_PREVIEW_HPP

View File

@@ -0,0 +1,88 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_SELECTOR_HPP
#define COLOR_SELECTOR_HPP
#include "qt-color-widgets/color_preview.hpp"
#include "qt-color-widgets/color_wheel.hpp"
namespace color_widgets {
/**
* Color preview that opens a color dialog
*/
class Q_DECL_EXPORT ColorSelector : public ColorPreview
{
Q_OBJECT
Q_ENUMS(UpdateMode)
Q_PROPERTY(UpdateMode updateMode READ updateMode WRITE setUpdateMode )
Q_PROPERTY(Qt::WindowModality dialogModality READ dialogModality WRITE setDialogModality )
Q_PROPERTY(ColorWheel::DisplayFlags wheelFlags READ wheelFlags WRITE setWheelFlags NOTIFY wheelFlagsChanged)
public:
enum UpdateMode {
Confirm, ///< Update color only after the dialog has been accepted
Continuous ///< Update color as it's being modified in the dialog
};
explicit ColorSelector(QWidget *parent = 0);
~ColorSelector();
void setUpdateMode(UpdateMode m);
UpdateMode updateMode() const;
Qt::WindowModality dialogModality() const;
void setDialogModality(Qt::WindowModality m);
ColorWheel::DisplayFlags wheelFlags() const;
Q_SIGNALS:
void wheelFlagsChanged(ColorWheel::DisplayFlags flags);
public Q_SLOTS:
void showDialog();
void setWheelFlags(ColorWheel::DisplayFlags flags);
private Q_SLOTS:
void accept_dialog();
void reject_dialog();
void update_old_color(const QColor &c);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent * event);
private:
/// Connect/Disconnect colorChanged based on UpdateMode
void connect_dialog();
/// Disconnect from dialog update
void disconnect_dialog();
class Private;
Private * const p;
};
} // namespace color_widgets
#endif // COLOR_SELECTOR_HPP

View File

@@ -0,0 +1,167 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WHEEL_HPP
#define COLOR_WHEEL_HPP
#include <QWidget>
namespace color_widgets {
/**
* \brief Display an analog widget that allows the selection of a HSV color
*
* It has an outer wheel to select the Hue and an intenal square to select
* Saturation and Lightness.
*/
class Q_DECL_EXPORT ColorWheel : public QWidget
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged DESIGNABLE true STORED false )
Q_PROPERTY(qreal hue READ hue WRITE setHue DESIGNABLE false )
Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation DESIGNABLE false )
Q_PROPERTY(qreal value READ value WRITE setValue DESIGNABLE false )
Q_PROPERTY(unsigned wheelWidth READ wheelWidth WRITE setWheelWidth DESIGNABLE true )
Q_PROPERTY(DisplayFlags displayFlags READ displayFlags WRITE setDisplayFlags NOTIFY displayFlagsChanged DESIGNABLE true )
public:
enum DisplayEnum
{
SHAPE_DEFAULT = 0x000, ///< Use the default shape
SHAPE_TRIANGLE = 0x001, ///< A triangle
SHAPE_SQUARE = 0x002, ///< A square
SHAPE_FLAGS = 0x00f, ///< Mask for the shape flags
ANGLE_DEFAULT = 0x000, ///< Use the default rotation style
ANGLE_FIXED = 0x010, ///< The inner part doesn't rotate
ANGLE_ROTATING = 0x020, ///< The inner part follows the hue selector
ANGLE_FLAGS = 0x0f0, ///< Mask for the angle flags
COLOR_DEFAULT = 0x000, ///< Use the default colorspace
COLOR_HSV = 0x100, ///< Use the HSV color space
COLOR_HSL = 0x200, ///< Use the HSL color space
COLOR_LCH = 0x400, ///< Use Luma Chroma Hue (Y_601')
COLOR_FLAGS = 0xf00, ///< Mask for the color space flags
FLAGS_DEFAULT = 0x000, ///< Use all defaults
FLAGS_ALL = 0xfff ///< Mask matching all flags
};
Q_DECLARE_FLAGS(DisplayFlags, DisplayEnum)
Q_FLAGS(DisplayFlags)
explicit ColorWheel(QWidget *parent = 0);
~ColorWheel();
/// Get current color
QColor color() const;
virtual QSize sizeHint() const Q_DECL_OVERRIDE;
/// Get current hue in the range [0-1]
qreal hue() const;
/// Get current saturation in the range [0-1]
qreal saturation() const;
/// Get current value in the range [0-1]
qreal value() const;
/// Get the width in pixels of the outer wheel
unsigned int wheelWidth() const;
/// Set the width in pixels of the outer wheel
void setWheelWidth(unsigned int w);
/// Get display flags
DisplayFlags displayFlags(DisplayFlags mask = FLAGS_ALL) const;
/// Set the default display flags
static void setDefaultDisplayFlags(DisplayFlags flags);
/// Get default display flags
static DisplayFlags defaultDisplayFlags(DisplayFlags mask = FLAGS_ALL);
/**
* @brief Set a specific display flag
* @param flag Flag replacing the mask
* @param mask Mask to be cleared
*/
void setDisplayFlag(DisplayFlags flag, DisplayFlags mask);
public Q_SLOTS:
/// Set current color
void setColor(QColor c);
/**
* @param h Hue [0-1]
*/
void setHue(qreal h);
/**
* @param s Saturation [0-1]
*/
void setSaturation(qreal s);
/**
* @param v Value [0-1]
*/
void setValue(qreal v);
/**
* @brief Set the display flags
* @param flags which will replace the current ones
*/
void setDisplayFlags(ColorWheel::DisplayFlags flags);
Q_SIGNALS:
/**
* Emitted when the user selects a color or setColor is called
*/
void colorChanged(QColor);
/**
* Emitted when the user selects a color
*/
void colorSelected(QColor);
void displayFlagsChanged(ColorWheel::DisplayFlags flags);
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
void dragEnterEvent(QDragEnterEvent* event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent* event) Q_DECL_OVERRIDE;
private:
class Private;
Private * const p;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(ColorWheel::DisplayFlags)
} // namespace color_widgets
#endif // COLOR_WHEEL_HPP

View File

@@ -0,0 +1,107 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
* \copyright Copyright (C) 2014 Calle Laakkonen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef GRADIENT_SLIDER_HPP
#define GRADIENT_SLIDER_HPP
#include <QSlider>
#include <QGradient>
namespace color_widgets {
/**
* \brief A slider that mover on top of a gradient
*/
class Q_DECL_EXPORT GradientSlider : public QSlider
{
Q_OBJECT
Q_PROPERTY(QBrush background READ background WRITE setBackground)
Q_PROPERTY(QGradientStops colors READ colors WRITE setColors DESIGNABLE false)
Q_PROPERTY(QColor firstColor READ firstColor WRITE setFirstColor STORED false)
Q_PROPERTY(QColor lastColor READ lastColor WRITE setLastColor STORED false)
Q_PROPERTY(QLinearGradient gradient READ gradient WRITE setGradient)
public:
explicit GradientSlider(QWidget *parent = 0);
explicit GradientSlider(Qt::Orientation orientation, QWidget *parent = 0);
~GradientSlider();
/// Get the background, it's visible for transparent gradient stops
QBrush background() const;
/// Set the background, it's visible for transparent gradient stops
void setBackground(const QBrush &bg);
/// Get the colors that make up the gradient
QGradientStops colors() const;
/// Set the colors that make up the gradient
void setColors(const QGradientStops &colors);
/// Get the gradient
QLinearGradient gradient() const;
/// Set the gradient
void setGradient(const QLinearGradient &gradient);
/**
* Overload: create an evenly distributed gradient of the given colors
*/
void setColors(const QVector<QColor> &colors);
/**
* \brief Set the first color of the gradient
*
* If the gradient is currently empty it will create a stop with the given color
*/
void setFirstColor(const QColor &c);
/**
* \brief Set the last color of the gradient
*
* If the gradient is has less than two colors,
* it will create a stop with the given color
*/
void setLastColor(const QColor &c);
/**
* \brief Get the first color
*
* \returns QColor() con empty gradient
*/
QColor firstColor() const;
/**
* \brief Get the last color
*
* \returns QColor() con empty gradient
*/
QColor lastColor() const;
protected:
void paintEvent(QPaintEvent *ev);
private:
class Private;
Private * const p;
};
} // namespace color_widgets
#endif // GRADIENT_SLIDER_HPP

View File

@@ -0,0 +1,95 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2014 Calle Laakkonen
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef HUE_SLIDER_HPP
#define HUE_SLIDER_HPP
#include "qt-color-widgets/gradient_slider.hpp"
namespace color_widgets {
/**
* \brief A slider for selecting a hue value
*/
class Q_DECL_EXPORT HueSlider : public GradientSlider
{
Q_OBJECT
/**
* \brief Saturation used in the rainbow gradient, as a [0-1] float
*/
Q_PROPERTY(qreal colorSaturation READ colorSaturation WRITE setColorSaturation)
/**
* \brief Value used in the rainbow gradient, as a [0-1] float
*/
Q_PROPERTY(qreal colorValue READ colorValue WRITE setColorValue)
/**
* \brief Alpha used in the rainbow gradient, as a [0-1] float
*/
Q_PROPERTY(qreal colorAlpha READ colorAlpha WRITE setColorAlpha)
/**
* \brief Color with corresponding color* components
*/
Q_PROPERTY(QColor color READ color WRITE setColor)
/**
* \brief Normalized Hue, as indicated from the slider
*/
Q_PROPERTY(qreal colorHue READ colorHue WRITE setColorHue NOTIFY colorHueChanged)
public:
explicit HueSlider(QWidget *parent = nullptr);
explicit HueSlider(Qt::Orientation orientation, QWidget *parent = nullptr);
~HueSlider();
qreal colorSaturation() const;
qreal colorValue() const;
qreal colorAlpha() const;
QColor color() const;
qreal colorHue() const;
public Q_SLOTS:
void setColorValue(qreal value);
void setColorSaturation(qreal value);
void setColorAlpha(qreal alpha);
void setColorHue(qreal colorHue);
/**
* \brief Set Hue Saturation and ColorValue, ignoring alpha
*/
void setColor(const QColor& color);
/**
* \brief Set Hue Saturation, ColorValue and Alpha
*/
void setFullColor(const QColor& color);
Q_SIGNALS:
void colorHueChanged(qreal colorHue);
private:
class Private;
Private * const p;
};
} // namespace color_widgets
#endif // HUE_SLIDER_HPP

View File

@@ -0,0 +1,194 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef COLOR_WIDGETS_SWATCH_HPP
#define COLOR_WIDGETS_SWATCH_HPP
#include <QWidget>
#include <QPen>
#include "qt-color-widgets/color_palette.hpp"
namespace color_widgets {
/**
* \brief A widget drawing a palette
*/
class Swatch : public QWidget
{
Q_OBJECT
/**
* \brief Palette shown by the widget
*/
Q_PROPERTY(const ColorPalette& palette READ palette WRITE setPalette NOTIFY paletteChanged)
/**
* \brief Currently selected color (-1 if no color is selected)
*/
Q_PROPERTY(int selected READ selected WRITE setSelected NOTIFY selectedChanged)
/**
* \brief Preferred size for a color square
*/
Q_PROPERTY(QSize colorSize READ colorSize WRITE setColorSize NOTIFY colorSizeChanged)
Q_PROPERTY(ColorSizePolicy colorSizePolicy READ colorSizePolicy WRITE setColorSizePolicy NOTIFY colorSizePolicyChanged)
/**
* \brief Border around the colors
*/
Q_PROPERTY(QPen border READ border WRITE setBorder NOTIFY borderChanged)
/**
* \brief Forces the Swatch to display that many rows of colors
*
* If there are too few elements, the widget will display less than this
* many rows.
*
* A value of0 means that the number of rows is automatic.
*
* \note Conflicts with forcedColumns
*/
Q_PROPERTY(int forcedRows READ forcedRows WRITE setForcedRows NOTIFY forcedRowsChanged)
/**
* \brief Forces the Swatch to display that many columns of colors
*
* If there are too few elements, the widget will display less than this
* many columns.
*
* A value of 0 means that the number of columns is automatic.
*
* \note Conflicts with forcedRows
*/
Q_PROPERTY(int forcedColumns READ forcedColumns WRITE setForcedColumns NOTIFY forcedColumnsChanged)
/**
* \brief Whether the palette can be modified via user interaction
* \note Even when this is \b false, it can still be altered programmatically
*/
Q_PROPERTY(bool readOnly READ readOnly WRITE setReadOnly NOTIFY readOnlyChanged)
public:
enum ColorSizePolicy
{
Hint, ///< The size is just a hint
Minimum, ///< Can expand but not contract
Fixed ///< Must be exactly as specified
};
Q_ENUMS(ColorSizePolicy)
Swatch(QWidget* parent = 0);
~Swatch();
QSize sizeHint() const Q_DECL_OVERRIDE;
QSize minimumSizeHint() const Q_DECL_OVERRIDE;
const ColorPalette& palette() const;
ColorPalette& palette();
int selected() const;
/**
* \brief Color at the currently selected index
*/
QColor selectedColor() const;
/**
* \brief Color index at the given position within the widget
* \param p Point in local coordinates
* \returns -1 if the position doesn't represent any color
*/
int indexAt(const QPoint& p);
/**
* \brief Color at the given position within the widget
* \param p Point in local coordinates
*/
QColor colorAt(const QPoint& p);
QSize colorSize() const;
ColorSizePolicy colorSizePolicy() const;
QPen border() const;
int forcedRows() const;
int forcedColumns() const;
bool readOnly() const;
public Q_SLOTS:
void setPalette(const ColorPalette& palette);
void setSelected(int selected);
void clearSelection();
void setColorSize(const QSize& colorSize);
void setColorSizePolicy(ColorSizePolicy colorSizePolicy);
void setBorder(const QPen& border);
void setForcedRows(int forcedRows);
void setForcedColumns(int forcedColumns);
void setReadOnly(bool readOnly);
/**
* \brief Remove the currently seleceted color
**/
void removeSelected();
Q_SIGNALS:
void paletteChanged(const ColorPalette& palette);
void selectedChanged(int selected);
void colorSelected(const QColor& color);
void colorSizeChanged(const QSize& colorSize);
void colorSizePolicyChanged(ColorSizePolicy colorSizePolicy);
void doubleClicked(int index);
void rightClicked(int index);
void forcedRowsChanged(int forcedRows);
void forcedColumnsChanged(int forcedColumns);
void readOnlyChanged(bool readOnly);
void borderChanged(const QPen& border);
protected:
bool event(QEvent* event) Q_DECL_OVERRIDE;
void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE;
void keyPressEvent(QKeyEvent* event) Q_DECL_OVERRIDE;
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseDoubleClickEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void wheelEvent(QWheelEvent* event) Q_DECL_OVERRIDE;
void dragEnterEvent(QDragEnterEvent *event) Q_DECL_OVERRIDE;
void dragMoveEvent(QDragMoveEvent* event) Q_DECL_OVERRIDE;
void dragLeaveEvent(QDragLeaveEvent *event) Q_DECL_OVERRIDE;
void dropEvent(QDropEvent* event) Q_DECL_OVERRIDE;
protected Q_SLOTS:
/**
* \brief Connected to the internal palette object to keep eveything consistent
*/
void paletteModified();
private:
class Private;
Private* p;
};
} // namespace color_widgets
#endif // COLOR_WIDGETS_SWATCH_HPP

View File

@@ -0,0 +1,185 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/abstract_widget_list.hpp"
#include <../../noggit/ui/font_awesome.hpp>
#include <QToolButton>
#include <QVBoxLayout>
#include <QHeaderView>
#include <QPushButton>
class AbstractWidgetList::Private
{
public:
QList<QWidget*> widgets;
QSignalMapper mapper_up;
QSignalMapper mapper_down;
QSignalMapper mapper_remove;
QTableWidget *table;
};
AbstractWidgetList::AbstractWidgetList(QWidget *parent) :
QWidget(parent), p(new Private)
{
connect(&p->mapper_up,SIGNAL(mapped(QWidget*)),SLOT(up_clicked(QWidget*)));
connect(&p->mapper_down,SIGNAL(mapped(QWidget*)),SLOT(down_clicked(QWidget*)));
connect(&p->mapper_remove,SIGNAL(mapped(QWidget*)),SLOT(remove_clicked(QWidget*)));
QVBoxLayout *verticalLayout = new QVBoxLayout(this);
verticalLayout->setContentsMargins(0, 0, 0, 0);
p->table = new QTableWidget(this);
verticalLayout->addWidget(p->table);
p->table->insertColumn(0);
p->table->insertColumn(1);
p->table->insertColumn(2);
p->table->insertColumn(3);
p->table->setColumnWidth(0,64);
p->table->setColumnWidth(1,24);
p->table->setColumnWidth(2,24);
p->table->setColumnWidth(3,24);
p->table->horizontalHeader()->hide();
p->table->verticalHeader()->hide();
p->table->setShowGrid(false);
QPushButton* add_button = new QPushButton(noggit::ui::font_awesome_icon(noggit::ui::font_awesome::plus),
tr("Add New"));
verticalLayout->addWidget(add_button);
connect(add_button,&QAbstractButton::clicked,this, &AbstractWidgetList::append);
}
AbstractWidgetList::~AbstractWidgetList()
{
delete p;
}
int AbstractWidgetList::count() const
{
return p->widgets.size();
}
void AbstractWidgetList::setRowHeight(int row, int height)
{
p->table->setRowHeight(row,height);
}
void AbstractWidgetList::clear()
{
p->widgets.clear();
while(p->table->rowCount() > 0)
p->table->removeRow(0);
}
void AbstractWidgetList::remove(int i)
{
if ( isValidRow(i) )
{
p->widgets.removeAt(i);
p->table->removeRow(i);
if ( i == 0 && !p->widgets.isEmpty() )
p->table->cellWidget(0,1)->setEnabled(false);
else if ( i != 0 && i == count() )
p->table->cellWidget(count()-1,2)->setEnabled(false);
Q_EMIT removed(i);
}
}
void AbstractWidgetList::appendWidget(QWidget *w)
{
int row = count();
p->table->insertRow(row);
QString button_style =
"QToolButton { \n "
" border: none; \n "
"} \n";
QWidget* b_up = create_button(w,&p->mapper_up, noggit::ui::font_awesome_icon(noggit::ui::font_awesome::chevronup),tr(""), tr("Move Up"));
b_up->setStyleSheet(button_style);
QWidget* b_down = create_button(w,&p->mapper_down, noggit::ui::font_awesome_icon(noggit::ui::font_awesome::chevrondown),tr(""), tr("Move Down"));
b_down->setStyleSheet(button_style);
QWidget* b_remove = create_button(w,&p->mapper_remove, noggit::ui::font_awesome_icon(noggit::ui::font_awesome::timescircle),tr(""), tr("Remove"));
b_remove->setStyleSheet(button_style);
if ( row == 0 )
b_up->setEnabled(false);
else
p->table->cellWidget(row-1,2)->setEnabled(true);
b_down->setEnabled(false);
p->table->setCellWidget(row,0,w);
p->table->setCellWidget(row,1,b_up);
p->table->setCellWidget(row,2,b_down);
p->table->setCellWidget(row,3,b_remove);
p->widgets.push_back(w);
}
QWidget *AbstractWidgetList::widget(int i)
{
if ( isValidRow(i) )
return p->widgets[i];
return 0;
}
QWidget *AbstractWidgetList::create_button(QWidget *data, QSignalMapper *mapper,
QIcon icon,
QString text, QString tooltip) const
{
QToolButton* btn = new QToolButton;
btn->setIcon(icon);
btn->setText(text);
btn->setToolTip(tooltip.isNull() ? btn->text() : tooltip );
connect(btn,SIGNAL(clicked()),mapper,SLOT(map()));
mapper->setMapping(btn,data);
return btn;
}
void AbstractWidgetList::remove_clicked(QWidget *w)
{
int row = p->widgets.indexOf(w);
remove(row);
}
void AbstractWidgetList::up_clicked(QWidget *w)
{
int row = p->widgets.indexOf(w);
if ( row > 0 )
swap(row,row-1);
}
void AbstractWidgetList::down_clicked(QWidget *w)
{
int row = p->widgets.indexOf(w);
if ( row+1 < count() )
swap(row,row+1);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

View File

@@ -0,0 +1,38 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/bound_color_selector.hpp"
namespace color_widgets {
BoundColorSelector::BoundColorSelector(QColor* reference, QWidget *parent) :
ColorSelector(parent), ref(reference)
{
setColor(*reference);
connect(this,&ColorPreview::colorChanged,this, &BoundColorSelector::update_reference);
}
void BoundColorSelector::update_reference(QColor c)
{
*ref = c;
}
} // namespace color_widgets

View File

@@ -0,0 +1,267 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_2d_slider.hpp"
#include "color_utils.hpp"
#include <QImage>
#include <QPainter>
#include <QMouseEvent>
#include <QResizeEvent>
namespace color_widgets {
static const double selector_radius = 6;
class Color2DSlider::Private
{
public:
qreal hue = 1, sat = 1, val = 1;
Component comp_x = Saturation;
Component comp_y = Value;
QImage square;
qreal PixHue(float x, float y)
{
if ( comp_x == Hue )
return x;
if ( comp_y == Hue )
return y;
return hue;
}
qreal PixSat(float x, float y)
{
if ( comp_x == Saturation )
return x;
if ( comp_y == Saturation )
return y;
return sat;
}
qreal PixVal(float x, float y)
{
if ( comp_x == Value )
return x;
if ( comp_y == Value )
return y;
return val;
}
void renderSquare(const QSize& size)
{
square = QImage(size, QImage::Format_RGB32);
for ( int y = 0; y < size.height(); ++y )
{
qreal yfloat = 1 - qreal(y) / size.height();
for ( int x = 0; x < size.width(); ++x )
{
qreal xfloat = qreal(x) / size.width();
square.setPixel( x, y, QColor::fromHsvF(
PixHue(xfloat, yfloat),
PixSat(xfloat, yfloat),
PixVal(xfloat, yfloat)
).rgb());
}
}
}
QPointF selectorPos(const QSize& size)
{
QPointF pt;
switch ( comp_x )
{
case Hue: pt.setX(size.width()*hue); break;
case Saturation:pt.setX(size.width()*sat); break;
case Value: pt.setX(size.width()*val); break;
}
switch ( comp_y )
{
case Hue: pt.setY(size.height()*(1-hue)); break;
case Saturation:pt.setY(size.height()*(1-sat)); break;
case Value: pt.setY(size.height()*(1-val)); break;
}
return pt;
}
void setColorFromPos(const QPoint& pt, const QSize& size)
{
QPointF ptfloat(
qBound(0.0, qreal(pt.x()) / size.width(), 1.0),
qBound(0.0, 1 - qreal(pt.y()) / size.height(), 1.0)
);
switch ( comp_x )
{
case Hue: hue = ptfloat.x(); break;
case Saturation:sat = ptfloat.x(); break;
case Value: val = ptfloat.x(); break;
}
switch ( comp_y )
{
case Hue: hue = ptfloat.y(); break;
case Saturation:sat = ptfloat.y(); break;
case Value: val = ptfloat.y(); break;
}
}
};
Color2DSlider::Color2DSlider(QWidget* parent)
: QWidget(parent), p(new Private)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
Color2DSlider::~Color2DSlider()
{
delete p;
}
QColor Color2DSlider::color() const
{
return QColor::fromHsvF(p->hue, p->sat, p->val);
}
QSize Color2DSlider::sizeHint() const
{
return {128, 128};
}
qreal Color2DSlider::hue() const
{
return p->hue;
}
qreal Color2DSlider::saturation() const
{
return p->sat;
}
qreal Color2DSlider::value() const
{
return p->val;
}
Color2DSlider::Component Color2DSlider::componentX() const
{
return p->comp_x;
}
Color2DSlider::Component Color2DSlider::componentY() const
{
return p->comp_y;
}
void Color2DSlider::setColor(const QColor& c)
{
p->hue = c.hsvHueF();
p->sat = c.saturationF();
p->val = c.valueF();
p->renderSquare(size());
update();
Q_EMIT colorChanged(color());
}
void Color2DSlider::setHue(qreal h)
{
p->hue = h;
p->renderSquare(size());
update();
Q_EMIT colorChanged(color());
}
void Color2DSlider::setSaturation(qreal s)
{
p->sat = s;
p->renderSquare(size());
update();
Q_EMIT colorChanged(color());
}
void Color2DSlider::setValue(qreal v)
{
p->val = v;
p->renderSquare(size());
update();
Q_EMIT colorChanged(color());
}
void Color2DSlider::setComponentX(Color2DSlider::Component componentX)
{
if ( componentX != p->comp_x )
{
p->comp_x = componentX;
p->renderSquare(size());
update();
Q_EMIT componentXChanged(p->comp_x);
}
}
void Color2DSlider::setComponentY(Color2DSlider::Component componentY)
{
if ( componentY != p->comp_y )
{
p->comp_y = componentY;
p->renderSquare(size());
update();
Q_EMIT componentXChanged(p->comp_y);
}
}
void Color2DSlider::paintEvent(QPaintEvent*)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.drawImage(0,0,p->square);
painter.setPen(QPen(p->val > 0.5 ? Qt::black : Qt::white, 3));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(p->selectorPos(size()), selector_radius, selector_radius);
}
void Color2DSlider::mousePressEvent(QMouseEvent* event)
{
p->setColorFromPos(event->pos(), size());
Q_EMIT colorChanged(color());
update();
}
void Color2DSlider::mouseMoveEvent(QMouseEvent* event)
{
p->setColorFromPos(event->pos(), size());
Q_EMIT colorChanged(color());
update();
}
void Color2DSlider::mouseReleaseEvent(QMouseEvent* event)
{
p->setColorFromPos(event->pos(), size());
Q_EMIT colorChanged(color());
update();
}
void Color2DSlider::resizeEvent(QResizeEvent* event)
{
p->renderSquare(event->size());
update();
}
} // namespace color_widgets

View File

@@ -0,0 +1,94 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_delegate.hpp"
#include "qt-color-widgets/color_selector.hpp"
#include "qt-color-widgets/color_dialog.hpp"
#include <QPainter>
#include <QMouseEvent>
namespace color_widgets {
ColorDelegate::ColorDelegate(QWidget *parent) :
QAbstractItemDelegate(parent)
{
}
void ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data().canConvert<QColor>())
{
QStyleOptionFrame panel;
panel.initFrom(option.widget);
if (option.widget->isEnabled())
panel.state = QStyle::State_Enabled;
panel.rect = option.rect;
panel.lineWidth = 2;
panel.midLineWidth = 0;
panel.state |= QStyle::State_Sunken;
option.widget->style()->drawPrimitive(QStyle::PE_Frame, &panel, painter, nullptr);
QRect r = option.widget->style()->subElementRect(QStyle::SE_FrameContents, &panel, nullptr);
painter->setClipRect(r);
painter->fillRect(option.rect, index.data().value<QColor>());
}
}
bool ColorDelegate::editorEvent(QEvent* event,
QAbstractItemModel* model,
const QStyleOptionViewItem& option,
const QModelIndex& index)
{
if ( event->type() == QEvent::MouseButtonRelease && index.data().canConvert<QColor>())
{
QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
if ( mouse_event->button() == Qt::LeftButton &&
( index.flags() & Qt::ItemIsEditable) )
{
ColorDialog *editor = new ColorDialog(const_cast<QWidget*>(option.widget));
connect(this, &QObject::destroyed, editor, &QObject::deleteLater);
editor->setMinimumSize(editor->sizeHint());
auto original_color = index.data().value<QColor>();
editor->setColor(original_color);
auto set_color = [model, index](const QColor& color){
model->setData(index, QVariant(color));
};
connect(editor, &ColorDialog::colorSelected, this, set_color);
editor->show();
}
return true;
}
return QAbstractItemDelegate::editorEvent(event, model, option, index);
}
QSize ColorDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index)
Q_UNUSED(option)
return QSize(24,16);
}
} // namespace color_widgets

View File

@@ -0,0 +1,340 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
* \copyright Copyright (C) 2014 Calle Laakkonen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_dialog.hpp"
#include "ui_color_dialog.h"
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QDesktopWidget>
#include <QMimeData>
#include <QPushButton>
#include <QScreen>
namespace color_widgets {
class ColorDialog::Private
{
public:
Ui_ColorDialog ui;
ButtonMode button_mode;
bool pick_from_screen;
bool alpha_enabled;
Private() : pick_from_screen(false), alpha_enabled(true)
{}
};
ColorDialog::ColorDialog(QWidget *parent, Qt::WindowFlags f) :
QDialog(parent, f), p(new Private)
{
p->ui.setupUi(this);
setAcceptDrops(true);
// Add "pick color" button
QPushButton *pickButton = p->ui.buttonBox->addButton(tr("Pick"), QDialogButtonBox::ActionRole);
pickButton->setIcon(QIcon::fromTheme(QStringLiteral("color-picker")));
setButtonMode(OkApplyCancel);
connect(p->ui.wheel,&ColorWheel::displayFlagsChanged,this, &ColorDialog::wheelFlagsChanged);
}
QSize ColorDialog::sizeHint() const
{
return QSize(400,0);
}
ColorWheel::DisplayFlags ColorDialog::wheelFlags() const
{
return p->ui.wheel->displayFlags();
}
QColor ColorDialog::color() const
{
QColor col = p->ui.wheel->color();
if(p->alpha_enabled)
col.setAlpha(p->ui.slide_alpha->value());
return col;
}
void ColorDialog::setColor(const QColor &c)
{
p->ui.preview->setComparisonColor(c);
p->ui.edit_hex->setModified(false);
setColorInternal(c);
}
void ColorDialog::setColorInternal(const QColor &c)
{
/**
* \note Unlike setColor, this is used to update the current color which
* migth differ from the final selected color
*/
p->ui.wheel->setColor(c);
p->ui.slide_alpha->setValue(c.alpha());
update_widgets();
}
void ColorDialog::showColor(const QColor &c)
{
setColor(c);
show();
}
void ColorDialog::setWheelFlags(ColorWheel::DisplayFlags flags)
{
p->ui.wheel->setDisplayFlags(flags);
}
void ColorDialog::setPreviewDisplayMode(ColorPreview::DisplayMode mode)
{
p->ui.preview->setDisplayMode(mode);
}
ColorPreview::DisplayMode ColorDialog::previewDisplayMode() const
{
return p->ui.preview->displayMode();
}
void ColorDialog::setAlphaEnabled(bool a)
{
if ( a != p->alpha_enabled )
{
p->alpha_enabled = a;
p->ui.edit_hex->setShowAlpha(a);
p->ui.line_alpha->setVisible(a);
p->ui.label_alpha->setVisible(a);
p->ui.slide_alpha->setVisible(a);
p->ui.spin_alpha->setVisible(a);
Q_EMIT alphaEnabledChanged(a);
}
}
bool ColorDialog::alphaEnabled() const
{
return p->alpha_enabled;
}
void ColorDialog::setButtonMode(ButtonMode mode)
{
p->button_mode = mode;
QDialogButtonBox::StandardButtons btns;
switch(mode) {
case OkCancel: btns = QDialogButtonBox::Ok | QDialogButtonBox::Cancel; break;
case OkApplyCancel: btns = QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Reset; break;
case Close: btns = QDialogButtonBox::Close;
}
p->ui.buttonBox->setStandardButtons(btns);
}
ColorDialog::ButtonMode ColorDialog::buttonMode() const
{
return p->button_mode;
}
void ColorDialog::update_widgets()
{
bool blocked = signalsBlocked();
blockSignals(true);
Q_FOREACH(QWidget* w, findChildren<QWidget*>())
w->blockSignals(true);
QColor col = color();
p->ui.slide_red->setValue(col.red());
p->ui.spin_red->setValue(p->ui.slide_red->value());
p->ui.slide_red->setFirstColor(QColor(0,col.green(),col.blue()));
p->ui.slide_red->setLastColor(QColor(255,col.green(),col.blue()));
p->ui.slide_green->setValue(col.green());
p->ui.spin_green->setValue(p->ui.slide_green->value());
p->ui.slide_green->setFirstColor(QColor(col.red(),0,col.blue()));
p->ui.slide_green->setLastColor(QColor(col.red(),255,col.blue()));
p->ui.slide_blue->setValue(col.blue());
p->ui.spin_blue->setValue(p->ui.slide_blue->value());
p->ui.slide_blue->setFirstColor(QColor(col.red(),col.green(),0));
p->ui.slide_blue->setLastColor(QColor(col.red(),col.green(),255));
p->ui.slide_hue->setValue(qRound(p->ui.wheel->hue()*360.0));
p->ui.slide_hue->setColorSaturation(p->ui.wheel->saturation());
p->ui.slide_hue->setColorValue(p->ui.wheel->value());
p->ui.spin_hue->setValue(p->ui.slide_hue->value());
p->ui.slide_saturation->setValue(qRound(p->ui.wheel->saturation()*255.0));
p->ui.spin_saturation->setValue(p->ui.slide_saturation->value());
p->ui.slide_saturation->setFirstColor(QColor::fromHsvF(p->ui.wheel->hue(),0,p->ui.wheel->value()));
p->ui.slide_saturation->setLastColor(QColor::fromHsvF(p->ui.wheel->hue(),1,p->ui.wheel->value()));
p->ui.slide_value->setValue(qRound(p->ui.wheel->value()*255.0));
p->ui.spin_value->setValue(p->ui.slide_value->value());
p->ui.slide_value->setFirstColor(QColor::fromHsvF(p->ui.wheel->hue(), p->ui.wheel->saturation(),0));
p->ui.slide_value->setLastColor(QColor::fromHsvF(p->ui.wheel->hue(), p->ui.wheel->saturation(),1));
QColor apha_color = col;
apha_color.setAlpha(0);
p->ui.slide_alpha->setFirstColor(apha_color);
apha_color.setAlpha(255);
p->ui.slide_alpha->setLastColor(apha_color);
p->ui.spin_alpha->setValue(p->ui.slide_alpha->value());
if ( !p->ui.edit_hex->isModified() )
p->ui.edit_hex->setColor(col);
p->ui.preview->setColor(col);
blockSignals(blocked);
Q_FOREACH(QWidget* w, findChildren<QWidget*>())
w->blockSignals(false);
Q_EMIT colorChanged(col);
}
void ColorDialog::set_hsv()
{
if ( !signalsBlocked() )
{
p->ui.wheel->setColor(QColor::fromHsv(
p->ui.slide_hue->value(),
p->ui.slide_saturation->value(),
p->ui.slide_value->value()
));
update_widgets();
}
}
void ColorDialog::set_rgb()
{
if ( !signalsBlocked() )
{
QColor col(
p->ui.slide_red->value(),
p->ui.slide_green->value(),
p->ui.slide_blue->value()
);
if (col.saturation() == 0)
col = QColor::fromHsv(p->ui.slide_hue->value(), 0, col.value());
p->ui.wheel->setColor(col);
update_widgets();
}
}
void ColorDialog::on_edit_hex_colorChanged(const QColor& color)
{
setColorInternal(color);
}
void ColorDialog::on_edit_hex_colorEditingFinished(const QColor& color)
{
p->ui.edit_hex->setModified(false);
setColorInternal(color);
}
void ColorDialog::on_buttonBox_clicked(QAbstractButton *btn)
{
QDialogButtonBox::ButtonRole role = p->ui.buttonBox->buttonRole(btn);
switch(role) {
case QDialogButtonBox::AcceptRole:
case QDialogButtonBox::ApplyRole:
// Explicitly select the color
p->ui.preview->setComparisonColor(color());
Q_EMIT colorSelected(color());
break;
case QDialogButtonBox::ActionRole:
// Currently, the only action button is the "pick color" button
grabMouse(Qt::CrossCursor);
p->pick_from_screen = true;
break;
case QDialogButtonBox::ResetRole:
// Restore old color
setColorInternal(p->ui.preview->comparisonColor());
break;
default: break;
}
}
void ColorDialog::dragEnterEvent(QDragEnterEvent *event)
{
if ( event->mimeData()->hasColor() ||
( event->mimeData()->hasText() && QColor(event->mimeData()->text()).isValid() ) )
event->acceptProposedAction();
}
void ColorDialog::dropEvent(QDropEvent *event)
{
if ( event->mimeData()->hasColor() )
{
setColorInternal(event->mimeData()->colorData().value<QColor>());
event->accept();
}
else if ( event->mimeData()->hasText() )
{
QColor col(event->mimeData()->text());
if ( col.isValid() )
{
setColorInternal(col);
event->accept();
}
}
}
static QColor get_screen_color(const QPoint &global_pos)
{
int screenNum = QApplication::desktop()->screenNumber(global_pos);
QScreen *screen = QApplication::screens().at(screenNum);
WId wid = QApplication::desktop()->winId();
QImage img = screen->grabWindow(wid, global_pos.x(), global_pos.y(), 1, 1).toImage();
return img.pixel(0,0);
}
void ColorDialog::mouseReleaseEvent(QMouseEvent *event)
{
if (p->pick_from_screen)
{
setColorInternal(get_screen_color(event->globalPos()));
p->pick_from_screen = false;
releaseMouse();
}
}
void ColorDialog::mouseMoveEvent(QMouseEvent *event)
{
if (p->pick_from_screen)
{
setColorInternal(get_screen_color(event->globalPos()));
}
}
} // namespace color_widgets

View File

@@ -0,0 +1,699 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ColorDialog</class>
<widget class="QDialog" name="ColorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>491</width>
<height>380</height>
</rect>
</property>
<property name="windowTitle">
<string>Select Color</string>
</property>
<property name="windowIcon">
<iconset theme="format-fill-color">
<normaloff/>
</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="color_widgets::ColorWheel" name="wheel">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="color_widgets::ColorPreview" name="preview">
<property name="display_mode" stdset="0">
<enum>color_widgets::ColorPreview::SplitColor</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="color_widgets::GradientSlider" name="slide_value">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Saturation</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Hue</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="color_widgets::GradientSlider" name="slide_saturation">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Hex</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Blue</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="color_widgets::GradientSlider" name="slide_alpha">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="color_widgets::GradientSlider" name="slide_red">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="color_widgets::GradientSlider" name="slide_green">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Green</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_alpha">
<property name="text">
<string>Alpha</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Red</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="color_widgets::GradientSlider" name="slide_blue">
<property name="maximum">
<number>255</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="spin_hue">
<property name="wrapping">
<bool>true</bool>
</property>
<property name="maximum">
<number>359</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="spin_saturation">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QSpinBox" name="spin_value">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QSpinBox" name="spin_red">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QSpinBox" name="spin_green">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QSpinBox" name="spin_blue">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QSpinBox" name="spin_alpha">
<property name="maximum">
<number>255</number>
</property>
</widget>
</item>
<item row="7" column="0" colspan="3">
<widget class="Line" name="line_alpha">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="9" column="0" colspan="3">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="color_widgets::HueSlider" name="slide_hue">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>359</number>
</property>
</widget>
</item>
<item row="10" column="1" colspan="2">
<widget class="color_widgets::ColorLineEdit" name="edit_hex">
<property name="font">
<font>
<family>Monospace</family>
</font>
</property>
<property name="showAlpha">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset</set>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>color_widgets::ColorPreview</class>
<extends>QWidget</extends>
<header>qt-color-widgets/color_preview.hpp</header>
<container>1</container>
</customwidget>
<customwidget>
<class>color_widgets::ColorWheel</class>
<extends>QWidget</extends>
<header>qt-color-widgets/color_wheel.hpp</header>
<container>1</container>
<slots>
<signal>colorSelected(QColor)</signal>
<slot>setColor(QColor)</slot>
</slots>
</customwidget>
<customwidget>
<class>color_widgets::GradientSlider</class>
<extends>QSlider</extends>
<header>qt-color-widgets/gradient_slider.hpp</header>
</customwidget>
<customwidget>
<class>color_widgets::HueSlider</class>
<extends>color_widgets::GradientSlider</extends>
<header>qt-color-widgets/hue_slider.hpp</header>
</customwidget>
<customwidget>
<class>color_widgets::ColorLineEdit</class>
<extends>QLineEdit</extends>
<header>qt-color-widgets/color_line_edit.hpp</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>slide_saturation</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_hsv()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>71</y>
</hint>
<hint type="destinationlabel">
<x>537</x>
<y>54</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_value</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_hsv()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>109</y>
</hint>
<hint type="destinationlabel">
<x>537</x>
<y>88</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_red</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_rgb()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>156</y>
</hint>
<hint type="destinationlabel">
<x>557</x>
<y>142</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_green</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_rgb()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>194</y>
</hint>
<hint type="destinationlabel">
<x>538</x>
<y>166</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_blue</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_rgb()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>232</y>
</hint>
<hint type="destinationlabel">
<x>537</x>
<y>205</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_alpha</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>update_widgets()</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>279</y>
</hint>
<hint type="destinationlabel">
<x>531</x>
<y>251</y>
</hint>
</hints>
</connection>
<connection>
<sender>wheel</sender>
<signal>colorSelected(QColor)</signal>
<receiver>ColorDialog</receiver>
<slot>update_widgets()</slot>
<hints>
<hint type="sourcelabel">
<x>175</x>
<y>101</y>
</hint>
<hint type="destinationlabel">
<x>568</x>
<y>106</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_saturation</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_saturation</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>71</y>
</hint>
<hint type="destinationlabel">
<x>480</x>
<y>62</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_saturation</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_saturation</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>461</x>
<y>55</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>71</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_value</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_value</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>109</y>
</hint>
<hint type="destinationlabel">
<x>480</x>
<y>91</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_value</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_value</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>480</x>
<y>91</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>109</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_red</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_red</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>156</y>
</hint>
<hint type="destinationlabel">
<x>482</x>
<y>162</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_red</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_red</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>482</x>
<y>162</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>156</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_green</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_green</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>194</y>
</hint>
<hint type="destinationlabel">
<x>482</x>
<y>200</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_green</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_green</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>482</x>
<y>200</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>194</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_alpha</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_alpha</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>279</y>
</hint>
<hint type="destinationlabel">
<x>482</x>
<y>285</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_alpha</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_alpha</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>482</x>
<y>285</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>279</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_blue</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_blue</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>416</x>
<y>232</y>
</hint>
<hint type="destinationlabel">
<x>482</x>
<y>238</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_blue</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_blue</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>482</x>
<y>238</y>
</hint>
<hint type="destinationlabel">
<x>416</x>
<y>232</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_hue</sender>
<signal>valueChanged(int)</signal>
<receiver>spin_hue</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>405</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>462</x>
<y>26</y>
</hint>
</hints>
</connection>
<connection>
<sender>spin_hue</sender>
<signal>valueChanged(int)</signal>
<receiver>slide_hue</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>448</x>
<y>18</y>
</hint>
<hint type="destinationlabel">
<x>388</x>
<y>24</y>
</hint>
</hints>
</connection>
<connection>
<sender>slide_hue</sender>
<signal>valueChanged(int)</signal>
<receiver>ColorDialog</receiver>
<slot>set_hsv()</slot>
<hints>
<hint type="sourcelabel">
<x>361</x>
<y>17</y>
</hint>
<hint type="destinationlabel">
<x>363</x>
<y>8</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ColorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>250</x>
<y>373</y>
</hint>
<hint type="destinationlabel">
<x>430</x>
<y>267</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ColorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>183</x>
<y>373</y>
</hint>
<hint type="destinationlabel">
<x>294</x>
<y>323</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<signal>colorChanged(QColor)</signal>
<slot>set_rgb()</slot>
<slot>set_hsv()</slot>
<slot>setColor(QColor)</slot>
<slot>update_widgets()</slot>
</slots>
</ui>

View File

@@ -0,0 +1,213 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_line_edit.hpp"
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QApplication>
#include <QPainter>
#include <QStyleOptionFrame>
#include "color_utils.hpp"
#include "qt-color-widgets/color_names.hpp"
namespace color_widgets {
class ColorLineEdit::Private
{
public:
QColor color;
bool show_alpha = false;
bool preview_color = false;
QBrush background;
bool customAlpha()
{
return preview_color && show_alpha && color.alpha() < 255;
}
void setPalette(const QColor& color, ColorLineEdit* parent)
{
if ( preview_color )
{
QPalette pal = parent->palette();
if ( customAlpha() )
pal.setColor(QPalette::Base, Qt::transparent);
else
pal.setColor(QPalette::Base, color.rgb());
pal.setColor(QPalette::Text,
detail::color_lumaF(color) > 0.5 || color.alphaF() < 0.2 ? Qt::black : Qt::white);
parent->setPalette(pal);
}
}
};
ColorLineEdit::ColorLineEdit(QWidget* parent)
: QLineEdit(parent), p(new Private)
{
p->background.setTexture(QPixmap(QStringLiteral(":/color_widgets/alphaback.png")));
setColor(Qt::white);
/// \todo determine if having this connection might be useful
/*connect(this, &QLineEdit::textChanged, [this](const QString& text){
QColor color = p->colorFromString(text);
if ( color.isValid() )
Q_EMIT colorChanged(color);
});*/
connect(this, &QLineEdit::textEdited, [this](const QString& text){
QColor color = color_widgets::colorFromString(text, p->show_alpha);
if ( color.isValid() )
{
p->color = color;
p->setPalette(color, this);
Q_EMIT colorEdited(color);
Q_EMIT colorChanged(color);
}
});
connect(this, &QLineEdit::editingFinished, [this](){
QColor color = color_widgets::colorFromString(text(), p->show_alpha);
if ( color.isValid() )
{
p->color = color;
Q_EMIT colorEditingFinished(color);
Q_EMIT colorChanged(color);
}
else
{
setText(color_widgets::stringFromColor(p->color, p->show_alpha));
Q_EMIT colorEditingFinished(p->color);
Q_EMIT colorChanged(color);
}
p->setPalette(p->color, this);
});
}
ColorLineEdit::~ColorLineEdit()
{
delete p;
}
QColor ColorLineEdit::color() const
{
return p->color;
}
void ColorLineEdit::setColor(const QColor& color)
{
if ( color != p->color )
{
p->color = color;
p->setPalette(p->color, this);
setText(color_widgets::stringFromColor(p->color, p->show_alpha));
Q_EMIT colorChanged(p->color);
}
}
void ColorLineEdit::setShowAlpha(bool showAlpha)
{
if ( p->show_alpha != showAlpha )
{
p->show_alpha = showAlpha;
p->setPalette(p->color, this);
setText(color_widgets::stringFromColor(p->color, p->show_alpha));
Q_EMIT showAlphaChanged(p->show_alpha);
}
}
bool ColorLineEdit::showAlpha() const
{
return p->show_alpha;
}
void ColorLineEdit::dragEnterEvent(QDragEnterEvent *event)
{
if ( isReadOnly() )
return;
if ( event->mimeData()->hasColor() ||
( event->mimeData()->hasText() &&
color_widgets::colorFromString(event->mimeData()->text(), p->show_alpha).isValid() ) )
{
event->acceptProposedAction();
}
}
void ColorLineEdit::dropEvent(QDropEvent *event)
{
if ( isReadOnly() )
return;
if ( event->mimeData()->hasColor() )
{
setColor(event->mimeData()->colorData().value<QColor>());
event->accept();
}
else if ( event->mimeData()->hasText() )
{
QColor col = color_widgets::colorFromString(event->mimeData()->text(), p->show_alpha);
if ( col.isValid() )
{
setColor(col);
event->accept();
}
}
}
bool ColorLineEdit::previewColor() const
{
return p->preview_color;
}
void ColorLineEdit::setPreviewColor(bool previewColor)
{
if ( previewColor != p->preview_color )
{
p->preview_color = previewColor;
if ( p->preview_color )
p->setPalette(p->color, this);
else
setPalette(QApplication::palette());
Q_EMIT previewColorChanged(p->preview_color);
}
}
void ColorLineEdit::paintEvent(QPaintEvent* event)
{
if ( p->customAlpha() )
{
QPainter painter(this);
QStyleOptionFrame panel;
initStyleOption(&panel);
QRect r = style()->subElementRect(QStyle::SE_LineEditContents, &panel, nullptr);
painter.fillRect(r, p->background);
painter.fillRect(r, p->color);
}
QLineEdit::paintEvent(event);
}
} // namespace color_widgets

View File

@@ -0,0 +1,140 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_list_widget.hpp"
#include "qt-color-widgets/color_selector.hpp"
namespace color_widgets {
class ColorListWidget::Private
{
public:
QList<QColor> colors;
QSignalMapper mapper;
ColorWheel::DisplayFlags wheel_flags;
};
ColorListWidget::ColorListWidget(QWidget *parent)
: AbstractWidgetList(parent), p(new Private)
{
connect(this, &AbstractWidgetList::removed, this, &ColorListWidget::handle_removed);
connect(&p->mapper, SIGNAL(mapped(int)), SLOT(color_changed(int)));
p->wheel_flags = ColorWheel::defaultDisplayFlags();
}
ColorListWidget::~ColorListWidget()
{
delete p;
}
QList<QColor> ColorListWidget::colors() const
{
return p->colors;
}
void ColorListWidget::setColors(const QList<QColor> &colors)
{
clear();
p->colors = colors;
for(int i = 0;i < colors.size();i++ )
append_widget(i);
Q_EMIT colorsChanged(colors);
}
void ColorListWidget::swap(int a, int b)
{
ColorSelector* sa = widget_cast<ColorSelector>(a);
ColorSelector* sb = widget_cast<ColorSelector>(b);
if ( sa && sb )
{
QColor ca = sa->color();
sa->setColor(sb->color());
sb->setColor(ca);
Q_EMIT colorsChanged(p->colors);
}
}
void ColorListWidget::append()
{
p->colors.push_back(Qt::black);
append_widget(p->colors.size()-1);
Q_EMIT colorsChanged(p->colors);
Q_EMIT color_added();
}
void ColorListWidget::emit_changed()
{
Q_EMIT colorsChanged(p->colors);
}
void ColorListWidget::handle_removed(int i)
{
p->colors.removeAt(i);
Q_EMIT colorsChanged(p->colors);
}
void ColorListWidget::color_changed(int row)
{
ColorSelector *cs = widget_cast<ColorSelector>(row);
if ( cs )
{
p->colors[row] = cs->color();
Q_EMIT colorsChanged(p->colors);
}
}
void ColorListWidget::setColorAt(int i, QColor const& color)
{
p->colors[i] = color;
ColorSelector* cs = widget_cast<ColorSelector>(i);
cs->setColor(color);
Q_EMIT colorsChanged(p->colors);
}
void ColorListWidget::append_widget(int col)
{
ColorSelector* cbs = new ColorSelector;
cbs->setDisplayMode(ColorPreview::AllAlpha);
cbs->setColor(p->colors[col]);
//connect(cbs,SIGNAL(colorChanged(QColor)),SLOT(emit_changed()));
p->mapper.setMapping(cbs,col);
connect(cbs,SIGNAL(colorChanged(QColor)),&p->mapper,SLOT(map()));
connect(this,&ColorListWidget::wheelFlagsChanged,
cbs,&ColorSelector::setWheelFlags);
appendWidget(cbs);
setRowHeight(count()-1,22);
}
ColorWheel::DisplayFlags ColorListWidget::wheelFlags() const
{
return p->wheel_flags;
}
void ColorListWidget::setWheelFlags(ColorWheel::DisplayFlags flags)
{
if ( p->wheel_flags != flags )
{
p->wheel_flags = flags;
Q_EMIT wheelFlagsChanged(flags);
}
}
} // namespace color_widgets

View File

@@ -0,0 +1,89 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_names.hpp"
#include <QRegularExpression>
static QRegularExpression regex_qcolor (QStringLiteral("^(?:(?:#[[:xdigit:]]{3})|(?:#[[:xdigit:]]{6})|(?:[[:alpha:]]+))$"));
static QRegularExpression regex_func_rgb (QStringLiteral(R"(^rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)$)"));
static QRegularExpression regex_hex_rgba (QStringLiteral("^#[[:xdigit:]]{8}$"));
static QRegularExpression regex_func_rgba (QStringLiteral(R"(^rgba?\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)$)"));
namespace color_widgets {
QString stringFromColor(const QColor& color, bool alpha)
{
if ( !alpha || color.alpha() == 255 )
return color.name();
return color.name()+QStringLiteral("%1").arg(color.alpha(), 2, 16, QChar('0'));
}
QColor colorFromString(const QString& string, bool alpha)
{
QString xs = string.trimmed();
QRegularExpressionMatch match;
match = regex_qcolor.match(xs);
if ( match.hasMatch() )
{
return QColor(xs);
}
match = regex_func_rgb.match(xs);
if ( match.hasMatch() )
{
return QColor(
match.captured(1).toInt(),
match.captured(2).toInt(),
match.captured(3).toInt()
);
}
if ( alpha )
{
match = regex_hex_rgba.match(xs);
if ( match.hasMatch() )
{
return QColor(
xs.mid(1,2).toInt(nullptr,16),
xs.mid(3,2).toInt(nullptr,16),
xs.mid(5,2).toInt(nullptr,16),
xs.mid(7,2).toInt(nullptr,16)
);
}
match = regex_func_rgba.match(xs);
if ( match.hasMatch() )
{
return QColor(
match.captured(1).toInt(),
match.captured(2).toInt(),
match.captured(3).toInt(),
match.captured(4).toInt()
);
}
}
return QColor();
}
} // namespace color_widgets

View File

@@ -0,0 +1,498 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_palette.hpp"
#include <cmath>
#include <QFile>
#include <QTextStream>
#include <QHash>
#include <QPainter>
#include <QFileInfo>
namespace color_widgets {
class ColorPalette::Private
{
public:
QVector<QPair<QColor,QString> > colors;
int columns;
QString name;
QString fileName;
bool dirty;
bool valid_index(int index)
{
return index >= 0 && index < colors.size();
}
};
ColorPalette::ColorPalette(const QVector<QColor>& colors,
const QString& name,
int columns)
: p ( new Private )
{
setName(name);
setColumns(columns);
setColors(colors);
}
ColorPalette::ColorPalette(const QString& name)
: p ( new Private )
{
setName(name);
p->columns = 0;
p->dirty = false;
}
ColorPalette::ColorPalette(const QVector<QPair<QColor,QString> >& colors,
const QString& name,
int columns)
{
setName(name);
setColumns(columns);
setColors(colors);
p->dirty = false;
}
ColorPalette::ColorPalette(const ColorPalette& other)
: QObject(), p ( new Private(*other.p) )
{
}
ColorPalette& ColorPalette::operator=(const ColorPalette& other)
{
*p = *other.p;
emitUpdate();
return *this;
}
ColorPalette::~ColorPalette()
{
delete p;
}
ColorPalette::ColorPalette(ColorPalette&& other)
: QObject(), p ( other.p )
{
other.p = nullptr;
}
ColorPalette& ColorPalette::operator=(ColorPalette&& other)
{
std::swap(p, other.p);
emitUpdate();
return *this;
}
void ColorPalette::emitUpdate()
{
Q_EMIT colorsChanged(p->colors);
Q_EMIT columnsChanged(p->columns);
Q_EMIT nameChanged(p->name);
Q_EMIT fileNameChanged(p->fileName);
Q_EMIT dirtyChanged(p->dirty);
}
QColor ColorPalette::colorAt(int index) const
{
return p->valid_index(index) ? p->colors[index].first : QColor();
}
QString ColorPalette::nameAt(int index) const
{
return p->valid_index(index) ? p->colors[index].second : QString();
}
QVector<QPair<QColor,QString> > ColorPalette::colors() const
{
return p->colors;
}
int ColorPalette::count() const
{
return p->colors.size();
}
int ColorPalette::columns()
{
return p->columns;
}
QString ColorPalette::name() const
{
return p->name;
}
void ColorPalette::loadColorTable(const QVector<QRgb>& color_table)
{
p->colors.clear();
p->colors.reserve(color_table.size());
for ( QRgb c : color_table )
{
QColor color ( c );
color.setAlpha(255);
p->colors.push_back(qMakePair(color,QString()));
}
Q_EMIT colorsChanged(p->colors);
setDirty(true);
}
bool ColorPalette::loadImage(const QImage& image)
{
if ( image.isNull() )
return false;
setColumns(image.width());
p->colors.clear();
p->colors.reserve(image.width()*image.height());
for ( int y = 0; y < image.height(); y++ )
{
for ( int x = 0; x < image.width(); x++ )
{
QColor color ( image.pixel(x, y) );
color.setAlpha(255);
p->colors.push_back(qMakePair(color,QString()));
}
}
Q_EMIT colorsChanged(p->colors);
setDirty(true);
return true;
}
ColorPalette ColorPalette::fromImage(const QImage& image)
{
ColorPalette p;
p.loadImage(image);
return p;
}
bool ColorPalette::load(const QString& name)
{
p->fileName = name;
p->colors.clear();
p->columns = 0;
p->dirty = false;
p->name = QFileInfo(name).baseName();
QFile file(name);
if ( !file.open(QFile::ReadOnly|QFile::Text) )
{
emitUpdate();
return false;
}
QTextStream stream( &file );
if ( stream.readLine() != QLatin1String("GIMP Palette") )
{
emitUpdate();
return false;
}
QString line;
// parse properties
QHash<QString,QString> properties;
while( !stream.atEnd() )
{
line = stream.readLine();
if ( line.isEmpty() )
continue;
if ( line[0] == '#' )
break;
int colon = line.indexOf(':');
if ( colon == -1 )
break;
properties[line.left(colon).toLower()] =
line.right(line.size() - colon - 1).trimmed();
}
/// \todo Store extra properties in the palette object
setName(properties[QStringLiteral("name")]);
setColumns(properties[QStringLiteral("columns")].toInt());
// Skip comments
if ( !stream.atEnd() && line[0] == '#' )
while( !stream.atEnd() )
{
qint64 pos = stream.pos();
line = stream.readLine();
if ( !line.isEmpty() && line[0] != '#' )
{
stream.seek(pos);
break;
}
}
while( !stream.atEnd() )
{
int r = 0, g = 0, b = 0;
stream >> r >> g >> b;
line = stream.readLine().trimmed();
p->colors.push_back(qMakePair(QColor(r, g, b), line));
}
Q_EMIT colorsChanged(p->colors);
setDirty(false);
return true;
}
ColorPalette ColorPalette::fromFile(const QString& name)
{
ColorPalette p;
p.load(name);
return p;
}
bool ColorPalette::save(const QString& filename)
{
setFileName(filename);
return save();
}
bool ColorPalette::save()
{
QString filename = p->fileName;
if ( filename.isEmpty() )
{
filename = unnamed(p->name)+".gpl";
}
QFile file(filename);
if ( !file.open(QFile::Text|QFile::WriteOnly) )
return false;
QTextStream stream(&file);
stream << "GIMP Palette\n";
stream << "Name: " << unnamed(p->name) << '\n';
if ( p->columns )
stream << "Columns: " << p->columns << '\n';
/// \todo Options to add comments
stream << "#\n";
for ( int i = 0; i < p->colors.size(); i++ )
{
stream << qSetFieldWidth(3) << p->colors[i].first.red() << qSetFieldWidth(0) << ' '
<< qSetFieldWidth(3) << p->colors[i].first.green() << qSetFieldWidth(0) << ' '
<< qSetFieldWidth(3) << p->colors[i].first.blue() << qSetFieldWidth(0) << '\t'
<< unnamed(p->colors[i].second) << '\n';
}
if ( !file.error() )
{
setDirty(false);
return true;
}
return false;
}
QString ColorPalette::fileName() const
{
return p->fileName;
}
void ColorPalette::setColumns(int columns)
{
if ( columns <= 0 )
columns = 0;
if ( columns != p->columns )
{
setDirty(true);
Q_EMIT columnsChanged( p->columns = columns );
}
}
void ColorPalette::setColors(const QVector<QColor>& colors)
{
p->colors.clear();
Q_FOREACH(const QColor& col, colors)
p->colors.push_back(qMakePair(col,QString()));
setDirty(true);
Q_EMIT colorsChanged(p->colors);
}
void ColorPalette::setColors(const QVector<QPair<QColor,QString> >& colors)
{
p->colors = colors;
setDirty(true);
Q_EMIT colorsChanged(p->colors);
}
void ColorPalette::setColorAt(int index, const QColor& color)
{
if ( !p->valid_index(index) )
return;
p->colors[index].first = color;
setDirty(true);
Q_EMIT colorChanged(index);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::setColorAt(int index, const QColor& color, const QString& name)
{
if ( !p->valid_index(index) )
return;
p->colors[index].first = color;
p->colors[index].second = name;
setDirty(true);
Q_EMIT colorChanged(index);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::setNameAt(int index, const QString& name)
{
if ( !p->valid_index(index) )
return;
p->colors[index].second = name;
setDirty(true);
Q_EMIT colorChanged(index);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::appendColor(const QColor& color, const QString& name)
{
p->colors.push_back(qMakePair(color,name));
setDirty(true);
Q_EMIT colorAdded(p->colors.size()-1);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::insertColor(int index, const QColor& color, const QString& name)
{
if ( index < 0 || index > p->colors.size() )
return;
p->colors.insert(index, qMakePair(color, name));
setDirty(true);
Q_EMIT colorAdded(index);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::eraseColor(int index)
{
if ( !p->valid_index(index) )
return;
p->colors.remove(index);
setDirty(true);
Q_EMIT colorRemoved(index);
Q_EMIT colorsUpdated(p->colors);
}
void ColorPalette::setName(const QString& name)
{
setDirty(true);
p->name = name;
}
void ColorPalette::setFileName(const QString& name)
{
setDirty(true);
p->fileName = name;
}
QString ColorPalette::unnamed(const QString& name) const
{
return name.isEmpty() ? tr("Unnamed") : name;
}
QPixmap ColorPalette::preview(const QSize& size, const QColor& background) const
{
if ( !size.isValid() || p->colors.empty() )
return QPixmap();
QPixmap out( size );
out.fill(background);
QPainter painter(&out);
int count = p->colors.size();
int columns = p->columns;
if ( !columns )
columns = std::ceil( std::sqrt( count * float(size.width()) / size.height() ) );
int rows = std::ceil( float(count) / columns );
QSizeF color_size(float(size.width()) / columns, float(size.height()) / rows);
for ( int y = 0, i = 0; y < rows && i < count; y++ )
{
for ( int x = 0; x < columns && i < count; x++, i++ )
{
painter.fillRect(QRectF(x*color_size.width(), y*color_size.height(),
color_size.width(), color_size.height()),
p->colors[i].first
);
}
}
return out;
}
bool ColorPalette::dirty() const
{
return p->dirty;
}
void ColorPalette::setDirty(bool dirty)
{
if ( dirty != p->dirty )
Q_EMIT dirtyChanged( p->dirty = dirty );
}
QVector<QColor> ColorPalette::onlyColors() const
{
QVector<QColor> out;
out.reserve(p->colors.size());
for ( int i = 0; i < p->colors.size(); i++ )
out.push_back(p->colors[i].first);
return out;
}
QVector<QRgb> ColorPalette::colorTable() const
{
QVector<QRgb> out;
out.reserve(p->colors.size());
for ( const auto& color_pair : p->colors )
out.push_back(color_pair.first.rgba());
return out;
}
ColorPalette ColorPalette::fromColorTable(const QVector<QRgb>& table)
{
ColorPalette palette;
palette.loadColorTable(table);
return palette;
}
} // namespace color_widgets

View File

@@ -0,0 +1,329 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_palette_model.hpp"
#include <QDir>
#include <QList>
#include <QRegularExpression>
namespace color_widgets {
class ColorPaletteModel::Private
{
public:
/// \todo Keep sorted by name (?)
QList<ColorPalette> palettes;
QSize icon_size;
QStringList search_paths;
QString save_path;
Private()
: icon_size(32, 32)
{}
bool acceptable(const QModelIndex& index) const
{
return acceptable(index.row());
}
bool acceptable(int row) const
{
return row >= 0 && row <= palettes.count();
}
QList<ColorPalette>::iterator find(const QString& name)
{
return std::find_if(palettes.begin(), palettes.end(),
[&name](const ColorPalette& palette) {
return palette.name() == name;
});
}
bool attemptSave(ColorPalette& palette, const QString& filename)
{
if ( filename.isEmpty() )
return false;
return palette.save(filename);
}
void fixUnnamed(ColorPalette& palette)
{
if ( palette.name().isEmpty() )
palette.setName(ColorPaletteModel::tr("Unnamed"));
}
bool save(ColorPalette& palette, const QString& suggested_filename = QString())
{
// Attempt to save with the existing file names
if ( !suggested_filename.isEmpty() && attemptSave(palette, suggested_filename) )
return true;
if ( attemptSave(palette, palette.fileName()) )
return true;
// Set up the save directory
QDir save_dir(save_path);
if ( !save_dir.exists() && !QDir().mkdir(save_path) )
return false;
// Attempt to save as (Name).gpl
QString filename = palette.name()+".gpl";
if ( !save_dir.exists(filename) &&
attemptSave(palette, save_dir.absoluteFilePath(filename)) )
return true;
// Get all of the files matching the pattern *.gpl
save_dir.setNameFilters(QStringList() << QStringLiteral("*.gpl"));
save_dir.setFilter(QDir::Files);
QStringList existing_files = save_dir.entryList();
// For all the files that match (Name)(Number).gpl, find the maximum (Number)
QRegularExpression name_regex(QRegularExpression::escape(palette.name())+"([0-9]+)\\.gpl");
int max = 0;
for ( const auto& existing_file : existing_files )
{
QRegularExpressionMatch match = name_regex.match(existing_file);
if ( match.hasMatch() )
{
int num = match.captured(1).toInt();
if ( num > max )
max = num;
}
}
return attemptSave(palette,
save_dir.absoluteFilePath(QStringLiteral("%1%2.gpl").arg(palette.name()).arg(max+1))
);
}
};
ColorPaletteModel::ColorPaletteModel()
: p ( new Private )
{}
ColorPaletteModel::~ColorPaletteModel()
{
delete p;
}
int ColorPaletteModel::rowCount(const QModelIndex &) const
{
return count();
}
QVariant ColorPaletteModel::data(const QModelIndex &index, int role) const
{
if ( !p->acceptable(index) )
return QVariant();
const ColorPalette& palette = p->palettes[index.row()];
switch( role )
{
case Qt::DisplayRole:
return palette.name();
case Qt::DecorationRole:
return palette.preview(p->icon_size);
case Qt::ToolTipRole:
return tr("%1 (%2 colors)").arg(palette.name()).arg(palette.count());
}
return QVariant();
}
bool ColorPaletteModel::removeRows(int row, int count, const QModelIndex & parent)
{
Q_UNUSED(parent)
if ( !p->acceptable(row) || count <= 0 )
return false;
auto begin = p->palettes.begin() + row;
auto end = row + count >= p->palettes.size() ? p->palettes.end() : begin + count;
for ( auto it = begin; it != end; ++it )
{
if ( !it->fileName().isEmpty() )
{
QFileInfo file(it->fileName());
if ( file.isWritable() && file.isFile() )
QFile::remove(it->fileName());
}
}
p->palettes.erase(begin, end);
return true;
}
QSize ColorPaletteModel::iconSize() const
{
return p->icon_size;
}
void ColorPaletteModel::setIconSize(const QSize& iconSize)
{
if ( p->icon_size != iconSize )
Q_EMIT iconSizeChanged( p->icon_size = iconSize );
}
QString ColorPaletteModel::savePath() const
{
return p->save_path;
}
QStringList ColorPaletteModel::searchPaths() const
{
return p->search_paths;
}
void ColorPaletteModel::setSavePath(const QString& savePath)
{
if ( p->save_path != savePath )
Q_EMIT savePathChanged( p->save_path = savePath );
}
void ColorPaletteModel::setSearchPaths(const QStringList& searchPaths)
{
if ( p->search_paths != searchPaths )
Q_EMIT searchPathsChanged( p->search_paths = searchPaths );
}
void ColorPaletteModel::addSearchPath(const QString& path)
{
/// \todo Should compare canonical paths
/// and these checks should also be made in setSearchPaths
if ( !p->search_paths.contains(path) )
{
p->search_paths.push_back(path);
Q_EMIT searchPathsChanged( p->search_paths );
}
}
void ColorPaletteModel::load()
{
beginResetModel();
p->palettes.clear();
QStringList filters;
filters << QStringLiteral("*.gpl");
for ( const QString& directory_name : p->search_paths )
{
QDir directory(directory_name);
directory.setNameFilters(filters);
directory.setFilter(QDir::Files|QDir::Readable);
directory.setSorting(QDir::Name);
for ( const QFileInfo& file : directory.entryInfoList() )
{
ColorPalette palette;
if ( palette.load(file.absoluteFilePath()) )
{
p->palettes.push_back(palette);
}
}
}
endResetModel();
}
bool ColorPaletteModel::hasPalette(const QString& name) const
{
return p->find(name) != p->palettes.end();
}
int ColorPaletteModel::count() const
{
return p->palettes.size();
}
const ColorPalette& ColorPaletteModel::palette(const QString& name) const
{
return *p->find(name);
}
const ColorPalette& ColorPaletteModel::palette(int index) const
{
return p->palettes[index];
}
bool ColorPaletteModel::updatePalette(int index, const ColorPalette& palette, bool save)
{
if ( !p->acceptable(index) )
return false;
// Store the old file name
QString filename = p->palettes[index].fileName();
// Update the palette
ColorPalette& local_palette = p->palettes[index] = palette;
p->fixUnnamed(local_palette);
if ( save )
return p->save(local_palette, filename);
return true;
}
bool ColorPaletteModel::removePalette(int index, bool remove_file)
{
if ( !p->acceptable(index) )
return false;
QString file_name = p->palettes[index].fileName();
beginRemoveRows(QModelIndex(), index, index);
p->palettes.removeAt(index);
endRemoveRows();
if ( !file_name.isEmpty() && remove_file )
{
QFileInfo file(file_name);
if ( file.isWritable() && file.isFile() )
return QFile::remove(file_name);
return false;
}
return true;
}
bool ColorPaletteModel::addPalette(const ColorPalette& palette, bool save)
{
beginInsertRows(QModelIndex(), p->palettes.size(), p->palettes.size());
p->palettes.push_back(palette);
p->fixUnnamed(p->palettes.back());
endInsertRows();
if ( save )
return p->save(p->palettes.back());
return true;
}
int ColorPaletteModel::indexFromFile(const QString& filename) const
{
QString canonical = QFileInfo(filename).canonicalFilePath();
int i = 0;
for ( const auto& pal : p->palettes )
{
if ( !pal.fileName().isEmpty() &&
QFileInfo(pal.fileName()).canonicalFilePath() == canonical )
return i;
i++;
}
return -1;
}
} // namespace color_widgets

View File

@@ -0,0 +1,415 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_palette_widget.hpp"
#include "ui_color_palette_widget.h"
#include "qt-color-widgets/color_dialog.hpp"
#include <QInputDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QImageReader>
namespace color_widgets {
class ColorPaletteWidget::Private : public Ui::ColorPaletteWidget
{
public:
ColorPaletteModel* model = nullptr;
bool read_only = false;
bool hasSelectedPalette()
{
return model && palette_list->currentIndex() != -1;
}
const ColorPalette& selectedPalette()
{
return model->palette(palette_list->currentIndex());
}
void addPalette(ColorPalette& palette)
{
bool save = false;
// Save palettes in the savePath
/// \todo This currently breaks opening the right directory
/// ie: the one containing the original file.
if ( !palette.fileName().isEmpty() )
{
QFileInfo file(palette.fileName());
if ( file.dir().canonicalPath() != QDir(model->savePath()).canonicalPath() )
{
palette.setFileName(QString());
save = true;
}
}
model->addPalette(palette, save);
palette_list->setCurrentIndex(model->count()-1);
}
bool openImage(const QString& file)
{
QImage image(file);
if ( !image.isNull() )
{
ColorPalette palette;
palette.loadImage(image);
palette.setName(QFileInfo(file).baseName());
palette.setFileName(file+".gpl");
addPalette(palette);
return true;
}
return false;
}
bool openGpl(const QString& file)
{
int existing = model->indexFromFile(file);
if ( existing != -1 )
{
palette_list->setCurrentIndex(existing);
return true;
}
ColorPalette palette;
if ( palette.load(file) )
{
addPalette(palette);
return true;
}
return false;
}
bool openPalette(const QString& file, int type)
{
if ( type == 1 )
return openImage(file);
return openGpl(file);
}
};
ColorPaletteWidget::ColorPaletteWidget(QWidget* parent)
: QWidget(parent), p(new Private)
{
p->setupUi(this);
// Connext Swatch signals
connect(p->swatch, &Swatch::colorSizeChanged, this, &ColorPaletteWidget::colorSizeChanged);
connect(p->swatch, &Swatch::colorSizePolicyChanged, this, &ColorPaletteWidget::colorSizePolicyChanged);
connect(p->swatch, &Swatch::forcedRowsChanged, this, &ColorPaletteWidget::forcedRowsChanged);
connect(p->swatch, &Swatch::forcedColumnsChanged, this, &ColorPaletteWidget::forcedColumnsChanged);
connect(p->swatch, &Swatch::colorSelected,
this, (void (ColorPaletteWidget::*)(const QColor&)) &ColorPaletteWidget::currentColorChanged);
connect(p->swatch, &Swatch::selectedChanged,
this, (void (ColorPaletteWidget::*)(int)) &ColorPaletteWidget::currentColorChanged);
connect(p->swatch, &Swatch::borderChanged, this, &ColorPaletteWidget::borderChanged);
connect(&p->swatch->palette(), &ColorPalette::dirtyChanged, p->button_palette_save, &QWidget::setEnabled);
connect(&p->swatch->palette(), &ColorPalette::dirtyChanged, p->button_palette_revert, &QWidget::setEnabled);
connect(p->palette_list, (void (QComboBox::*)(int))&QComboBox::currentIndexChanged,
this, &ColorPaletteWidget::currentRowChanged);
// Buttons changing the colors in the current palette
connect(p->button_color_add, &QAbstractButton::clicked, [this](){
if ( !p->read_only && p->hasSelectedPalette() )
{
ColorDialog dialog(this);
dialog.setAlphaEnabled(false);
if ( p->swatch->selected() != -1 )
dialog.setColor(p->swatch->selectedColor());
if ( dialog.exec() )
{
p->swatch->palette().appendColor(dialog.color());
p->swatch->setSelected(p->swatch->palette().count()-1);
}
}
});
connect(p->button_color_remove, &QAbstractButton::clicked, p->swatch, &Swatch::removeSelected);
// Buttons modifying the current palette file
connect(p->button_palette_delete, &QAbstractButton::clicked, [this]() {
if ( !p->read_only && p->hasSelectedPalette() )
p->model->removePalette(p->palette_list->currentIndex());
});
connect(p->button_palette_save, &QAbstractButton::clicked, [this](){
if ( !p->read_only && p->hasSelectedPalette() && p->swatch->palette().dirty() )
if ( p->model->updatePalette( p->palette_list->currentIndex(), p->swatch->palette() ) )
{
p->swatch->palette().setDirty(false);
}
/// \todo else ask for a file name (?)
});
connect(p->button_palette_revert, &QAbstractButton::clicked, [this](){
if ( p->hasSelectedPalette() )
{
p->swatch->setPalette(p->selectedPalette());
}
});
// Buttons creating new palettes
connect(p->button_palette_duplicate, &QAbstractButton::clicked, [this](){
if ( p->hasSelectedPalette() )
{
ColorPalette new_palette = p->selectedPalette();
new_palette.setFileName(QString());
bool ok = false;
QString name = QInputDialog::getText(this, tr("New Palette"),
tr("Name"), QLineEdit::Normal, new_palette.name(), &ok);
if ( ok )
{
new_palette.setName(name);
p->model->addPalette(new_palette);
p->palette_list->setCurrentIndex(p->model->count()-1);
}
}
});
/// \todo Show a dialog that asks for the number of columns (?)
connect(p->button_palette_new, &QAbstractButton::clicked, [this](){
if ( p->hasSelectedPalette() )
{
bool ok = false;
QString name = QInputDialog::getText(this, tr("New Palette"),
tr("Name"), QLineEdit::Normal, QString(), &ok);
if ( ok )
{
ColorPalette new_palette(name);
p->model->addPalette(new_palette);
p->palette_list->setCurrentIndex(p->model->count()-1);
}
}
});
QString image_formats;
Q_FOREACH(QByteArray ba, QImageReader::supportedImageFormats())
image_formats += " *."+QString(ba);
connect(p->button_palette_open, &QAbstractButton::clicked, [this, image_formats](){
if ( p->model )
{
QString default_dir;
if ( p->hasSelectedPalette() )
{
const ColorPalette& palette = p->selectedPalette();
if ( !palette.fileName().isEmpty() )
default_dir = QFileInfo(palette.fileName()).dir().path();
}
QStringList file_formats = QStringList()
<< tr("GIMP Palettes (*.gpl)")
<< tr("Palette Image (%1)").arg(image_formats)
<< tr("All Files (*)");
QFileDialog open_dialog(this, tr("Open Palette"), default_dir);
open_dialog.setFileMode(QFileDialog::ExistingFile);
open_dialog.setAcceptMode(QFileDialog::AcceptOpen);
open_dialog.setNameFilters(file_formats);
if ( !open_dialog.exec() )
return;
int type = file_formats.indexOf(open_dialog.selectedNameFilter());
QString file_name = open_dialog.selectedFiles()[0];
if ( !p->openPalette( file_name, type ) )
{
QMessageBox::warning(this, tr("Open Palette"),
tr("Failed to load the palette file\n%1").arg(file_name));
}
}
});
}
ColorPaletteWidget::~ColorPaletteWidget() = default;
ColorPaletteModel* ColorPaletteWidget::model() const
{
return p->model;
}
const ColorPalette& ColorPaletteWidget::currentPalette() const
{
return p->swatch->palette();
}
QSize ColorPaletteWidget::colorSize() const
{
return p->swatch->colorSize();
}
Swatch::ColorSizePolicy ColorPaletteWidget::colorSizePolicy() const
{
return p->swatch->colorSizePolicy();
}
QPen ColorPaletteWidget::border() const
{
return p->swatch->border();
}
int ColorPaletteWidget::forcedRows() const
{
return p->swatch->forcedRows();
}
int ColorPaletteWidget::forcedColumns() const
{
return p->swatch->forcedColumns();
}
bool ColorPaletteWidget::readOnly() const
{
return p->read_only;
}
QColor ColorPaletteWidget::currentColor() const
{
return p->swatch->selectedColor();
}
void ColorPaletteWidget::setModel(ColorPaletteModel* model)
{
if ( model == p->model )
return;
p->model = model;
p->swatch->setPalette(ColorPalette());
p->palette_list->setModel(model);
}
void ColorPaletteWidget::setColorSize(const QSize& colorSize)
{
p->swatch->setColorSize(colorSize);
}
void ColorPaletteWidget::setColorSizePolicy(Swatch::ColorSizePolicy colorSizePolicy)
{
p->swatch->setColorSizePolicy(colorSizePolicy);
}
void ColorPaletteWidget::setBorder(const QPen& border)
{
p->swatch->setBorder(border);
}
void ColorPaletteWidget::setForcedRows(int forcedRows)
{
p->swatch->setForcedRows(forcedRows);
}
void ColorPaletteWidget::setForcedColumns(int forcedColumns)
{
p->swatch->setForcedColumns(forcedColumns);
}
void ColorPaletteWidget::setReadOnly(bool readOnly)
{
if ( readOnly == p->read_only )
return;
p->swatch->setReadOnly(readOnly);
p->group_edit_list->setVisible(!readOnly);
p->group_edit_palette->setVisible(!readOnly);
Q_EMIT readOnlyChanged(p->read_only = readOnly);
}
bool ColorPaletteWidget::setCurrentColor(const QColor& color)
{
const auto& palette = p->swatch->palette();
for ( int i = 0; i < palette.count(); i++ )
{
if ( palette.colorAt(i) == color )
{
p->swatch->setSelected(i);
return true;
}
}
p->swatch->clearSelection();
return false;
}
bool ColorPaletteWidget::setCurrentColor(const QString& name)
{
const auto& palette = p->swatch->palette();
for ( int i = 0; i < palette.count(); i++ )
{
if ( palette.nameAt(i) == name )
{
p->swatch->setSelected(i);
return true;
}
}
p->swatch->clearSelection();
return false;
}
bool ColorPaletteWidget::setCurrentColor(int index)
{
const auto& palette = p->swatch->palette();
if ( index >= 0 && index < palette.count() )
{
p->swatch->setSelected(index);
return true;
}
p->swatch->clearSelection();
return false;
}
void ColorPaletteWidget::on_palette_list_currentIndexChanged(int index)
{
if ( !p->model )
p->swatch->setPalette(ColorPalette());
else
p->swatch->setPalette(p->model->palette(index));
p->swatch->palette().setDirty(false);
}
void ColorPaletteWidget::on_swatch_doubleClicked(int index)
{
if ( !p->read_only )
{
ColorDialog dialog(this);
dialog.setAlphaEnabled(false);
dialog.setColor(p->swatch->palette().colorAt(index));
if ( dialog.exec() )
p->swatch->palette().setColorAt(index, dialog.color());
}
}
int ColorPaletteWidget::currentRow() const
{
return p->palette_list->currentIndex();
}
void ColorPaletteWidget::setCurrentRow(int row)
{
p->palette_list->setCurrentIndex(row);
}
void ColorPaletteWidget::clearCurrentColor()
{
p->swatch->clearSelection();
}
} // namespace color_widgets

View File

@@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>color_widgets::ColorPaletteWidget</class>
<widget class="QWidget" name="color_widgets::ColorPaletteWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>227</width>
<height>186</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QComboBox" name="palette_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="group_edit_list" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="button_palette_open">
<property name="toolTip">
<string>Open a new palette from file</string>
</property>
<property name="icon">
<iconset theme="document-open">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="button_palette_new">
<property name="toolTip">
<string>Create a new palette</string>
</property>
<property name="icon">
<iconset theme="document-new">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="button_palette_duplicate">
<property name="toolTip">
<string>Duplicate the current palette</string>
</property>
<property name="icon">
<iconset theme="edit-copy">
<normaloff/>
</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="color_widgets::Swatch" name="swatch"/>
</item>
<item>
<widget class="QWidget" name="group_edit_palette" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="button_palette_delete">
<property name="toolTip">
<string>Delete the current palette</string>
</property>
<property name="icon">
<iconset theme="document-close">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="button_palette_revert">
<property name="toolTip">
<string>Revert changes to the current palette</string>
</property>
<property name="icon">
<iconset theme="document-revert">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="button_palette_save">
<property name="toolTip">
<string>Save changes to the current palette</string>
</property>
<property name="icon">
<iconset theme="document-save">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="button_color_add">
<property name="toolTip">
<string>Add a color to the palette</string>
</property>
<property name="icon">
<iconset theme="list-add">
<normaloff/>
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="button_color_remove">
<property name="toolTip">
<string>Remove the selected color from the palette</string>
</property>
<property name="icon">
<iconset theme="list-remove">
<normaloff/>
</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>color_widgets::Swatch</class>
<extends>QWidget</extends>
<header>qt-color-widgets/swatch.hpp</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,182 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
* \copyright Copyright (C) 2014 Calle Laakkonen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_preview.hpp"
#include <QStylePainter>
#include <QStyleOptionFrame>
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
namespace color_widgets {
class ColorPreview::Private
{
public:
QColor col; ///< color to be viewed
QColor comparison; ///< comparison color
QBrush back;///< Background brush, visible on a transparent color
DisplayMode display_mode; ///< How the color(s) are to be shown
Private() : col(Qt::red), back(Qt::darkGray, Qt::DiagCrossPattern), display_mode(NoAlpha)
{}
};
ColorPreview::ColorPreview(QWidget *parent) :
QWidget(parent), p(new Private)
{
p->back.setTexture(QPixmap(QStringLiteral(":/color_widgets/alphaback.png")));
}
ColorPreview::~ColorPreview()
{
delete p;
}
void ColorPreview::setBackground(const QBrush &bk)
{
p->back = bk;
update();
}
QBrush ColorPreview::background() const
{
return p->back;
}
ColorPreview::DisplayMode ColorPreview::displayMode() const
{
return p->display_mode;
}
void ColorPreview::setDisplayMode(DisplayMode m)
{
p->display_mode = m;
update();
}
QColor ColorPreview::color() const
{
return p->col;
}
QColor ColorPreview::comparisonColor() const
{
return p->comparison;
}
QSize ColorPreview::sizeHint() const
{
return QSize(24,24);
}
void ColorPreview::paint(QPainter &painter, QRect rect) const
{
QColor c1, c2;
switch(p->display_mode) {
case DisplayMode::NoAlpha:
c1 = c2 = p->col.rgb();
break;
case DisplayMode::AllAlpha:
c1 = c2 = p->col;
break;
case DisplayMode::SplitAlpha:
c1 = p->col.rgb();
c2 = p->col;
break;
case DisplayMode::SplitColor:
c1 = p->comparison;
c2 = p->col;
break;
}
QStyleOptionFrame panel;
panel.initFrom(this);
panel.lineWidth = 2;
panel.midLineWidth = 0;
panel.state |= QStyle::State_Sunken;
style()->drawPrimitive(QStyle::PE_Frame, &panel, &painter, this);
QRect r = style()->subElementRect(QStyle::SE_FrameContents, &panel, this);
painter.setClipRect(r);
if ( c1.alpha() < 255 || c2.alpha() < 255 )
painter.fillRect(0, 0, rect.width(), rect.height(), p->back);
int w = rect.width() / 2;
int h = rect.height();
painter.fillRect(0, 0, w, h, c1);
painter.fillRect(w, 0, w, h, c2);
}
void ColorPreview::setColor(const QColor &c)
{
p->col = c;
update();
Q_EMIT colorChanged(c);
}
void ColorPreview::setComparisonColor(const QColor &c)
{
p->comparison = c;
update();
}
void ColorPreview::paintEvent(QPaintEvent *)
{
QStylePainter painter(this);
paint(painter, geometry());
}
void ColorPreview::resizeEvent(QResizeEvent *)
{
update();
}
void ColorPreview::mouseReleaseEvent(QMouseEvent * ev)
{
if ( QRect(QPoint(0,0),size()).contains(ev->pos()) )
Q_EMIT clicked();
}
void ColorPreview::mouseMoveEvent(QMouseEvent *ev)
{
if ( ev->buttons() &Qt::LeftButton && !QRect(QPoint(0,0),size()).contains(ev->pos()) )
{
QMimeData *data = new QMimeData;
data->setColorData(p->col);
QDrag* drag = new QDrag(this);
drag->setMimeData(data);
QPixmap preview(24,24);
preview.fill(p->col);
drag->setPixmap(preview);
drag->exec();
}
}
} // namespace color_widgets

View File

@@ -0,0 +1,158 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_selector.hpp"
#include "qt-color-widgets/color_dialog.hpp"
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QMimeData>
namespace color_widgets {
class ColorSelector::Private
{
public:
UpdateMode update_mode;
ColorDialog *dialog;
QColor old_color;
Private(QWidget *widget) : dialog(new ColorDialog(widget))
{
dialog->setButtonMode(ColorDialog::OkCancel);
}
};
ColorSelector::ColorSelector(QWidget *parent) :
ColorPreview(parent), p(new Private(this))
{
setUpdateMode(Continuous);
p->old_color = color();
connect(this,&ColorPreview::clicked,this,&ColorSelector::showDialog);
connect(this,SIGNAL(colorChanged(QColor)),this,SLOT(update_old_color(QColor)));
connect(p->dialog,&QDialog::rejected,this,&ColorSelector::reject_dialog);
connect(p->dialog,&ColorDialog::colorSelected, this, &ColorSelector::accept_dialog);
connect(p->dialog,&ColorDialog::wheelFlagsChanged,
this, &ColorSelector::wheelFlagsChanged);
setAcceptDrops(true);
}
ColorSelector::~ColorSelector()
{
delete p;
}
ColorSelector::UpdateMode ColorSelector::updateMode() const
{
return p->update_mode;
}
void ColorSelector::setUpdateMode(UpdateMode m)
{
p->update_mode = m;
}
Qt::WindowModality ColorSelector::dialogModality() const
{
return p->dialog->windowModality();
}
void ColorSelector::setDialogModality(Qt::WindowModality m)
{
p->dialog->setWindowModality(m);
}
ColorWheel::DisplayFlags ColorSelector::wheelFlags() const
{
return p->dialog->wheelFlags();
}
void ColorSelector::showDialog()
{
p->old_color = color();
p->dialog->setColor(color());
connect_dialog();
p->dialog->show();
}
void ColorSelector::setWheelFlags(ColorWheel::DisplayFlags flags)
{
p->dialog->setWheelFlags(flags);
}
void ColorSelector::connect_dialog()
{
if (p->update_mode == Continuous)
connect(p->dialog, SIGNAL(colorChanged(QColor)), this, SLOT(setColor(QColor)), Qt::UniqueConnection);
else
disconnect_dialog();
}
void ColorSelector::disconnect_dialog()
{
disconnect(p->dialog, SIGNAL(colorChanged(QColor)), this, SLOT(setColor(QColor)));
}
void ColorSelector::accept_dialog()
{
setColor(p->dialog->color());
p->old_color = color();
}
void ColorSelector::reject_dialog()
{
setColor(p->old_color);
}
void ColorSelector::update_old_color(const QColor &c)
{
if (!p->dialog->isVisible())
p->old_color = c;
}
void ColorSelector::dragEnterEvent(QDragEnterEvent *event)
{
if ( event->mimeData()->hasColor() ||
( event->mimeData()->hasText() && QColor(event->mimeData()->text()).isValid() ) )
event->acceptProposedAction();
}
void ColorSelector::dropEvent(QDropEvent *event)
{
if ( event->mimeData()->hasColor() )
{
setColor(event->mimeData()->colorData().value<QColor>());
event->accept();
}
else if ( event->mimeData()->hasText() )
{
QColor col(event->mimeData()->text());
if ( col.isValid() )
{
setColor(col);
event->accept();
}
}
}
} // namespace color_widgets

View File

@@ -0,0 +1,83 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "color_utils.hpp"
namespace color_widgets {
namespace detail {
QColor color_from_lch(qreal hue, qreal chroma, qreal luma, qreal alpha )
{
qreal h1 = hue*6;
qreal x = chroma*(1-qAbs(std::fmod(h1,2)-1));
QColor col;
if ( h1 >= 0 && h1 < 1 )
col = QColor::fromRgbF(chroma,x,0);
else if ( h1 < 2 )
col = QColor::fromRgbF(x,chroma,0);
else if ( h1 < 3 )
col = QColor::fromRgbF(0,chroma,x);
else if ( h1 < 4 )
col = QColor::fromRgbF(0,x,chroma);
else if ( h1 < 5 )
col = QColor::fromRgbF(x,0,chroma);
else if ( h1 < 6 )
col = QColor::fromRgbF(chroma,0,x);
qreal m = luma - color_lumaF(col);
return QColor::fromRgbF(
qBound(0.0,col.redF()+m,1.0),
qBound(0.0,col.greenF()+m,1.0),
qBound(0.0,col.blueF()+m,1.0),
alpha);
}
QColor color_from_hsl(qreal hue, qreal sat, qreal lig, qreal alpha )
{
qreal chroma = (1 - qAbs(2*lig-1))*sat;
qreal h1 = hue*6;
qreal x = chroma*(1-qAbs(std::fmod(h1,2)-1));
QColor col;
if ( h1 >= 0 && h1 < 1 )
col = QColor::fromRgbF(chroma,x,0);
else if ( h1 < 2 )
col = QColor::fromRgbF(x,chroma,0);
else if ( h1 < 3 )
col = QColor::fromRgbF(0,chroma,x);
else if ( h1 < 4 )
col = QColor::fromRgbF(0,x,chroma);
else if ( h1 < 5 )
col = QColor::fromRgbF(x,0,chroma);
else if ( h1 < 6 )
col = QColor::fromRgbF(chroma,0,x);
qreal m = lig-chroma/2;
return QColor::fromRgbF(
qBound(0.0,col.redF()+m,1.0),
qBound(0.0,col.greenF()+m,1.0),
qBound(0.0,col.blueF()+m,1.0),
alpha);
}
} // namespace detail
} // namespace color_widgets

View File

@@ -0,0 +1,70 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QColor>
#include <qmath.h>
namespace color_widgets {
namespace detail {
inline qreal color_chromaF(const QColor& c)
{
qreal max = qMax(c.redF(), qMax(c.greenF(), c.blueF()));
qreal min = qMin(c.redF(), qMin(c.greenF(), c.blueF()));
return max - min;
}
inline qreal color_lumaF(const QColor& c)
{
return 0.30 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF();
}
QColor color_from_lch(qreal hue, qreal chroma, qreal luma, qreal alpha = 1 );
inline QColor rainbow_lch(qreal hue)
{
return color_from_lch(hue,1,1);
}
inline QColor rainbow_hsv(qreal hue)
{
return QColor::fromHsvF(hue,1,1);
}
inline qreal color_lightnessF(const QColor& c)
{
return ( qMax(c.redF(),qMax(c.greenF(),c.blueF())) +
qMin(c.redF(),qMin(c.greenF(),c.blueF())) ) / 2;
}
inline qreal color_HSL_saturationF(const QColor& col)
{
qreal c = color_chromaF(col);
qreal l = color_lightnessF(col);
if ( qFuzzyCompare(l+1,1) || qFuzzyCompare(l+1,2) )
return 0;
return c / (1-qAbs(2*l-1));
}
QColor color_from_hsl(qreal hue, qreal sat, qreal lig, qreal alpha = 1 );
} // namespace detail
} // namespace color_widgets

View File

@@ -0,0 +1,561 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/color_wheel.hpp"
#include <cmath>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QLineF>
#include <QDragEnterEvent>
#include <QMimeData>
#include "color_utils.hpp"
namespace color_widgets {
enum MouseStatus
{
Nothing,
DragCircle,
DragSquare
};
static const ColorWheel::DisplayFlags hard_default_flags = ColorWheel::SHAPE_TRIANGLE|ColorWheel::ANGLE_ROTATING|ColorWheel::COLOR_HSV;
static ColorWheel::DisplayFlags default_flags = hard_default_flags;
static const double selector_radius = 6;
class ColorWheel::Private
{
private:
ColorWheel * const w;
public:
qreal hue, sat, val;
unsigned int wheel_width;
MouseStatus mouse_status;
QPixmap hue_ring;
QImage inner_selector;
DisplayFlags display_flags;
QColor (*color_from)(qreal,qreal,qreal,qreal);
QColor (*rainbow_from_hue)(qreal);
int max_size = 128;
Private(ColorWheel *widget)
: w(widget), hue(0), sat(0), val(0),
wheel_width(20), mouse_status(Nothing),
display_flags(FLAGS_DEFAULT),
color_from(&QColor::fromHsvF), rainbow_from_hue(&detail::rainbow_hsv)
{ }
/// Calculate outer wheel radius from idget center
qreal outer_radius() const
{
return qMin(w->geometry().width(), w->geometry().height())/2;
}
/// Calculate inner wheel radius from idget center
qreal inner_radius() const
{
return outer_radius()-wheel_width;
}
/// Calculate the edge length of the inner square
qreal square_size() const
{
return inner_radius()*qSqrt(2);
}
/// Calculate the height of the inner triangle
qreal triangle_height() const
{
return inner_radius()*3/2;
}
/// Calculate the side of the inner triangle
qreal triangle_side() const
{
return inner_radius()*qSqrt(3);
}
/// return line from center to given point
QLineF line_to_point(const QPoint &p) const
{
return QLineF (w->geometry().width()/2, w->geometry().height()/2, p.x(), p.y());
}
void render_square()
{
int width = qMin<int>(square_size(), max_size);
QSize size(width, width);
inner_selector = QImage(size, QImage::Format_RGB32);
for ( int y = 0; y < width; ++y )
{
for ( int x = 0; x < width; ++x )
{
inner_selector.setPixel( x, y,
color_from(hue,double(x)/width,double(y)/width,1).rgb());
}
}
}
/**
* \brief renders the selector as a triangle
* \note It's the same as a square with the edge with value=0 collapsed to a single point
*/
void render_triangle()
{
QSizeF size = selector_size();
if ( size.height() > max_size )
size *= max_size / size.height();
qreal ycenter = size.height()/2;
inner_selector = QImage(size.toSize(), QImage::Format_RGB32);
for (int x = 0; x < inner_selector.width(); x++ )
{
qreal pval = x / size.height();
qreal slice_h = size.height() * pval;
for (int y = 0; y < inner_selector.height(); y++ )
{
qreal ymin = ycenter-slice_h/2;
qreal psat = qBound(0.0,(y-ymin)/slice_h,1.0);
inner_selector.setPixel(x,y,color_from(hue,psat,pval,1).rgb());
}
}
}
/// Updates the inner image that displays the saturation-value selector
void render_inner_selector()
{
if ( display_flags & ColorWheel::SHAPE_TRIANGLE )
render_triangle();
else
render_square();
}
/// Offset of the selector image
QPointF selector_image_offset()
{
if ( display_flags & SHAPE_TRIANGLE )
return QPointF(-inner_radius(),-triangle_side()/2);
return QPointF(-square_size()/2,-square_size()/2);
}
/**
* \brief Size of the selector when rendered to the screen
*/
QSizeF selector_size()
{
if ( display_flags & SHAPE_TRIANGLE )
return QSizeF(triangle_height(), triangle_side());
return QSizeF(square_size(), square_size());
}
/// Rotation of the selector image
qreal selector_image_angle()
{
if ( display_flags & SHAPE_TRIANGLE )
{
if ( display_flags & ANGLE_ROTATING )
return -hue*360-60;
return -150;
}
else
{
if ( display_flags & ANGLE_ROTATING )
return -hue*360-45;
else
return 180;
}
}
/// Updates the outer ring that displays the hue selector
void render_ring()
{
hue_ring = QPixmap(outer_radius()*2,outer_radius()*2);
hue_ring.fill(Qt::transparent);
QPainter painter(&hue_ring);
painter.setRenderHint(QPainter::Antialiasing);
painter.setCompositionMode(QPainter::CompositionMode_Source);
const int hue_stops = 24;
QConicalGradient gradient_hue(0, 0, 0);
if ( gradient_hue.stops().size() < hue_stops )
{
for ( double a = 0; a < 1.0; a+=1.0/(hue_stops-1) )
{
gradient_hue.setColorAt(a,rainbow_from_hue(a));
}
gradient_hue.setColorAt(1,rainbow_from_hue(0));
}
painter.translate(outer_radius(),outer_radius());
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(gradient_hue));
painter.drawEllipse(QPointF(0,0),outer_radius(),outer_radius());
painter.setBrush(Qt::transparent);//palette().background());
painter.drawEllipse(QPointF(0,0),inner_radius(),inner_radius());
}
void set_color(const QColor& c)
{
if ( display_flags & ColorWheel::COLOR_HSV )
{
hue = qMax(0.0, c.hsvHueF());
sat = c.hsvSaturationF();
val = c.valueF();
}
else if ( display_flags & ColorWheel::COLOR_HSL )
{
hue = qMax(0.0, c.hueF());
sat = detail::color_HSL_saturationF(c);
val = detail::color_lightnessF(c);
}
else if ( display_flags & ColorWheel::COLOR_LCH )
{
hue = qMax(0.0, c.hsvHueF());
sat = detail::color_chromaF(c);
val = detail::color_lumaF(c);
}
}
};
ColorWheel::ColorWheel(QWidget *parent) :
QWidget(parent), p(new Private(this))
{
setDisplayFlags(FLAGS_DEFAULT);
setAcceptDrops(true);
}
ColorWheel::~ColorWheel()
{
delete p;
}
QColor ColorWheel::color() const
{
return p->color_from(p->hue, p->sat, p->val, 1);
}
QSize ColorWheel::sizeHint() const
{
return QSize(p->wheel_width*5, p->wheel_width*5);
}
qreal ColorWheel::hue() const
{
if ( (p->display_flags & COLOR_LCH) && p->sat > 0.01 )
return color().hueF();
return p->hue;
}
qreal ColorWheel::saturation() const
{
return color().hsvSaturationF();
}
qreal ColorWheel::value() const
{
return color().valueF();
}
unsigned int ColorWheel::wheelWidth() const
{
return p->wheel_width;
}
void ColorWheel::setWheelWidth(unsigned int w)
{
p->wheel_width = w;
p->render_inner_selector();
update();
}
void ColorWheel::paintEvent(QPaintEvent * )
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.translate(geometry().width()/2,geometry().height()/2);
// hue wheel
if(p->hue_ring.isNull())
p->render_ring();
painter.drawPixmap(-p->outer_radius(), -p->outer_radius(), p->hue_ring);
// hue selector
painter.setPen(QPen(Qt::black,3));
painter.setBrush(Qt::NoBrush);
QLineF ray(0, 0, p->outer_radius(), 0);
ray.setAngle(p->hue*360);
QPointF h1 = ray.p2();
ray.setLength(p->inner_radius());
QPointF h2 = ray.p2();
painter.drawLine(h1,h2);
// lum-sat square
if(p->inner_selector.isNull())
p->render_inner_selector();
painter.rotate(p->selector_image_angle());
painter.translate(p->selector_image_offset());
QPointF selector_position;
if ( p->display_flags & SHAPE_SQUARE )
{
qreal side = p->square_size();
selector_position = QPointF(p->sat*side, p->val*side);
}
else if ( p->display_flags & SHAPE_TRIANGLE )
{
qreal side = p->triangle_side();
qreal height = p->triangle_height();
qreal slice_h = side * p->val;
qreal ymin = side/2-slice_h/2;
selector_position = QPointF(p->val*height, ymin + p->sat*slice_h);
QPolygonF triangle;
triangle.append(QPointF(0,side/2));
triangle.append(QPointF(height,0));
triangle.append(QPointF(height,side));
QPainterPath clip;
clip.addPolygon(triangle);
painter.setClipPath(clip);
}
painter.drawImage(QRectF(QPointF(0, 0), p->selector_size()), p->inner_selector);
painter.setClipping(false);
// lum-sat selector
painter.setPen(QPen(p->val > 0.5 ? Qt::black : Qt::white, 3));
painter.setBrush(Qt::NoBrush);
painter.drawEllipse(selector_position, selector_radius, selector_radius);
}
void ColorWheel::mouseMoveEvent(QMouseEvent *ev)
{
if (p->mouse_status == DragCircle )
{
p->hue = p->line_to_point(ev->pos()).angle()/360.0;
p->render_inner_selector();
Q_EMIT colorSelected(color());
Q_EMIT colorChanged(color());
update();
}
else if(p->mouse_status == DragSquare)
{
QLineF glob_mouse_ln = p->line_to_point(ev->pos());
QLineF center_mouse_ln ( QPointF(0,0),
glob_mouse_ln.p2() - glob_mouse_ln.p1() );
center_mouse_ln.setAngle(center_mouse_ln.angle()+p->selector_image_angle());
center_mouse_ln.setP2(center_mouse_ln.p2()-p->selector_image_offset());
if ( p->display_flags & SHAPE_SQUARE )
{
p->sat = qBound(0.0, center_mouse_ln.x2()/p->square_size(), 1.0);
p->val = qBound(0.0, center_mouse_ln.y2()/p->square_size(), 1.0);
}
else if ( p->display_flags & SHAPE_TRIANGLE )
{
QPointF pt = center_mouse_ln.p2();
qreal side = p->triangle_side();
p->val = qBound(0.0, pt.x() / p->triangle_height(), 1.0);
qreal slice_h = side * p->val;
qreal ycenter = side/2;
qreal ymin = ycenter-slice_h/2;
if ( slice_h > 0 )
p->sat = qBound(0.0, (pt.y()-ymin)/slice_h, 1.0);
}
Q_EMIT colorSelected(color());
Q_EMIT colorChanged(color());
update();
}
}
void ColorWheel::mousePressEvent(QMouseEvent *ev)
{
if ( ev->buttons() & Qt::LeftButton )
{
setFocus();
QLineF ray = p->line_to_point(ev->pos());
if ( ray.length() <= p->inner_radius() )
p->mouse_status = DragSquare;
else if ( ray.length() <= p->outer_radius() )
p->mouse_status = DragCircle;
// Update the color
mouseMoveEvent(ev);
}
}
void ColorWheel::mouseReleaseEvent(QMouseEvent *ev)
{
mouseMoveEvent(ev);
p->mouse_status = Nothing;
}
void ColorWheel::resizeEvent(QResizeEvent *)
{
p->render_ring();
p->render_inner_selector();
}
void ColorWheel::setColor(QColor c)
{
qreal oldh = p->hue;
p->set_color(c);
if (!qFuzzyCompare(oldh+1, p->hue+1))
p->render_inner_selector();
update();
Q_EMIT colorChanged(c);
}
void ColorWheel::setHue(qreal h)
{
p->hue = qBound(0.0, h, 1.0);
p->render_inner_selector();
update();
}
void ColorWheel::setSaturation(qreal s)
{
p->sat = qBound(0.0, s, 1.0);
update();
}
void ColorWheel::setValue(qreal v)
{
p->val = qBound(0.0, v, 1.0);
update();
}
void ColorWheel::setDisplayFlags(DisplayFlags flags)
{
if ( ! (flags & COLOR_FLAGS) )
flags |= default_flags & COLOR_FLAGS;
if ( ! (flags & ANGLE_FLAGS) )
flags |= default_flags & ANGLE_FLAGS;
if ( ! (flags & SHAPE_FLAGS) )
flags |= default_flags & SHAPE_FLAGS;
if ( (flags & COLOR_FLAGS) != (p->display_flags & COLOR_FLAGS) )
{
QColor old_col = color();
if ( flags & ColorWheel::COLOR_HSL )
{
p->hue = old_col.hueF();
p->sat = detail::color_HSL_saturationF(old_col);
p->val = detail::color_lightnessF(old_col);
p->color_from = &detail::color_from_hsl;
p->rainbow_from_hue = &detail::rainbow_hsv;
}
else if ( flags & ColorWheel::COLOR_LCH )
{
p->hue = old_col.hueF();
p->sat = detail::color_chromaF(old_col);
p->val = detail::color_lumaF(old_col);
p->color_from = &detail::color_from_lch;
p->rainbow_from_hue = &detail::rainbow_lch;
}
else
{
p->hue = old_col.hsvHueF();
p->sat = old_col.hsvSaturationF();
p->val = old_col.valueF();
p->color_from = &QColor::fromHsvF;
p->rainbow_from_hue = &detail::rainbow_hsv;
}
p->render_ring();
}
p->display_flags = flags;
p->render_inner_selector();
update();
Q_EMIT displayFlagsChanged(flags);
}
ColorWheel::DisplayFlags ColorWheel::displayFlags(DisplayFlags mask) const
{
return p->display_flags & mask;
}
void ColorWheel::setDefaultDisplayFlags(DisplayFlags flags)
{
if ( !(flags & COLOR_FLAGS) )
flags |= hard_default_flags & COLOR_FLAGS;
if ( !(flags & ANGLE_FLAGS) )
flags |= hard_default_flags & ANGLE_FLAGS;
if ( !(flags & SHAPE_FLAGS) )
flags |= hard_default_flags & SHAPE_FLAGS;
default_flags = flags;
}
ColorWheel::DisplayFlags ColorWheel::defaultDisplayFlags(DisplayFlags mask)
{
return default_flags & mask;
}
void ColorWheel::setDisplayFlag(DisplayFlags flag, DisplayFlags mask)
{
setDisplayFlags((p->display_flags&~mask)|flag);
}
void ColorWheel::dragEnterEvent(QDragEnterEvent* event)
{
if ( event->mimeData()->hasColor() ||
( event->mimeData()->hasText() && QColor(event->mimeData()->text()).isValid() ) )
event->acceptProposedAction();
}
void ColorWheel::dropEvent(QDropEvent* event)
{
if ( event->mimeData()->hasColor() )
{
setColor(event->mimeData()->colorData().value<QColor>());
event->accept();
}
else if ( event->mimeData()->hasText() )
{
QColor col(event->mimeData()->text());
if ( col.isValid() )
{
setColor(col);
event->accept();
}
}
}
} // namespace color_widgets

View File

@@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/color_widgets">
<file>alphaback.png</file>
</qresource>
</RCC>

View File

@@ -0,0 +1,195 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
* \copyright Copyright (C) 2014 Calle Laakkonen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/gradient_slider.hpp"
#include <QPainter>
#include <QStyleOptionSlider>
#include <QLinearGradient>
static void loadResource()
{
static bool loaded = false;
if ( !loaded )
{
Q_INIT_RESOURCE(color_widgets);
loaded = true;
}
}
namespace color_widgets {
class GradientSlider::Private
{
public:
QLinearGradient gradient;
QBrush back;
Private() :
back(Qt::darkGray, Qt::DiagCrossPattern)
{
loadResource();
back.setTexture(QPixmap(QStringLiteral(":/color_widgets/alphaback.png")));
gradient.setCoordinateMode(QGradient::StretchToDeviceMode);
}
};
GradientSlider::GradientSlider(QWidget *parent) :
QSlider(Qt::Horizontal, parent), p(new Private)
{}
GradientSlider::GradientSlider(Qt::Orientation orientation, QWidget *parent) :
QSlider(orientation, parent), p(new Private)
{}
GradientSlider::~GradientSlider()
{
delete p;
}
QBrush GradientSlider::background() const
{
return p->back;
}
void GradientSlider::setBackground(const QBrush &bg)
{
p->back = bg;
update();
}
QGradientStops GradientSlider::colors() const
{
return p->gradient.stops();
}
void GradientSlider::setColors(const QGradientStops &colors)
{
p->gradient.setStops(colors);
update();
}
QLinearGradient GradientSlider::gradient() const
{
return p->gradient;
}
void GradientSlider::setGradient(const QLinearGradient &gradient)
{
p->gradient = gradient;
update();
}
void GradientSlider::setColors(const QVector<QColor> &colors)
{
QGradientStops stops;
stops.reserve(colors.size());
double c = colors.size() - 1;
if(c==0) {
stops.append(QGradientStop(0, colors.at(0)));
} else {
for(int i=0;i<colors.size();++i) {
stops.append(QGradientStop(i/c, colors.at(i)));
}
}
setColors(stops);
}
void GradientSlider::setFirstColor(const QColor &c)
{
QGradientStops stops = p->gradient.stops();
if(stops.isEmpty())
stops.push_back(QGradientStop(0.0, c));
else
stops.front().second = c;
p->gradient.setStops(stops);
update();
}
void GradientSlider::setLastColor(const QColor &c)
{
QGradientStops stops = p->gradient.stops();
if(stops.size()<2)
stops.push_back(QGradientStop(1.0, c));
else
stops.back().second = c;
p->gradient.setStops(stops);
update();
}
QColor GradientSlider::firstColor() const
{
QGradientStops s = colors();
return s.empty() ? QColor() : s.front().second;
}
QColor GradientSlider::lastColor() const
{
QGradientStops s = colors();
return s.empty() ? QColor() : s.back().second;
}
void GradientSlider::paintEvent(QPaintEvent *)
{
QPainter painter(this);
QStyleOptionFrame panel;
panel.initFrom(this);
panel.lineWidth = 1;
panel.midLineWidth = 0;
panel.state |= QStyle::State_Sunken;
style()->drawPrimitive(QStyle::PE_Frame, &panel, &painter, this);
QRect r = style()->subElementRect(QStyle::SE_FrameContents, &panel, this);
painter.setClipRect(r);
if(orientation() == Qt::Horizontal)
p->gradient.setFinalStop(1, 0);
else
p->gradient.setFinalStop(0, 1);
painter.setPen(Qt::NoPen);
painter.setBrush(p->back);
painter.drawRect(1,1,geometry().width()-2,geometry().height()-2);
painter.setBrush(p->gradient);
painter.drawRect(1,1,geometry().width()-2,geometry().height()-2);
painter.setClipping(false);
QStyleOptionSlider opt_slider;
initStyleOption(&opt_slider);
opt_slider.state &= ~QStyle::State_HasFocus;
opt_slider.subControls = QStyle::SC_SliderHandle;
if (isSliderDown())
{
opt_slider.state |= QStyle::State_Sunken;
opt_slider.activeSubControls = QStyle::SC_SliderHandle;
}
opt_slider.rect = style()->subControlRect(QStyle::CC_Slider,&opt_slider,
QStyle::SC_SliderHandle,this);
style()->drawComplexControl(QStyle::CC_Slider, &opt_slider, &painter, this);
}
} // namespace color_widgets

View File

@@ -0,0 +1,140 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2014 Calle Laakkonen
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/hue_slider.hpp"
namespace color_widgets {
class HueSlider::Private
{
private:
HueSlider *w;
public:
qreal saturation = 1;
qreal value = 1;
qreal alpha = 1;
Private(HueSlider *widget)
: w(widget)
{
w->setRange(0, 359);
connect(w, &QSlider::valueChanged, [this]{
Q_EMIT w->colorHueChanged(percent());
});
updateGradient();
}
void updateGradient()
{
static const double n_colors = 6;
QGradientStops colors;
colors.reserve(n_colors+1);
for ( int i = 0; i <= n_colors; ++i )
colors.append(QGradientStop(i/n_colors, QColor::fromHsvF(i/n_colors, saturation, value)));
w->setColors(colors);
}
qreal percent()
{
return qreal(w->value() - w->minimum()) / (w->maximum() - w->minimum());
}
};
HueSlider::HueSlider(QWidget *parent) :
GradientSlider(parent), p(new Private(this))
{
}
HueSlider::HueSlider(Qt::Orientation orientation, QWidget *parent) :
GradientSlider(orientation, parent), p(new Private(this))
{
}
HueSlider::~HueSlider()
{
delete p;
}
qreal HueSlider::colorSaturation() const
{
return p->saturation;
}
void HueSlider::setColorSaturation(qreal s)
{
p->saturation = qBound(0.0, s, 1.0);
p->updateGradient();
}
qreal HueSlider::colorValue() const
{
return p->value;
}
void HueSlider::setColorValue(qreal v)
{
p->value = qBound(0.0, v, 1.0);
p->updateGradient();
}
qreal HueSlider::colorAlpha() const
{
return p->alpha;
}
void HueSlider::setColorAlpha(qreal alpha)
{
p->alpha = alpha;
p->updateGradient();
}
QColor HueSlider::color() const
{
return QColor::fromHsvF(p->percent(), p->saturation, p->value, p->alpha);
}
void HueSlider::setColor(const QColor& color)
{
p->saturation = color.saturationF();
p->value = color.valueF();
p->updateGradient();
setColorHue(color.hueF());
}
void HueSlider::setFullColor(const QColor& color)
{
p->alpha = color.alphaF();
setColor(color);
}
qreal HueSlider::colorHue() const
{
return p->percent();
}
void HueSlider::setColorHue(qreal colorHue)
{
setValue(minimum()+colorHue*(maximum()-minimum()));
}
} // namespace color_widgets

View File

@@ -0,0 +1,786 @@
/**
* \file
*
* \author Mattia Basaglia
*
* \copyright Copyright (C) 2013-2017 Mattia Basaglia
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "qt-color-widgets/swatch.hpp"
#include <cmath>
#include <QPainter>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QApplication>
#include <QDrag>
#include <QMimeData>
#include <QDropEvent>
#include <QDragEnterEvent>
#include <QStyleOption>
#include <QToolTip>
namespace color_widgets {
class Swatch::Private
{
public:
ColorPalette palette; ///< Palette with colors and related metadata
int selected; ///< Current selection index (-1 for no selection)
QSize color_size; ///< Preferred size for the color squares
ColorSizePolicy size_policy;
QPen border;
int forced_rows;
int forced_columns;
bool readonly; ///< Whether the palette can be modified via user interaction
QPoint drag_pos; ///< Point used to keep track of dragging
int drag_index; ///< Index used by drags
int drop_index; ///< Index for a requested drop
QColor drop_color; ///< Dropped color
bool drop_overwrite; ///< Whether the drop will overwrite an existing color
Swatch* owner;
Private(Swatch* owner)
: selected(-1),
color_size(16,16),
size_policy(Hint),
border(Qt::black, 1),
forced_rows(0),
forced_columns(0),
readonly(false),
drag_index(-1),
drop_index(-1),
drop_overwrite(false),
owner(owner)
{}
/**
* \brief Number of rows/columns in the palette
*/
QSize rowcols()
{
int count = palette.count();
if ( count == 0 )
return QSize();
if ( forced_rows )
return QSize(std::ceil( float(count) / forced_rows ), forced_rows);
int columns = palette.columns();
if ( forced_columns )
columns = forced_columns;
else if ( columns == 0 )
columns = qMin(palette.count(), owner->width() / color_size.width());
int rows = std::ceil( float(count) / columns );
return QSize(columns, rows);
}
/**
* \brief Sets the drop properties
*/
void dropEvent(QDropEvent* event)
{
// Find the output location
drop_index = owner->indexAt(event->pos());
if ( drop_index == -1 )
drop_index = palette.count();
// Gather up the color
if ( event->mimeData()->hasColor() )
{
drop_color = event->mimeData()->colorData().value<QColor>();
drop_color.setAlpha(255);
}
else if ( event->mimeData()->hasText() )
{
drop_color = QColor(event->mimeData()->text());
}
drop_overwrite = false;
QRectF drop_rect = indexRect(drop_index);
if ( drop_index < palette.count() && drop_rect.isValid() )
{
// 1 column => vertical style
if ( palette.columns() == 1 || forced_columns == 1 )
{
// Dragged to the last quarter of the size of the square, add after
if ( event->posF().y() >= drop_rect.top() + drop_rect.height() * 3.0 / 4 )
drop_index++;
// Dragged to the middle of the square, overwrite existing color
else if ( event->posF().x() > drop_rect.top() + drop_rect.height() / 4 &&
( event->dropAction() != Qt::MoveAction || event->source() != owner ) )
drop_overwrite = true;
}
else
{
// Dragged to the last quarter of the size of the square, add after
if ( event->posF().x() >= drop_rect.left() + drop_rect.width() * 3.0 / 4 )
drop_index++;
// Dragged to the middle of the square, overwrite existing color
else if ( event->posF().x() > drop_rect.left() + drop_rect.width() / 4 &&
( event->dropAction() != Qt::MoveAction || event->source() != owner ) )
drop_overwrite = true;
}
}
owner->update();
}
/**
* \brief Clears drop properties
*/
void clearDrop()
{
drop_index = -1;
drop_color = QColor();
drop_overwrite = false;
owner->update();
}
/**
* \brief Actual size of a color square
*/
QSizeF actualColorSize()
{
QSize rowcols = this->rowcols();
if ( !rowcols.isValid() )
return QSizeF();
return actualColorSize(rowcols);
}
/**
* \brief Actual size of a color square
* \pre rowcols.isValid() and obtained via rowcols()
*/
QSizeF actualColorSize(const QSize& rowcols)
{
return QSizeF (float(owner->width()) / rowcols.width(),
float(owner->height()) / rowcols.height());
}
/**
* \brief Rectangle corresponding to the color at the given index
* \pre rowcols.isValid() and obtained via rowcols()
* \pre color_size obtained via rowlcols(rowcols)
*/
QRectF indexRect(int index, const QSize& rowcols, const QSizeF& color_size)
{
if ( index == -1 )
return QRectF();
return QRectF(
index % rowcols.width() * color_size.width(),
index / rowcols.width() * color_size.height(),
color_size.width(),
color_size.height()
);
}
/**
* \brief Rectangle corresponding to the color at the given index
*/
QRectF indexRect(int index)
{
QSize rc = rowcols();
if ( index == -1 || !rc.isValid() )
return QRectF();
return indexRect(index, rc, actualColorSize(rc));
}
};
Swatch::Swatch(QWidget* parent)
: QWidget(parent), p(new Private(this))
{
connect(&p->palette, &ColorPalette::colorsChanged, this, &Swatch::paletteModified);
connect(&p->palette, &ColorPalette::columnsChanged, this, (void(QWidget::*)())&QWidget::update);
connect(&p->palette, &ColorPalette::colorsUpdated, this, (void(QWidget::*)())&QWidget::update);
connect(&p->palette, &ColorPalette::colorChanged, [this](int index){
if ( index == p->selected )
Q_EMIT colorSelected( p->palette.colorAt(index) );
});
connect(&p->palette, &ColorPalette::colorRemoved, [this](int index){
if ( index == p->selected )
clearSelection();
});
setFocusPolicy(Qt::StrongFocus);
setAcceptDrops(true);
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
setAttribute(Qt::WA_Hover, true);
}
Swatch::~Swatch()
{
delete p;
}
QSize Swatch::sizeHint() const
{
QSize rowcols = p->rowcols();
if ( !p->color_size.isValid() || !rowcols.isValid() )
return QSize();
return QSize(
p->color_size.width() * rowcols.width(),
p->color_size.height() * rowcols.height()
);
}
QSize Swatch::minimumSizeHint() const
{
if ( p->size_policy != Hint )
return sizeHint();
return QSize();
}
const ColorPalette& Swatch::palette() const
{
return p->palette;
}
ColorPalette& Swatch::palette()
{
return p->palette;
}
int Swatch::selected() const
{
return p->selected;
}
QColor Swatch::selectedColor() const
{
return p->palette.colorAt(p->selected);
}
int Swatch::indexAt(const QPoint& pt)
{
QSize rowcols = p->rowcols();
if ( rowcols.isEmpty() )
return -1;
QSizeF color_size = p->actualColorSize(rowcols);
QPoint point(
qBound<int>(0, pt.x() / color_size.width(), rowcols.width() - 1),
qBound<int>(0, pt.y() / color_size.height(), rowcols.height() - 1)
);
int index = point.y() * rowcols.width() + point.x();
if ( index >= p->palette.count() )
return -1;
return index;
}
QColor Swatch::colorAt(const QPoint& pt)
{
return p->palette.colorAt(indexAt(pt));
}
void Swatch::setPalette(const ColorPalette& palette)
{
clearSelection();
p->palette = palette;
update();
Q_EMIT paletteChanged(p->palette);
}
void Swatch::setSelected(int selected)
{
if ( selected < 0 || selected >= p->palette.count() )
selected = -1;
if ( selected != p->selected )
{
Q_EMIT selectedChanged( p->selected = selected );
if ( selected != -1 )
Q_EMIT colorSelected( p->palette.colorAt(p->selected) );
update();
}
}
void Swatch::clearSelection()
{
setSelected(-1);
}
void Swatch::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event)
QSize rowcols = p->rowcols();
if ( rowcols.isEmpty() )
return;
QSizeF color_size = p->actualColorSize(rowcols);
QPainter painter(this);
QStyleOptionFrame panel;
panel.initFrom(this);
panel.lineWidth = 1;
panel.midLineWidth = 0;
panel.state |= QStyle::State_Sunken;
style()->drawPrimitive(QStyle::PE_Frame, &panel, &painter, this);
QRect r = style()->subElementRect(QStyle::SE_FrameContents, &panel, this);
painter.setClipRect(r);
int count = p->palette.count();
painter.setPen(p->border);
for ( int y = 0, i = 0; i < count; y++ )
{
for ( int x = 0; x < rowcols.width() && i < count; x++, i++ )
{
painter.setBrush(p->palette.colorAt(i));
painter.drawRect(p->indexRect(i, rowcols, color_size));
}
}
painter.setClipping(false);
if ( p->drop_index != -1 )
{
QRectF drop_area = p->indexRect(p->drop_index, rowcols, color_size);
if ( p->drop_overwrite )
{
painter.setBrush(p->drop_color);
painter.setPen(QPen(Qt::gray));
painter.drawRect(drop_area);
}
else if ( rowcols.width() == 1 )
{
// 1 column => vertical style
painter.setPen(QPen(p->drop_color, 2));
painter.setBrush(Qt::transparent);
painter.drawLine(drop_area.topLeft(), drop_area.topRight());
}
else
{
painter.setPen(QPen(p->drop_color, 2));
painter.setBrush(Qt::transparent);
painter.drawLine(drop_area.topLeft(), drop_area.bottomLeft());
// Draw also on the previous line when the first item of a line is selected
if ( p->drop_index % rowcols.width() == 0 && p->drop_index != 0 )
{
drop_area = p->indexRect(p->drop_index-1, rowcols, color_size);
drop_area.translate(color_size.width(), 0);
painter.drawLine(drop_area.topLeft(), drop_area.bottomLeft());
}
}
}
if ( p->selected != -1 )
{
QRectF rect = p->indexRect(p->selected, rowcols, color_size);
painter.setBrush(Qt::transparent);
painter.setPen(QPen(Qt::darkGray, 2));
painter.drawRect(rect);
painter.setPen(QPen(Qt::gray, 2, Qt::DotLine));
painter.drawRect(rect);
}
}
void Swatch::keyPressEvent(QKeyEvent* event)
{
if ( p->palette.count() == 0 )
QWidget::keyPressEvent(event);
int selected = p->selected;
int count = p->palette.count();
QSize rowcols = p->rowcols();
int columns = rowcols.width();
int rows = rowcols.height();
switch ( event->key() )
{
default:
QWidget::keyPressEvent(event);
return;
case Qt::Key_Left:
if ( selected == -1 )
selected = count - 1;
else if ( selected > 0 )
selected--;
break;
case Qt::Key_Right:
if ( selected == -1 )
selected = 0;
else if ( selected < count - 1 )
selected++;
break;
case Qt::Key_Up:
if ( selected == -1 )
selected = count - 1;
else if ( selected >= columns )
selected -= columns;
break;
case Qt::Key_Down:
if ( selected == -1 )
selected = 0;
else if ( selected < count - columns )
selected += columns;
break;
case Qt::Key_Home:
if ( event->modifiers() & Qt::ControlModifier )
selected = 0;
else
selected -= selected % columns;
break;
case Qt::Key_End:
if ( event->modifiers() & Qt::ControlModifier )
selected = count - 1;
else
selected += columns - (selected % columns) - 1;
break;
case Qt::Key_Delete:
removeSelected();
return;
case Qt::Key_Backspace:
if (selected != -1 && !p->readonly )
{
p->palette.eraseColor(selected);
if ( p->palette.count() == 0 )
selected = -1;
else
selected = qMax(selected - 1, 0);
}
break;
case Qt::Key_PageUp:
if ( selected == -1 )
selected = 0;
else
selected = selected % columns;
break;
case Qt::Key_PageDown:
if ( selected == -1 )
{
selected = count - 1;
}
else
{
selected = columns * (rows-1) + selected % columns;
if ( selected >= count )
selected -= columns;
}
break;
}
setSelected(selected);
}
void Swatch::removeSelected()
{
if (p->selected != -1 && !p->readonly )
{
int selected = p->selected;
p->palette.eraseColor(p->selected);
setSelected(qMin(selected, p->palette.count() - 1));
}
}
void Swatch::mousePressEvent(QMouseEvent *event)
{
if ( event->button() == Qt::LeftButton )
{
setSelected(indexAt(event->pos()));
p->drag_pos = event->pos();
p->drag_index = indexAt(event->pos());
}
else if ( event->button() == Qt::RightButton )
{
int index = indexAt(event->pos());
if ( index != -1 )
Q_EMIT rightClicked(index);
}
}
void Swatch::mouseMoveEvent(QMouseEvent *event)
{
if ( p->drag_index != -1 && (event->buttons() & Qt::LeftButton) &&
(p->drag_pos - event->pos()).manhattanLength() >= QApplication::startDragDistance() )
{
QColor color = p->palette.colorAt(p->drag_index);
QPixmap preview(24,24);
preview.fill(color);
QMimeData *mimedata = new QMimeData;
mimedata->setColorData(color);
mimedata->setText(p->palette.nameAt(p->drag_index));
QDrag *drag = new QDrag(this);
drag->setMimeData(mimedata);
drag->setPixmap(preview);
Qt::DropActions actions = Qt::CopyAction;
if ( !p->readonly )
actions |= Qt::MoveAction;
drag->exec(actions);
}
}
void Swatch::mouseReleaseEvent(QMouseEvent *event)
{
if ( event->button() == Qt::LeftButton )
{
p->drag_index = -1;
}
}
void Swatch::mouseDoubleClickEvent(QMouseEvent *event)
{
if ( event->button() == Qt::LeftButton )
{
int index = indexAt(event->pos());
if ( index != -1 )
Q_EMIT doubleClicked(index);
}
}
void Swatch::wheelEvent(QWheelEvent* event)
{
if ( event->delta() > 0 )
p->selected = qMin(p->selected + 1, p->palette.count() - 1);
else if ( p->selected == -1 )
p->selected = p->palette.count() - 1;
else if ( p->selected > 0 )
p->selected--;
setSelected(p->selected);
}
void Swatch::dragEnterEvent(QDragEnterEvent *event)
{
if ( p->readonly )
return;
p->dropEvent(event);
if ( p->drop_color.isValid() && p->drop_index != -1 )
{
if ( event->proposedAction() == Qt::MoveAction && event->source() == this )
event->setDropAction(Qt::MoveAction);
else
event->setDropAction(Qt::CopyAction);
event->accept();
}
}
void Swatch::dragMoveEvent(QDragMoveEvent* event)
{
if ( p->readonly )
return;
p->dropEvent(event);
}
void Swatch::dragLeaveEvent(QDragLeaveEvent *event)
{
Q_UNUSED(event)
p->clearDrop();
}
void Swatch::dropEvent(QDropEvent *event)
{
if ( p->readonly )
return;
QString name;
// Gather up the color
if ( event->mimeData()->hasColor() && event->mimeData()->hasText() )
name = event->mimeData()->text();
// Not a color, discard
if ( !p->drop_color.isValid() || p->drop_index == -1 )
return;
p->dropEvent(event);
// Move unto self
if ( event->dropAction() == Qt::MoveAction && event->source() == this )
{
// Not moved => noop
if ( p->drop_index != p->drag_index && p->drop_index != p->drag_index + 1 )
{
// Erase the old color
p->palette.eraseColor(p->drag_index);
if ( p->drop_index > p->drag_index )
p->drop_index--;
p->selected = p->drop_index;
// Insert the dropped color
p->palette.insertColor(p->drop_index, p->drop_color, name);
}
}
// Move into a color cell
else if ( p->drop_overwrite )
{
p->palette.setColorAt(p->drop_index, p->drop_color, name);
}
// Insert the dropped color
else
{
p->palette.insertColor(p->drop_index, p->drop_color, name);
}
// Finalize
event->accept();
p->drag_index = -1;
p->clearDrop();
}
void Swatch::paletteModified()
{
if ( p->selected >= p->palette.count() )
clearSelection();
if ( p->size_policy == Minimum )
setMinimumSize(sizeHint());
else if ( p->size_policy == Fixed )
setFixedSize(sizeHint());
update();
}
QSize Swatch::colorSize() const
{
return p->color_size;
}
void Swatch::setColorSize(const QSize& colorSize)
{
if ( p->color_size != colorSize )
Q_EMIT colorSizeChanged(p->color_size = colorSize);
}
Swatch::ColorSizePolicy Swatch::colorSizePolicy() const
{
return p->size_policy;
}
void Swatch::setColorSizePolicy(ColorSizePolicy colorSizePolicy)
{
if ( p->size_policy != colorSizePolicy )
{
setMinimumSize(0,0);
setFixedSize(QWIDGETSIZE_MAX,QWIDGETSIZE_MAX);
paletteModified();
Q_EMIT colorSizePolicyChanged(p->size_policy = colorSizePolicy);
}
}
int Swatch::forcedColumns() const
{
return p->forced_columns;
}
int Swatch::forcedRows() const
{
return p->forced_rows;
}
void Swatch::setForcedColumns(int forcedColumns)
{
if ( forcedColumns <= 0 )
forcedColumns = 0;
if ( forcedColumns != p->forced_columns )
{
Q_EMIT forcedColumnsChanged(p->forced_columns = forcedColumns);
Q_EMIT forcedRowsChanged(p->forced_rows = 0);
}
}
void Swatch::setForcedRows(int forcedRows)
{
if ( forcedRows <= 0 )
forcedRows = 0;
if ( forcedRows != p->forced_rows )
{
Q_EMIT forcedColumnsChanged(p->forced_columns = 0);
Q_EMIT forcedRowsChanged(p->forced_rows = forcedRows);
}
}
bool Swatch::readOnly() const
{
return p->readonly;
}
void Swatch::setReadOnly(bool readOnly)
{
if ( readOnly != p->readonly )
{
Q_EMIT readOnlyChanged(p->readonly = readOnly);
setAcceptDrops(!p->readonly);
}
}
bool Swatch::event(QEvent* event)
{
if(event->type() == QEvent::ToolTip)
{
QHelpEvent* help_ev = static_cast<QHelpEvent*>(event);
int index = indexAt(help_ev->pos());
if ( index != -1 )
{
QColor color = p->palette.colorAt(index);
QString name = p->palette.nameAt(index);
QString message = color.name();
if ( !name.isEmpty() )
message = tr("%1 (%2)").arg(name).arg(message);
message = "<tt style='background-color:"+color.name()+";color:"+color.name()+";'>MM</tt> "+message.toHtmlEscaped();
QToolTip::showText(help_ev->globalPos(), message, this,
p->indexRect(index).toRect());
event->accept();
}
else
{
QToolTip::hideText();
event->ignore();
}
return true;
}
return QWidget::event(event);
}
QPen Swatch::border() const
{
return p->border;
}
void Swatch::setBorder(const QPen& border)
{
if ( border != p->border )
{
p->border = border;
Q_EMIT borderChanged(border);
update();
}
}
} // namespace color_widgets

11
src/glsl/cursor_frag.glsl Normal file
View File

@@ -0,0 +1,11 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}

14
src/glsl/cursor_vert.glsl Normal file
View File

@@ -0,0 +1,14 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform mat4 model_view_projection;
uniform vec3 cursor_pos;
uniform float radius;
in vec4 position;
void main()
{
vec3 p = cursor_pos + position.xyz * radius;
gl_Position = model_view_projection * vec4(p,1.);
}

View File

@@ -0,0 +1,11 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform vec3 color;
out vec4 out_color;
void main()
{
out_color = vec4(color, 1.0);
}

View File

@@ -0,0 +1,12 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec4 position;
uniform mat4 model_view;
uniform mat4 projection;
void main()
{
gl_Position = projection * model_view * position;
}

44
src/glsl/liquid_frag.glsl Normal file
View File

@@ -0,0 +1,44 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform sampler2D tex;
uniform vec4 ocean_color_light;
uniform vec4 ocean_color_dark;
uniform vec4 river_color_light;
uniform vec4 river_color_dark;
uniform int type;
uniform float animtime;
uniform vec2 param;
in float depth_;
in vec2 tex_coord_;
out vec4 out_color;
vec2 rot2(vec2 p, float degree)
{
float a = radians(degree);
return mat2(cos(a), -sin(a), sin(a), cos(a))*p;
}
void main()
{
// lava || slime
if(type == 2 || type == 3)
{
out_color = texture(tex, tex_coord_ + vec2(param.x*animtime, param.y*animtime));
}
else
{
vec2 uv = rot2(tex_coord_ * param.x, param.y);
vec4 texel = texture(tex, uv);
vec4 lerp = (type == 1)
? mix (ocean_color_light, ocean_color_dark, depth_)
: mix (river_color_light, river_color_dark, depth_)
;
//clamp shouldn't be needed
out_color = vec4 (clamp(texel + lerp, 0.0, 1.0).rgb, lerp.a);
}
}

30
src/glsl/liquid_vert.glsl Normal file
View File

@@ -0,0 +1,30 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec4 position;
in vec2 tex_coord;
in float depth;
uniform mat4 model_view;
uniform mat4 projection;
uniform mat4 transform;
uniform int use_transform = int(0);
out float depth_;
out vec2 tex_coord_;
void main()
{
depth_ = depth;
tex_coord_ = tex_coord;
if(use_transform == 1)
{
gl_Position = projection * model_view * transform * position;
}
else
{
gl_Position = projection * model_view * position;
}
}

11
src/glsl/m2_box_frag.glsl Normal file
View File

@@ -0,0 +1,11 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}

13
src/glsl/m2_box_vert.glsl Normal file
View File

@@ -0,0 +1,13 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in mat4 transform;
in vec4 position;
uniform mat4 model_view;
uniform mat4 projection;
void main()
{
gl_Position = projection * model_view * transform * position;
}

194
src/glsl/m2_frag.glsl Normal file
View File

@@ -0,0 +1,194 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec2 uv1;
in vec2 uv2;
in float camera_dist;
in vec3 norm;
out vec4 out_color;
uniform vec4 mesh_color;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform vec4 fog_color;
uniform float fog_start;
uniform float fog_end;
uniform int draw_fog;
uniform int fog_mode;
uniform int unfogged;
uniform int unlit;
uniform vec3 light_dir;
uniform vec3 diffuse_color;
uniform vec3 ambient_color;
uniform float alpha_test;
uniform int pixel_shader;
void main()
{
vec4 color = vec4(0.0);
if(mesh_color.a < alpha_test)
{
discard;
}
vec4 texture1 = texture(tex1, uv1);
vec4 texture2 = texture(tex2, uv2);
// code from Deamon87 and https://wowdev.wiki/M2/Rendering#Pixel_Shaders
if (pixel_shader == 0) //Combiners_Opaque
{
color.rgb = texture1.rgb * mesh_color.rgb;
color.a = mesh_color.a;
}
else if (pixel_shader == 1) // Combiners_Decal
{
color.rgb = mix(mesh_color.rgb, texture1.rgb, mesh_color.a);
color.a = mesh_color.a;
}
else if (pixel_shader == 2) // Combiners_Add
{
color.rgba = texture1.rgba + mesh_color.rgba;
}
else if (pixel_shader == 3) // Combiners_Mod2x
{
color.rgb = texture1.rgb * mesh_color.rgb * vec3(2.0);
color.a = texture1.a * mesh_color.a * 2.0;
}
else if (pixel_shader == 4) // Combiners_Fade
{
color.rgb = mix(texture1.rgb, mesh_color.rgb, mesh_color.a);
color.a = mesh_color.a;
}
else if (pixel_shader == 5) // Combiners_Mod
{
color.rgba = texture1.rgba * mesh_color.rgba;
}
else if (pixel_shader == 6) // Combiners_Opaque_Opaque
{
color.rgb = texture1.rgb * texture2.rgb * mesh_color.rgb;
color.a = mesh_color.a;
}
else if (pixel_shader == 7) // Combiners_Opaque_Add
{
color.rgb = texture2.rgb + texture1.rgb * mesh_color.rgb;
color.a = mesh_color.a + texture1.a;
}
else if (pixel_shader == 8) // Combiners_Opaque_Mod2x
{
color.rgb = texture1.rgb * mesh_color.rgb * texture2.rgb * vec3(2.0);
color.a = texture2.a * mesh_color.a * 2.0;
}
else if (pixel_shader == 9) // Combiners_Opaque_Mod2xNA
{
color.rgb = texture1.rgb * mesh_color.rgb * texture2.rgb * vec3(2.0);
color.a = mesh_color.a;
}
else if (pixel_shader == 10) // Combiners_Opaque_AddNA
{
color.rgb = texture2.rgb + texture1.rgb * mesh_color.rgb;
color.a = mesh_color.a;
}
else if (pixel_shader == 11) // Combiners_Opaque_Mod
{
color.rgb = texture1.rgb * texture2.rgb * mesh_color.rgb;
color.a = texture2.a * mesh_color.a;
}
else if (pixel_shader == 12) // Combiners_Mod_Opaque
{
color.rgb = texture1.rgb * texture2.rgb * mesh_color.rgb;
color.a = texture1.a;
}
else if (pixel_shader == 13) // Combiners_Mod_Add
{
color.rgba = texture2.rgba + texture1.rgba * mesh_color.rgba;
}
else if (pixel_shader == 14) // Combiners_Mod_Mod2x
{
color.rgba = texture1.rgba * texture2.rgba * mesh_color.rgba * vec4(2.0);
}
else if (pixel_shader == 15) // Combiners_Mod_Mod2xNA
{
color.rgb = texture1.rgb * texture2.rgb * mesh_color.rgb * vec3(2.0);
color.a = texture1.a * mesh_color.a;
}
else if (pixel_shader == 16) // Combiners_Mod_AddNA
{
color.rgb = texture2.rgb + texture1.rgb * mesh_color.rgb;
color.a = texture1.a * mesh_color.a;
}
else if (pixel_shader == 17) // Combiners_Mod_Mod
{
color.rgba = texture1.rgba * texture2.rgba * mesh_color.rgba;
}
else if (pixel_shader == 18) // Combiners_Add_Mod
{
color.rgb = (texture1.rgb + mesh_color.rgb) * texture2.a;
color.a = (texture1.a + mesh_color.a) * texture2.a;
}
else if (pixel_shader == 19) // Combiners_Mod2x_Mod2x
{
color.rgba = texture1.rgba * texture2.rgba * mesh_color.rgba * vec4(4.0);
}
else if (pixel_shader == 20) // Combiners_Opaque_Mod2xNA_Alpha
{
color.rgb = (mesh_color.rgb * texture1.rgb) * mix(texture2.rgb * 2.0, vec3(1.0), texture1.a);
color.a = mesh_color.a;
}
else if (pixel_shader == 21) //Combiners_Opaque_AddAlpha
{
color.rgb = (mesh_color.rgb * texture1.rgb) + (texture2.rgb * texture2.a);
color.a = mesh_color.a;
}
else if (pixel_shader == 22) // Combiners_Opaque_AddAlpha_Alpha
{
color.rgb = (mesh_color.rgb * texture1.rgb) + (texture2.rgb * texture2.a * texture1.a);
color.a = mesh_color.a;
}
if(color.a < alpha_test)
{
discard;
}
if(unlit == 0)
{
// diffuse + ambient lighting
color.rgb *= vec3(clamp (diffuse_color * max(dot(norm, light_dir), 0.0), 0.0, 1.0)) + ambient_color;
}
if(draw_fog == 1 && unfogged == 0 && camera_dist >= fog_end * fog_start)
{
float start = fog_end * fog_start;
float alpha = (camera_dist - start) / (fog_end - start);
vec3 fog;
// see https://wowdev.wiki/M2/Rendering#Fog_Modes
if(fog_mode == 1)
{
fog = fog_color.rgb;
}
else if(fog_mode == 2)
{
fog = vec3(0.);
}
else if(fog_mode == 3)
{
fog = vec3(1.);
}
else if(fog_mode == 4)
{
fog = vec3(0.5);
}
color.rgb = mix(color.rgb, fog, alpha);
}
out_color = color;
}

71
src/glsl/m2_vert.glsl Normal file
View File

@@ -0,0 +1,71 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec4 pos;
in vec3 normal;
in vec2 texcoord1;
in vec2 texcoord2;
#ifdef instanced
in mat4 transform;
#else
uniform mat4 transform;
#endif
out vec2 uv1;
out vec2 uv2;
out float camera_dist;
out vec3 norm;
uniform mat4 model_view;
uniform mat4 projection;
uniform int tex_unit_lookup_1;
uniform int tex_unit_lookup_2;
uniform mat4 tex_matrix_1;
uniform mat4 tex_matrix_2;
// code from https://wowdev.wiki/M2/.skin#Environment_mapping
vec2 sphere_map(vec3 vert, vec3 norm)
{
vec3 normPos = -(normalize(vert));
vec3 temp = (normPos - (norm * (2.0 * dot(normPos, norm))));
temp = vec3(temp.x, temp.y, temp.z + 1.0);
return ((normalize(temp).xy * 0.5) + vec2(0.5));
}
vec2 get_texture_uv(int tex_unit_lookup, vec3 vert, vec3 norm)
{
if(tex_unit_lookup == 0)
{
return sphere_map(vert, norm);
}
else if(tex_unit_lookup == 1)
{
return (transpose(tex_matrix_1) * vec4(texcoord1, 0.0, 1.0)).xy;
}
else if(tex_unit_lookup == 2)
{
return (transpose(tex_matrix_2) * vec4(texcoord2, 0.0, 1.0)).xy;
}
else
{
return vec2(0.0);
}
}
void main()
{
vec4 vertex = model_view * transform * pos;
// important to normalize because of the scaling !!
norm = normalize(mat3(transform) * normal);
uv1 = get_texture_uv(tex_unit_lookup_1, vertex.xyz, norm);
uv2 = get_texture_uv(tex_unit_lookup_2, vertex.xyz, norm);
camera_dist = -vertex.z;
gl_Position = projection * vertex;
}

11
src/glsl/mfbo_frag.glsl Normal file
View File

@@ -0,0 +1,11 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform vec4 color;
out vec4 out_color;
void main()
{
out_color = color;
}

11
src/glsl/mfbo_vert.glsl Normal file
View File

@@ -0,0 +1,11 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec4 position;
uniform mat4 model_view_projection;
void main()
{
gl_Position = model_view_projection * position;
}

View File

@@ -0,0 +1,23 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec2 f_uv;
in vec4 f_color;
out vec4 out_color;
uniform sampler2D tex;
uniform float alpha_test;
void main()
{
vec4 t = texture(tex, f_uv);
if(t.a < alpha_test)
{
discard;
}
out_color = vec4(f_color.rgb * t.rgb, t.a);
}

View File

@@ -0,0 +1,30 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in mat4 transform;
in vec4 position;
in vec3 offset;
in vec2 uv;
in vec4 color;
out vec2 f_uv;
out vec4 f_color;
uniform mat4 model_view_projection;
uniform int billboard;
void main()
{
f_uv = uv;
f_color = color;
if(billboard == 1)
{
vec4 pos = transform*position;
pos.xyz += offset;
gl_Position = model_view_projection * pos;
}
else
{
gl_Position = model_view_projection * transform * position;
}
}

15
src/glsl/ribbon_frag.glsl Normal file
View File

@@ -0,0 +1,15 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec2 f_uv;
out vec4 out_color;
uniform sampler2D tex;
uniform vec4 color;
void main()
{
vec4 t = texture(tex, f_uv);
out_color = vec4(color.rgb * t.rgb, color.a);
}

16
src/glsl/ribbon_vert.glsl Normal file
View File

@@ -0,0 +1,16 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in mat4 transform;
in vec4 position;
in vec2 uv;
out vec2 f_uv;
uniform mat4 model_view_projection;
void main()
{
f_uv = uv;
gl_Position = model_view_projection * transform * position;
}

196
src/glsl/terrain_frag.glsl Normal file
View File

@@ -0,0 +1,196 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform sampler2D shadow_map;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform sampler2D tex3;
uniform vec2 tex_anim_0;
uniform vec2 tex_anim_1;
uniform vec2 tex_anim_2;
uniform vec2 tex_anim_3;
uniform sampler2D alphamap;
uniform int layer_count;
uniform bool has_mccv;
uniform bool cant_paint;
uniform bool draw_areaid_overlay;
uniform vec4 areaid_color;
uniform bool draw_impassible_flag;
uniform bool draw_terrain_height_contour;
uniform bool draw_lines;
uniform bool draw_hole_lines;
uniform bool draw_wireframe;
uniform int wireframe_type;
uniform float wireframe_radius;
uniform float wireframe_width;
uniform vec4 wireframe_color;
uniform bool rainbow_wireframe;
uniform vec3 camera;
uniform bool draw_fog;
uniform vec4 fog_color;
uniform float fog_start;
uniform float fog_end;
uniform bool draw_cursor_circle;
uniform vec3 cursor_position;
uniform float outer_cursor_radius;
uniform float inner_cursor_ratio;
uniform vec4 cursor_color;
uniform vec3 light_dir;
uniform vec3 diffuse_color;
uniform vec3 ambient_color;
in vec3 vary_position;
in vec2 vary_texcoord;
in vec3 vary_normal;
in vec3 vary_mccv;
out vec4 out_color;
const float TILESIZE = 533.33333;
const float CHUNKSIZE = TILESIZE / 16.0;
const float HOLESIZE = CHUNKSIZE * 0.25;
const float UNITSIZE = HOLESIZE * 0.5;
vec4 texture_blend()
{
if(layer_count == 0)
return vec4 (1.0, 1.0, 1.0, 1.0);
vec3 alpha = texture(alphamap, vary_texcoord / 8.0).rgb;
float a0 = alpha.r;
float a1 = alpha.g;
float a2 = alpha.b;
vec3 t0 = texture(tex0, vary_texcoord + tex_anim_0).rgb;
vec3 t1 = texture(tex1, vary_texcoord + tex_anim_1).rgb;
vec3 t2 = texture(tex2, vary_texcoord + tex_anim_2).rgb;
vec3 t3 = texture(tex3, vary_texcoord + tex_anim_3).rgb;
return vec4 (t0 * (1.0 - (a0 + a1 + a2)) + t1 * a0 + t2 * a1 + t3 * a2, 1.0);
}
float contour_alpha(float unit_size, float pos, float line_width)
{
float f = abs(fract((pos + unit_size*0.5) / unit_size) - 0.5);
float df = abs(line_width / unit_size);
return smoothstep(0.0, df, f);
}
float contour_alpha(float unit_size, vec2 pos, vec2 line_width)
{
return 1.0 - min( contour_alpha(unit_size, pos.x, line_width.x)
, contour_alpha(unit_size, pos.y, line_width.y)
);
}
void main()
{
float dist_from_camera = distance(camera, vary_position);
if(draw_fog && dist_from_camera >= fog_end)
{
out_color = fog_color;
return;
}
vec3 fw = fwidth(vary_position.xyz);
out_color = texture_blend();
out_color.rgb *= vary_mccv;
// diffuse + ambient lighting
out_color.rgb *= vec3(clamp (diffuse_color * max(dot(vary_normal, light_dir), 0.0), 0.0, 1.0)) + ambient_color;
if(cant_paint)
{
out_color *= vec4(1.0, 0.0, 0.0, 1.0);
}
if(draw_areaid_overlay)
{
out_color = out_color * 0.3 + areaid_color;
}
if(draw_impassible_flag)
{
out_color.rgb = mix(vec3(1.0), out_color.rgb, 0.5);
}
float shadow_alpha = texture(shadow_map, vary_texcoord / 8.0).r;
out_color = vec4 (out_color.rgb * (1.0 - shadow_alpha), 1.0);
if (draw_terrain_height_contour)
{
out_color = vec4(out_color.rgb * contour_alpha(4.0, vary_position.y+0.1, fw.y), 1.0);
}
bool lines_drawn = false;
if(draw_lines)
{
vec4 color = vec4(0.0, 0.0, 0.0, 0.0);
color.a = contour_alpha(TILESIZE, vary_position.xz, fw.xz * 1.5);
color.g = color.a > 0.0 ? 0.8 : 0.0;
if(color.a == 0.0)
{
color.a = contour_alpha(CHUNKSIZE, vary_position.xz, fw.xz);
color.r = color.a > 0.0 ? 0.8 : 0.0;
}
if(draw_hole_lines && color.a == 0.0)
{
color.a = contour_alpha(HOLESIZE, vary_position.xz, fw.xz * 0.75);
color.b = 0.8;
}
lines_drawn = color.a > 0.0;
out_color.rgb = mix(out_color.rgb, color.rgb, color.a);
}
if(draw_fog && dist_from_camera >= fog_end * fog_start)
{
float start = fog_end * fog_start;
float alpha = (dist_from_camera - start) / (fog_end - start);
out_color.rgb = mix(out_color.rgb, fog_color.rgb, alpha);
}
if(draw_wireframe && !lines_drawn)
{
// true by default => type 0
bool draw_wire = true;
float real_wireframe_radius = max(outer_cursor_radius * wireframe_radius, 2.0 * UNITSIZE);
if(wireframe_type == 1)
{
draw_wire = (length(vary_position.xz - cursor_position.xz) < real_wireframe_radius);
}
if(draw_wire)
{
float alpha = contour_alpha(UNITSIZE, vary_position.xz, fw.xz * wireframe_width);
float xmod = mod(vary_position.x, UNITSIZE);
float zmod = mod(vary_position.z, UNITSIZE);
float d = length(fw.xz) * wireframe_width;
float diff = min( min(abs(xmod - zmod), abs(xmod - UNITSIZE + zmod))
, min(abs(zmod - xmod), abs(zmod + UNITSIZE - zmod))
);
alpha = max(alpha, 1.0 - smoothstep(0.0, d, diff));
out_color.rgb = mix(out_color.rgb, wireframe_color.rgb, wireframe_color.a*alpha);
}
}
if (draw_cursor_circle)
{
float diff = length(vary_position.xz - cursor_position.xz);
diff = min(abs(diff - outer_cursor_radius), abs(diff - outer_cursor_radius * inner_cursor_ratio));
float alpha = smoothstep(0.0, length(fw.xz), diff);
out_color.rgb = mix(cursor_color.rgb, out_color.rgb, alpha);
}
}

View File

@@ -0,0 +1,24 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec3 position;
in vec3 normal;
in vec3 mccv;
in vec2 texcoord;
uniform mat4 model_view;
uniform mat4 projection;
out vec3 vary_position;
out vec2 vary_texcoord;
out vec3 vary_normal;
out vec3 vary_mccv;
void main()
{
gl_Position = projection * model_view * vec4(position, 1.0);
vary_normal = normal;
vary_position = position;
vary_texcoord = texcoord;
vary_mccv = mccv;
}

119
src/glsl/wmo_frag.glsl Normal file
View File

@@ -0,0 +1,119 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform bool use_vertex_color;
uniform bool draw_fog;
uniform bool unfogged;
uniform float fog_start;
uniform float fog_end;
uniform vec3 fog_color;
uniform vec3 camera;
uniform bool unlit;
uniform bool exterior_lit;
uniform vec3 exterior_light_dir;
uniform vec3 exterior_diffuse_color;
uniform vec3 exterior_ambient_color;
uniform vec3 ambient_color;
uniform float alpha_test;
uniform int shader_id;
in vec3 f_position;
in vec3 f_normal;
in vec2 f_texcoord;
in vec2 f_texcoord_2;
in vec4 f_vertex_color;
out vec4 out_color;
vec3 lighting(vec3 material)
{
vec3 light_color = vec3(1.);
vec3 vertex_color = use_vertex_color ? f_vertex_color.rgb : vec3(0.);
if(unlit)
{
light_color = vertex_color + (exterior_lit ? exterior_ambient_color : ambient_color);
}
else if(exterior_lit)
{
vec3 ambient = exterior_ambient_color + vertex_color.rgb;
light_color = vec3(clamp (exterior_diffuse_color * max(dot(f_normal, exterior_light_dir), 0.0), 0.0, 1.0)) + ambient;
}
else
{
light_color = ambient_color + vertex_color.rgb;
}
return material * light_color;
}
void main()
{
float dist_from_camera = distance(camera, f_position);
bool fog = draw_fog && !unfogged;
if(fog && dist_from_camera >= fog_end)
{
out_color = vec4(fog_color, 1.);
return;
}
vec4 tex = texture(tex1, f_texcoord);
vec4 tex_2 = texture(tex2, f_texcoord_2);
if(tex.a < alpha_test)
{
discard;
}
vec4 vertex_color = vec4(0., 0., 0., 1.f);
vec3 light_color = vec3(1.);
if(use_vertex_color)
{
vertex_color = f_vertex_color;
}
// see: https://github.com/Deamon87/WebWowViewerCpp/blob/master/wowViewerLib/src/glsl/wmoShader.glsl
if(shader_id == 3) // Env
{
vec3 env = tex_2.rgb * tex.rgb;
out_color = vec4(lighting(tex.rgb) + env, 1.);
}
else if(shader_id == 5) // EnvMetal
{
vec3 env = tex_2.rgb * tex.rgb * tex.a;
out_color = vec4(lighting(tex.rgb) + env, 1.);
}
else if(shader_id == 6) // TwoLayerDiffuse
{
vec3 layer2 = mix(tex.rgb, tex_2.rgb, tex_2.a);
out_color = vec4(lighting(mix(layer2, tex.rgb, vertex_color.a)), 1.);
}
else // default shader, used for shader_id 0,1,2,4 (Diffuse, Specular, Metal, Opaque)
{
out_color = vec4(lighting(tex.rgb), 1.);
}
if(fog && (dist_from_camera >= fog_end * fog_start))
{
float start = fog_end * fog_start;
float alpha = (dist_from_camera - start) / (fog_end - start);
out_color.rgb = mix(out_color.rgb, fog_color, alpha);
}
if(out_color.a < alpha_test)
{
discard;
}
}

44
src/glsl/wmo_vert.glsl Normal file
View File

@@ -0,0 +1,44 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#version 330 core
in vec4 position;
in vec3 normal;
in vec4 vertex_color;
in vec2 texcoord;
in vec2 texcoord_2;
out vec3 f_position;
out vec3 f_normal;
out vec2 f_texcoord;
out vec2 f_texcoord_2;
out vec4 f_vertex_color;
uniform mat4 model_view;
uniform mat4 projection;
uniform mat4 transform;
uniform int shader_id;
void main()
{
vec4 pos = transform * position;
vec4 view_space_pos = model_view * pos;
gl_Position = projection * view_space_pos;
f_position = pos.xyz;
f_normal = mat3(transform) * normal;
// Env and EnvMetal
if(shader_id == 3 || shader_id == 5)
{
f_texcoord = texcoord;
f_texcoord_2 = reflect(normalize(view_space_pos.xyz), f_normal).xy;
}
else
{
f_texcoord = texcoord;
f_texcoord_2 = texcoord_2;
}
f_vertex_color = vertex_color;
}

62
src/math/bounding_box.cpp Normal file
View File

@@ -0,0 +1,62 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/bounding_box.hpp>
namespace
{
math::vector_3d min_per_dimension(std::vector<math::vector_3d> const& points)
{
auto min(math::vector_3d::max());
for (auto const& point : points)
{
min = math::min(min, point);
}
return min;
}
math::vector_3d max_per_dimension(std::vector<math::vector_3d> const& points)
{
auto max(math::vector_3d::min());
for (auto const& point : points)
{
max = math::max(max, point);
}
return max;
}
}
namespace math
{
aabb::aabb(math::vector_3d const& min_, math::vector_3d const& max_)
: min(min_)
, max(max_)
{
}
aabb::aabb(std::vector<math::vector_3d> points)
: aabb(min_per_dimension(points), max_per_dimension(points))
{
}
//! \todo Optimize: iterate lazily.
std::vector<math::vector_3d> aabb::all_corners() const
{
return box_points(min, max);
}
std::vector<math::vector_3d> box_points(math::vector_3d const& box_min, math::vector_3d const& box_max)
{
std::vector<math::vector_3d> points;
points.emplace_back(box_max.x, box_max.y, box_max.z);
points.emplace_back(box_max.x, box_max.y, box_min.z);
points.emplace_back(box_max.x, box_min.y, box_max.z);
points.emplace_back(box_max.x, box_min.y, box_min.z);
points.emplace_back(box_min.x, box_max.y, box_max.z);
points.emplace_back(box_min.x, box_max.y, box_min.z);
points.emplace_back(box_min.x, box_min.y, box_max.z);
points.emplace_back(box_min.x, box_min.y, box_min.z);
return points;
}
}

23
src/math/bounding_box.hpp Normal file
View File

@@ -0,0 +1,23 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/vector_3d.hpp>
#include <vector>
namespace math
{
struct aabb
{
aabb(math::vector_3d const& min_, math::vector_3d const& max_);
aabb(std::vector<math::vector_3d> points);
std::vector<math::vector_3d> all_corners() const;
math::vector_3d min;
math::vector_3d max;
};
std::vector<math::vector_3d> box_points(math::vector_3d const& box_min, math::vector_3d const& box_max);
}

9
src/math/constants.hpp Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
namespace math
{
namespace constants
{
constexpr float const pi = 3.141592653f;
}
}

95
src/math/frustum.cpp Normal file
View File

@@ -0,0 +1,95 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/frustum.hpp>
#include <vector>
namespace math
{
frustum::frustum (matrix_4x4 const& matrix)
{
const vector_4d column_0 (matrix.column<0>());
const vector_4d column_1 (matrix.column<1>());
const vector_4d column_2 (matrix.column<2>());
const vector_4d column_3 (matrix.column<3>());
_planes[RIGHT] = column_3 - column_0;
_planes[LEFT] = column_3 + column_0;
_planes[TOP] = column_3 - column_1;
_planes[BOTTOM] = column_3 + column_1;
_planes[BACK] = column_3 - column_2;
_planes[FRONT] = column_3 + column_2;
}
bool frustum::contains (const vector_3d& point) const
{
for (auto const& plane : _planes)
{
if (plane.normal() * point <= -plane.distance())
{
return false;
}
}
return true;
}
bool frustum::intersects (const std::vector<vector_3d>& intersect_points) const
{
for (auto const& plane : _planes)
{
for (auto const& point : intersect_points)
{
if (plane.normal() * point > -plane.distance())
{
//! \note C does not know how to continue out of two loops otherwise.
goto intersects_next_side;
}
}
return false;
intersects_next_side:;
}
return true;
}
bool frustum::intersects ( const vector_3d& v1
, const vector_3d& v2
) const
{
std::vector<vector_3d> points;
points.emplace_back (v1.x, v1.y, v1.z);
points.emplace_back (v1.x, v1.y, v2.z);
points.emplace_back (v1.x, v2.y, v1.z);
points.emplace_back (v1.x, v2.y, v2.z);
points.emplace_back (v2.x, v1.y, v1.z);
points.emplace_back (v2.x, v1.y, v2.z);
points.emplace_back (v2.x, v2.y, v1.z);
points.emplace_back (v2.x, v2.y, v2.z);
return intersects (points);
}
bool frustum::intersectsSphere ( const vector_3d& position
, const float& radius
) const
{
for (auto const& plane : _planes)
{
const float distance ( plane.normal() * position
+ plane.distance()
);
if (distance < -radius)
{
return false;
}
else if (std::abs (distance) < radius)
{
return true;
}
}
return true;
}
}

73
src/math/frustum.hpp Normal file
View File

@@ -0,0 +1,73 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/vector_3d.hpp>
#include <math/vector_4d.hpp>
#include <math/matrix_4x4.hpp>
#include <array>
#include <vector>
namespace math
{
class frustum
{
enum SIDES
{
RIGHT,
LEFT,
BOTTOM,
TOP,
BACK,
FRONT,
SIDES_MAX,
};
class plane
{
public:
plane() = default;
plane (vector_4d const& vec)
: _normal (vec.xyz())
, _distance (vec.w)
{
normalize();
}
void normalize()
{
const float recip (1.0f / _normal.length());
_normal *= recip;
_distance *= recip;
}
const float& distance() const
{
return _distance;
}
const vector_3d& normal() const
{
return _normal;
}
private:
vector_3d _normal;
float _distance;
};
std::array<plane, SIDES_MAX> _planes;
public:
frustum (matrix_4x4 const& matrix);
bool contains (const vector_3d& point) const;
bool intersects (const std::vector<vector_3d>& intersect_points) const;
bool intersects ( const vector_3d& v1
, const vector_3d& v2
) const;
bool intersectsSphere ( const vector_3d& position
, const float& radius
) const;
};
}

View File

@@ -0,0 +1,54 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/trig.hpp>
namespace math
{
namespace interpolation
{
template<typename T>
static T linear (const float& percentage, const T& start, const T& end)
{
return T (start * (1.0f - percentage) + end * percentage);
}
template<typename T>
static T slerp (const float& percentage, const T& start, const T& end)
{
const float dot (start * end);
if (std::abs (dot) > 0.9995f)
{
//! \note Don't call linear here, as this will recurse with quaternions.
return T (start * (1.0f - percentage) + end * percentage);
}
radians const a (acos (dot)._ * percentage);
return T (start * cos (a) + T (end - start * dot).normalize() * sin (a));
}
template<typename T>
static T hermite ( const float& percentage
, const T& start
, const T& end
, const T& in
, const T& out
)
{
const float percentage_2 (percentage * percentage);
const float percentage_3 (percentage_2 * percentage);
const float _2_percentage_3 (2.0f * percentage_3);
const float _3_percentage_2 (3.0f * percentage_2);
const float h1 (_2_percentage_3 - _3_percentage_2 + 1.0f);
const float h2 (_3_percentage_2 - _2_percentage_3);
const float h3 (percentage_3 - 2.0f * percentage_2 + percentage);
const float h4 (percentage_3 - percentage_2);
return T (start * h1 + end * h2 + in * h3 + out * h4);
}
}
}

211
src/math/matrix_4x4.cpp Normal file
View File

@@ -0,0 +1,211 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/matrix_4x4.hpp>
#include <math/quaternion.hpp>
#include <math/vector_3d.hpp>
#include <cmath>
#include <cstring> // memcpy, memset
namespace math
{
matrix_4x4::uninitialized_t matrix_4x4::uninitialized;
matrix_4x4::zero_t matrix_4x4::zero;
matrix_4x4::unit_t matrix_4x4::unit;
matrix_4x4::translation_t matrix_4x4::translation;
matrix_4x4::scale_t matrix_4x4::scale;
matrix_4x4::rotation_t matrix_4x4::rotation;
matrix_4x4::rotation_xyz_t matrix_4x4::rotation_xyz;
matrix_4x4::rotation_yzx_t matrix_4x4::rotation_yzx;
matrix_4x4::matrix_4x4 (rotation_t, quaternion const& q)
{
_m[0][0] = 1.0f - 2.0f * q.y * q.y - 2.0f * q.z * q.z;
_m[0][1] = 2.0f * q.x * q.y + 2.0f * q.w * q.z;
_m[0][2] = 2.0f * q.x * q.z - 2.0f * q.w * q.y;
_m[0][3] = 0.0f;
_m[1][0] = 2.0f * q.x * q.y - 2.0f * q.w * q.z;
_m[1][1] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.z * q.z;
_m[1][2] = 2.0f * q.y * q.z + 2.0f * q.w * q.x;
_m[1][3] = 0.0f;
_m[2][0] = 2.0f * q.x * q.z + 2.0f * q.w * q.y;
_m[2][1] = 2.0f * q.y * q.z - 2.0f * q.w * q.x;
_m[2][2] = 1.0f - 2.0f * q.x * q.x - 2.0f * q.y * q.y;
_m[2][3] = 0.0f;
_m[3][0] = 0.0f;
_m[3][1] = 0.0f;
_m[3][2] = 0.0f;
_m[3][3] = 1.0f;
}
namespace
{
enum axis
{
x = 0,
y = 1,
z = 2,
num_axis,
};
template<axis a>
matrix_4x4 rotate_axis (radians angle)
{
static std::size_t const i_indices[num_axis] = {2, 2, 1};
static std::size_t const j_indices[num_axis] = {1, 0, 0};
std::size_t const i_index (i_indices[a]);
std::size_t const j_index (j_indices[a]);
float const cosV (cos (angle));
float const sinV (sin (angle));
matrix_4x4 mat (matrix_4x4::unit);
mat (j_index, j_index, cosV);
mat (j_index, i_index, sinV);
mat (i_index, j_index, -sinV);
mat (i_index, i_index, cosV);
return mat;
}
}
matrix_4x4::matrix_4x4 (rotation_xyz_t, degrees::vec3 const& angle)
: matrix_4x4 (unit)
{
*this *= rotate_axis<x> (angle.x);
*this *= rotate_axis<y> (angle.y);
*this *= rotate_axis<z> (angle.z);
}
matrix_4x4::matrix_4x4 (rotation_yzx_t, degrees::vec3 const& angle)
: matrix_4x4 (unit)
{
*this *= rotate_axis<y> (angle.y);
*this *= rotate_axis<z> (angle.z);
*this *= rotate_axis<x> (angle.x);
}
vector_3d matrix_4x4::operator* (vector_3d const& v) const
{
return { _m[0][0] * v[0] + _m[0][1] * v[1] + _m[0][2] * v[2] + _m[0][3]
, _m[1][0] * v[0] + _m[1][1] * v[1] + _m[1][2] * v[2] + _m[1][3]
, _m[2][0] * v[0] + _m[2][1] * v[1] + _m[2][2] * v[2] + _m[2][3]
};
}
vector_4d matrix_4x4::operator* (const vector_4d& v) const
{
return { _m[0][0] * v[0] + _m[0][1] * v[1] + _m[0][2] * v[2] + _m[0][3] * v[3]
, _m[1][0] * v[0] + _m[1][1] * v[1] + _m[1][2] * v[2] + _m[1][3] * v[3]
, _m[2][0] * v[0] + _m[2][1] * v[1] + _m[2][2] * v[2] + _m[2][3] * v[3]
, _m[3][0] * v[0] + _m[3][1] * v[1] + _m[3][2] * v[2] + _m[3][3] * v[3]
};
}
matrix_4x4 matrix_4x4::operator* (matrix_4x4 const& other) const
{
return { _m[0][0] * other._m[0][0] + _m[0][1] * other._m[1][0] + _m[0][2] * other._m[2][0] + _m[0][3] * other._m[3][0]
, _m[0][0] * other._m[0][1] + _m[0][1] * other._m[1][1] + _m[0][2] * other._m[2][1] + _m[0][3] * other._m[3][1]
, _m[0][0] * other._m[0][2] + _m[0][1] * other._m[1][2] + _m[0][2] * other._m[2][2] + _m[0][3] * other._m[3][2]
, _m[0][0] * other._m[0][3] + _m[0][1] * other._m[1][3] + _m[0][2] * other._m[2][3] + _m[0][3] * other._m[3][3]
, _m[1][0] * other._m[0][0] + _m[1][1] * other._m[1][0] + _m[1][2] * other._m[2][0] + _m[1][3] * other._m[3][0]
, _m[1][0] * other._m[0][1] + _m[1][1] * other._m[1][1] + _m[1][2] * other._m[2][1] + _m[1][3] * other._m[3][1]
, _m[1][0] * other._m[0][2] + _m[1][1] * other._m[1][2] + _m[1][2] * other._m[2][2] + _m[1][3] * other._m[3][2]
, _m[1][0] * other._m[0][3] + _m[1][1] * other._m[1][3] + _m[1][2] * other._m[2][3] + _m[1][3] * other._m[3][3]
, _m[2][0] * other._m[0][0] + _m[2][1] * other._m[1][0] + _m[2][2] * other._m[2][0] + _m[2][3] * other._m[3][0]
, _m[2][0] * other._m[0][1] + _m[2][1] * other._m[1][1] + _m[2][2] * other._m[2][1] + _m[2][3] * other._m[3][1]
, _m[2][0] * other._m[0][2] + _m[2][1] * other._m[1][2] + _m[2][2] * other._m[2][2] + _m[2][3] * other._m[3][2]
, _m[2][0] * other._m[0][3] + _m[2][1] * other._m[1][3] + _m[2][2] * other._m[2][3] + _m[2][3] * other._m[3][3]
, _m[3][0] * other._m[0][0] + _m[3][1] * other._m[1][0] + _m[3][2] * other._m[2][0] + _m[3][3] * other._m[3][0]
, _m[3][0] * other._m[0][1] + _m[3][1] * other._m[1][1] + _m[3][2] * other._m[2][1] + _m[3][3] * other._m[3][1]
, _m[3][0] * other._m[0][2] + _m[3][1] * other._m[1][2] + _m[3][2] * other._m[2][2] + _m[3][3] * other._m[3][2]
, _m[3][0] * other._m[0][3] + _m[3][1] * other._m[1][3] + _m[3][2] * other._m[2][3] + _m[3][3] * other._m[3][3]
};
}
std::vector<math::vector_3d> matrix_4x4::operator*
(std::vector<math::vector_3d> points) const
{
return apply ( [&] (math::vector_3d const& point)
{
return *this * point;
}
, points
);
}
namespace
{
float minor_size (matrix_4x4 const& mat, std::size_t x, std::size_t y)
{
float s[3][3];
for (std::size_t j (0), v (0); j < 4; ++j)
{
if (j != y)
{
for (std::size_t i (0), u (0); i < 4; ++i)
{
if (i != x)
{
s[v][u++] = mat (j, i);
}
}
++v;
}
}
#define SUB(a,b) (s[1][a] * s[2][b] - s[2][a] * s[1][b])
return s[0][0] * SUB (1,2) - s[0][1] * SUB (0,2) + s[0][2] * SUB (0,1);
#undef SUB
}
}
matrix_4x4 matrix_4x4::adjoint() const
{
return { minor_size (*this, 0, 0), -minor_size (*this, 0, 1), minor_size (*this, 0, 2), -minor_size (*this, 0, 3)
, -minor_size (*this, 1, 0), minor_size (*this, 1, 1), -minor_size (*this, 1, 2), minor_size (*this, 1, 3)
, minor_size (*this, 2, 0), -minor_size (*this, 2, 1), minor_size (*this, 2, 2), -minor_size (*this, 2, 3)
, -minor_size (*this, 3, 0), minor_size (*this, 3, 1), -minor_size (*this, 3, 2), minor_size (*this, 3, 3)
};
}
matrix_4x4& matrix_4x4::operator* (float f)
{
for (std::size_t i (0); i < 16; ++i)
{
_data[i] *= f;
}
return *this;
}
matrix_4x4& matrix_4x4::operator/ (float f)
{
return *this * (1 / f);
}
namespace
{
float determinant (matrix_4x4 const& mat)
{
#define SUB(a, b) (mat (2, a) * mat (3, b) - mat (3, a) * mat (2, b))
return mat (0, 0) * (mat (1, 1) * SUB (2, 3) - mat (1, 2) * SUB (1, 3) + mat (1, 3) * SUB (1, 2))
- mat (0, 1) * (mat (1, 0) * SUB (2, 3) - mat (1, 2) * SUB (0, 3) + mat (1, 3) * SUB (0, 2))
+ mat (0, 2) * (mat (1, 0) * SUB (1, 3) - mat (1, 1) * SUB (0, 3) + mat (1, 3) * SUB (0, 1))
- mat (0, 3) * (mat (1, 0) * SUB (1, 2) - mat (1, 1) * SUB (0, 2) + mat (1, 2) * SUB (0, 1));
#undef SUB
}
}
matrix_4x4 matrix_4x4::inverted() const
{
return adjoint() / determinant (*this);
}
matrix_4x4 matrix_4x4::transposed() const
{
return { _m[0][0], _m[1][0], _m[2][0], _m[3][0]
, _m[0][1], _m[1][1], _m[2][1], _m[3][1]
, _m[0][2], _m[1][2], _m[2][2], _m[3][2]
, _m[0][3], _m[1][3], _m[2][3], _m[3][3]
};
}
}

136
src/math/matrix_4x4.hpp Normal file
View File

@@ -0,0 +1,136 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/quaternion.hpp>
#include <math/trig.hpp>
#include <algorithm>
namespace math
{
struct vector_3d;
struct matrix_4x4
{
public:
static struct uninitialized_t {} uninitialized;
matrix_4x4 (uninitialized_t) {}
static struct zero_t {} zero;
matrix_4x4 (zero_t)
{
std::fill (_data, _data + 16, 0.f);
}
static struct unit_t {} unit;
matrix_4x4 (unit_t) : matrix_4x4 (zero)
{
_m[0][0] = _m[1][1] = _m[2][2] = _m[3][3] = 1.0f;
}
matrix_4x4 ( float m00, float m01, float m02, float m03
, float m10, float m11, float m12, float m13
, float m20, float m21, float m22, float m23
, float m30, float m31, float m32, float m33
)
{
_m[0][0] = m00; _m[0][1] = m01; _m[0][2] = m02; _m[0][3] = m03;
_m[1][0] = m10; _m[1][1] = m11; _m[1][2] = m12; _m[1][3] = m13;
_m[2][0] = m20; _m[2][1] = m21; _m[2][2] = m22; _m[2][3] = m23;
_m[3][0] = m30; _m[3][1] = m31; _m[3][2] = m32; _m[3][3] = m33;
}
static struct translation_t {} translation;
matrix_4x4 (translation_t, vector_3d const& tr)
: matrix_4x4 ( 1.0f, 0.0f, 0.0f, tr.x
, 0.0f, 1.0f, 0.0f, tr.y
, 0.0f, 0.0f, 1.0f, tr.z
, 0.0f, 0.0f, 0.0f, 1.0f
)
{}
static struct scale_t {} scale;
matrix_4x4 (scale_t, vector_3d const& sc)
: matrix_4x4 ( sc.x, 0.0f, 0.0f, 0.0f
, 0.0f, sc.y, 0.0f, 0.0f
, 0.0f, 0.0f, sc.z, 0.0f
, 0.0f, 0.0f, 0.0f, 1.0f
)
{}
matrix_4x4 (scale_t, float sc)
: matrix_4x4 (scale, {sc, sc, sc})
{}
static struct rotation_t {} rotation;
matrix_4x4 (rotation_t, quaternion const&);
static struct rotation_xyz_t {} rotation_xyz;
matrix_4x4 (rotation_xyz_t, degrees::vec3 const&);
static struct rotation_yzx_t {} rotation_yzx;
matrix_4x4 (rotation_yzx_t, degrees::vec3 const&);
float operator() (std::size_t const& j, std::size_t const& i) const
{
return _m[j][i];
}
float operator() (std::size_t const& j, std::size_t const& i, float value)
{
return _m[j][i] = value;
}
vector_3d operator* (vector_3d const&) const;
vector_4d operator* (vector_4d const&) const;
quaternion operator* (quaternion const& q) const
{
return quaternion {*this * static_cast<vector_4d> (q)};
}
matrix_4x4 operator* (matrix_4x4 const&) const;
std::vector<math::vector_3d> operator*(std::vector<math::vector_3d> points) const;
matrix_4x4& operator* (float);
matrix_4x4& operator/ (float);
matrix_4x4 adjoint() const;
matrix_4x4 inverted() const;
matrix_4x4 transposed() const;
inline matrix_4x4& operator*= (matrix_4x4 const& p)
{
return *this = operator* (p);
}
inline operator float*()
{
return _data;
}
inline operator const float*() const
{
return _data;
}
template<std::size_t i>
vector_4d column() const
{
return {_m[0][i], _m[1][i], _m[2][i], _m[3][i]};
}
bool operator== (matrix_4x4 const& rhs)
{
for (std::size_t i (0); i < 16; ++i)
{
if (_m[i] != rhs._m[i])
{
return false;
}
}
return true;
}
union
{
float _m[4][4];
float _data[16];
};
};
}

75
src/math/projection.hpp Normal file
View File

@@ -0,0 +1,75 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/matrix_4x4.hpp>
#include <math/trig.hpp>
#include <math/vector_3d.hpp>
namespace math
{
inline matrix_4x4 perspective (math::degrees fovy, float aspect, float zNear, float zFar)
{
// assuming
// math::vector_3d lower_left_clipping_plane (left, bottom, -nearVal);
// math::vector_3d upper_right_clipping_plane (right, top, -nearVal);
// math::vector_3d eye (0, 0, 0);
// float clipping_plane_distance (zFar - zNear);
// with
float const ymax (zNear * math::tan (fovy) / 2.0f);
// float left (-ymax * aspect);
// float right (ymax * aspect);
// float bottom (-ymax);
// float top (ymax);
// float nearVal (zNear);
// float farVal (zFar);
// multiply matrix by
// math::matrix_4x4 frustum ( 2 * nearVal / (right - left), 0.0f, (right + left) / (right - left), 0.0f
// , 0.0f, 2 * nearVal / (top - bottom), (top + bottom) / (top - bottom), 0.0f
// , 0.0f, 0.0f, - (farVal + nearVal) / (farVal - nearVal), - 2 * farVal * nearVal / (farVal - nearVal)
// , 0.0f, 0.0f, -1.0f, 0.0f
// );
// with optimized values
return { zNear / (ymax * aspect), 0.0f, 0.0f, 0.0f
, 0.0f, zNear / ymax, 0.0f, 0.0f
, 0.0f, 0.0f, -(zFar + zNear) / (zFar - zNear), -2 * zFar * zNear / (zFar - zNear)
, 0.0f, 0.0f, -1.0f, 0.0f
};
}
inline matrix_4x4 ortho(float left, float right, float bottom, float top, float z_near, float z_far)
{
float v0 = 2.f / (right - left);
float v1 = 2.f / (top - bottom);
float v2 = -2.f / (z_far - z_near);
float tx = -(right + left) / (right - left);
float ty = -(top + bottom) / (top - bottom);
float tz = -(z_far + z_near) / (z_far - z_near);
return { v0, 0.f, 0.f, tx
, 0.f, v1, 0.f, ty
, 0.f, 0.f, v2, tz
, 0.f, 0.f, 0.f, 1.f
};
}
inline matrix_4x4 look_at ( vector_3d const& eye
, vector_3d const& center
, vector_3d const& up
)
{
vector_3d const z ((eye - center).normalized());
vector_3d const x ((up % z).normalized());
vector_3d const y ((z % x).normalized());
return { x.x, x.y, x.z, x * -eye
, y.x, y.y, y.z, y * -eye
, z.x, z.y, z.z, z * -eye
, 0.f, 0.f, 0.f, 1.f
};
}
}

60
src/math/quaternion.hpp Normal file
View File

@@ -0,0 +1,60 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/interpolation.hpp>
#include <math/vector_4d.hpp>
#include <cstdint>
namespace math
{
struct vector_3d;
//! \note Actually, a typedef would be enough.
struct quaternion : public vector_4d
{
public:
quaternion()
: quaternion (0.f, 0.f, 0.f, 1.0f)
{}
quaternion ( const float& x
, const float& y
, const float& z
, const float& w
)
: vector_4d (x, y, z, w)
{ }
explicit quaternion (const vector_4d& v)
: vector_4d (v)
{ }
quaternion (const vector_3d& v, const float w)
: vector_4d (v, w)
{ }
};
//! \note "linear" interpolation for quaternions should be slerp by default.
namespace interpolation
{
template<>
inline quaternion linear ( const float& percentage
, const quaternion& start
, const quaternion& end
)
{
return slerp (percentage, start, end);
}
}
//! \note In WoW 2.0+ Blizzard is now storing rotation data in 16bit values instead of 32bit. I don't really understand why as its only a very minor saving in model sizes and adds extra overhead in processing the models. Need this structure to read the data into.
struct packed_quaternion
{
int16_t x;
int16_t y;
int16_t z;
int16_t w;
};
}

90
src/math/ray.cpp Normal file
View File

@@ -0,0 +1,90 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/ray.hpp>
#include <limits>
namespace math
{
boost::optional<float> ray::intersect_bounds
(vector_3d const& min, vector_3d const& max) const
{
float tmin (std::numeric_limits<float>::lowest());
float tmax (std::numeric_limits<float>::max());
if (_direction.x != 0.0f)
{
float const tx1 ((min.x - _origin.x) / _direction.x);
float const tx2 ((max.x - _origin.x) / _direction.x);
tmin = std::max (tmin, std::min (tx1, tx2));
tmax = std::min (tmax, std::max (tx1, tx2));
}
if (_direction.y != 0.0f)
{
float const ty1 ((min.y - _origin.y) / _direction.y);
float const ty2 ((max.y - _origin.y) / _direction.y);
tmin = std::max (tmin, std::min (ty1, ty2));
tmax = std::min (tmax, std::max (ty1, ty2));
}
if (_direction.z != 0.0f)
{
float const tz1 ((min.z - _origin.z) / _direction.z);
float const tz2 ((max.z - _origin.z) / _direction.z);
tmin = std::max (tmin, std::min (tz1, tz2));
tmax = std::min (tmax, std::max (tz1, tz2));
}
if (tmax >= tmin)
{
return tmin;
}
return boost::none;
}
boost::optional<float> ray::intersect_triangle
(vector_3d const& v0, vector_3d const& v1, vector_3d const& v2) const
{
vector_3d const e1 (v1 - v0);
vector_3d const e2 (v2 - v0);
vector_3d const P (_direction % e2);
float const det (e1 * P);
if (det == 0.0f)
{
return boost::none;
}
vector_3d const T (_origin - v0);
float const u ((T * P) / det);
if (u < 0.0f || u > 1.0f)
{
return boost::none;
}
vector_3d const Q (T % e1);
float const v ((_direction * Q) / det);
if (v < 0.0f || u + v > 1.0f)
{
return boost::none;
}
float const t ((e2 * Q) / det);
if (t > std::numeric_limits<float>::min())
{
return t;
}
return boost::none;
}
}

39
src/math/ray.hpp Normal file
View File

@@ -0,0 +1,39 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/matrix_4x4.hpp>
#include <math/vector_3d.hpp>
#include <boost/optional/optional.hpp>
namespace math
{
struct ray
{
ray (vector_3d origin, vector_3d const& direction)
: _origin (std::move (origin))
, _direction (direction.normalized())
{}
ray (matrix_4x4 const& transform, ray const& other)
: ray ( (transform * math::vector_4d (other._origin, 1.0)).xyz()
, (transform * math::vector_4d (other._direction, 0.0)).xyz()
)
{}
boost::optional<float> intersect_bounds
(vector_3d const& _min, vector_3d const& _max) const;
boost::optional<float> intersect_triangle
(vector_3d const& _v0, vector_3d const& _v1, vector_3d const& _v2) const;
vector_3d position (float distance) const
{
return _origin + _direction * distance;
}
private:
vector_3d _origin;
vector_3d _direction;
};
}

61
src/math/trig.hpp Normal file
View File

@@ -0,0 +1,61 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/constants.hpp>
#include <math/vector_3d.hpp>
#include <cmath>
namespace math
{
struct radians;
struct degrees
{
explicit degrees (float x) : _ (x) {}
degrees (radians);
float _;
using vec3 = vector_3d_base<degrees>;
};
struct radians
{
explicit radians (float x) : _ (x) {}
radians (degrees);
float _;
using vec3 = vector_3d_base<radians>;
};
inline degrees::degrees (radians x) : _ (x._ * 180.0f / math::constants::pi) {}
inline radians::radians (degrees x) : _ (x._ * math::constants::pi / 180.0f) {}
inline float sin (radians x)
{
return std::sin (x._);
}
inline float cos (radians x)
{
return std::cos (x._);
}
inline float tan (radians x)
{
return std::tan (x._);
}
inline radians asin (float x)
{
return radians {std::asin (x)};
}
inline radians acos (float x)
{
return radians {std::acos (x)};
}
inline radians atan2 (float y, float x)
{
return radians {std::atan2 (y, x)};
}
}

15
src/math/vector_2d.cpp Normal file
View File

@@ -0,0 +1,15 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <math/vector_2d.hpp>
#include <math/trig.hpp>
namespace math
{
void rotate (float x0, float y0, float* x, float* y, radians angle)
{
const float xa (*x - x0);
const float ya (*y - y0);
*x = xa * cos (angle) - ya * sin (angle) + x0;
*y = xa * sin (angle) + ya * cos (angle) + y0;
}
}

64
src/math/vector_2d.hpp Normal file
View File

@@ -0,0 +1,64 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/trig.hpp>
#include <ostream>
#include <tuple>
namespace math
{
struct vector_2d
{
union
{
float _data[2];
struct
{
float x;
float y;
};
};
vector_2d() : vector_2d (0.f, 0.f) {}
vector_2d (float x_, float y_)
: x (x_)
, y (y_)
{}
inline operator float*()
{
return _data;
}
inline operator float const*() const
{
return _data;
}
vector_2d operator* (float factor) const
{
return {x * factor, y * factor};
}
vector_2d operator+ (vector_2d const& other) const
{
return {x + other.x, y + other.y};
}
bool operator== (vector_2d const& rhs) const
{
return std::tie (x, y) == std::tie (rhs.x, rhs.y);
}
friend std::ostream& operator<< (std::ostream& os, vector_2d const& x)
{
return os << x.x << ", " << x.y;
}
};
void rotate (float x0, float y0, float* x, float* y, radians);
inline vector_2d rotate (vector_2d const& around, vector_2d point, radians angle)
{
rotate (around.x, around.y, &point.x, &point.y, angle);
return point;
}
}

218
src/math/vector_3d.hpp Normal file
View File

@@ -0,0 +1,218 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <algorithm>
#include <cmath>
#include <limits>
#include <ostream>
#include <tuple>
#include <vector>
namespace math
{
template<typename T>
struct vector_3d_base
{
union
{
T _data[3];
struct
{
T x;
T y;
T z;
};
};
vector_3d_base<T>() : vector_3d_base (0.f, 0.f, 0.f) {}
vector_3d_base<T> (T x_, T y_, T z_)
: x (x_)
, y (y_)
, z (z_)
{}
inline static vector_3d_base<T> min()
{
return {std::numeric_limits<T>::lowest(), std::numeric_limits<T>::lowest(), std::numeric_limits<T>::lowest()};
}
inline static vector_3d_base<T> max()
{
return {std::numeric_limits<T>::max(), std::numeric_limits<T>::max(), std::numeric_limits<T>::max()};
}
vector_3d_base<T> (vector_3d_base<T> const&) = default;
vector_3d_base<T> (vector_3d_base<T>&&) = default;
vector_3d_base<T>& operator= (vector_3d_base<T> const&) = default;
vector_3d_base<T>& operator= (vector_3d_base<T>&&) = default;
inline vector_3d_base<T> operator+ (const vector_3d_base<T> &v) const
{
return vector_3d_base<T> (x + v.x, y + v.y, z + v.z);
}
inline vector_3d_base<T> operator- (const vector_3d_base<T> &v) const
{
return vector_3d_base<T> (x - v.x, y - v.y, z - v.z);
}
inline vector_3d_base<T> operator-() const
{
return vector_3d_base<T> (-x, -y, -z);
}
inline T operator* (const vector_3d_base<T> &v) const
{
return x * v.x + y * v.y + z * v.z;
}
inline T operator/ (const vector_3d_base<T>& v) const
{
return x / v.x + y / v.y + z / v.z;
}
inline vector_3d_base<T> operator* (const T& d) const
{
return vector_3d_base<T> (x * d, y * d, z * d);
}
inline vector_3d_base<T> operator/ (const T& d) const
{
return vector_3d_base<T>(x / d, y / d, z / d);
}
friend vector_3d_base<T> operator* (const T& d, const vector_3d_base<T>& v)
{
return v * d;
}
friend vector_3d_base<T> operator/ (const T& d, const vector_3d_base<T>& v)
{
return v / d;
}
inline vector_3d_base<T> operator% (const vector_3d_base<T>& v) const
{
return vector_3d_base<T> ( y * v.z - z * v.y
, z * v.x - x * v.z
, x * v.y - y * v.x
);
}
inline vector_3d_base<T>& operator+= (const vector_3d_base<T>& v)
{
x += v.x;
y += v.y;
z += v.z;
return *this;
}
inline vector_3d_base<T>& operator-= (const vector_3d_base<T>& v)
{
x -= v.x;
y -= v.y;
z -= v.z;
return *this;
}
inline vector_3d_base<T>& operator*= (T d)
{
x *= d;
y *= d;
z *= d;
return *this;
}
inline vector_3d_base<T>& operator/= (T d)
{
x /= d;
y /= d;
z /= d;
return *this;
}
inline T length_squared() const
{
return x * x + y * y + z * z;
}
inline T length() const
{
return std::sqrt (length_squared());
}
inline vector_3d_base<T>& normalize()
{
return operator *= (1.0f / length());
}
vector_3d_base<T> normalized() const
{
return *this * (1.0f / length());
}
inline operator T*()
{
return _data;
}
inline operator const T*() const
{
return _data;
}
inline bool is_inside_of (const vector_3d_base<T>& a, const vector_3d_base<T>& b ) const
{
return a.x < x && b.x > x
&& a.y < y && b.y > y
&& a.z < z && b.z > z;
}
bool operator== (vector_3d_base<T> const& rhs) const
{
return std::tie (x, y, z) == std::tie (rhs.x, rhs.y, rhs.z);
}
friend std::ostream& operator<< (std::ostream& os, vector_3d_base<T> const& x)
{
return os << x.x << ", " << x.y << ", " << x.z;
}
};
template<typename T>
inline vector_3d_base<T> min (vector_3d_base<T> const& lhs, vector_3d_base<T> const& rhs)
{
return { std::min (lhs.x, rhs.x)
, std::min (lhs.y, rhs.y)
, std::min (lhs.z, rhs.z)
};
}
template<typename T>
inline vector_3d_base<T> max (vector_3d_base<T> const& lhs, vector_3d_base<T> const& rhs)
{
return { std::max (lhs.x, rhs.x)
, std::max (lhs.y, rhs.y)
, std::max (lhs.z, rhs.z)
};
}
struct vector_3d : public vector_3d_base<float>
{
public:
using vector_3d_base<float>::vector_3d_base;
vector_3d (vector_3d_base<float> x) : vector_3d_base<float> (std::move (x)) {}
vector_3d() : vector_3d_base<float>() {}
};
template<typename Fun>
std::vector<math::vector_3d> apply
(Fun&& fun, std::vector<math::vector_3d> points)
{
for (auto& point : points)
{
point = fun (point);
}
return points;
}
}

113
src/math/vector_4d.hpp Normal file
View File

@@ -0,0 +1,113 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/vector_3d.hpp>
namespace math
{
struct vector_4d
{
union
{
float _data[4];
struct
{
float x;
float y;
float z;
float w;
};
};
vector_4d() : vector_4d (0.f, 0.f, 0.f, 0.f) {}
vector_4d (float x_, float y_, float z_, float w_)
: x (x_)
, y (y_)
, z (z_)
, w (w_)
{ }
vector_4d (const vector_4d& v)
: x (v.x)
, y (v.y)
, z (v.z)
, w (v.w)
{ }
vector_4d (const ::math::vector_3d& v, const float w)
: x (v.x)
, y (v.y)
, z (v.z)
, w (w)
{ }
vector_4d& operator= (const vector_4d &v)
{
x = v.x;
y = v.y;
z = v.z;
w = v.w;
return *this;
}
vector_3d xyz() const
{
return vector_3d (x, y, z);
}
const vector_3d& xyz (const vector_3d& xyz_)
{
x = xyz_.x;
y = xyz_.y;
z = xyz_.z;
return xyz_;
}
vector_3d xyz_normalized_by_w() const
{
return vector_3d (x / w, y / w, z / w);
}
vector_4d operator+ (const vector_4d &v) const
{
return vector_4d (x + v.x, y + v.y, z + v.z, w + v.w);
}
vector_4d operator- (const vector_4d &v) const
{
return vector_4d (x - v.x, y - v.y, z - v.z, w - v.w);
}
vector_4d operator* (float d) const
{
return vector_4d (x * d, y * d, z * d, w * d);
}
float operator* (const vector_4d& v) const
{
return x * v.x + y * v.y + z * v.z + w * v.w;
}
vector_4d& operator*= (float d)
{
x *= d;
y *= d;
z *= d;
w *= d;
return *this;
}
vector_4d& normalize()
{
return operator *= (1.0f / std::sqrt (x * x + y * y + z * z + w * w));
}
operator const float*() const
{
return _data;
}
operator float*()
{
return _data;
}
};
}

77
src/mysql/mysql.cpp Normal file
View File

@@ -0,0 +1,77 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <mysql/mysql.h>
#include <noggit/world.h>
#include <QtCore/QSettings>
#include <cppconn/driver.h>
#include <cppconn/prepared_statement.h>
namespace
{
std::unique_ptr<sql::Connection> connect()
{
QSettings settings;
std::unique_ptr<sql::Connection> Con
(get_driver_instance()->connect
( settings.value("project/mysql/server").toString().toStdString()
, settings.value("project/mysql/user").toString().toStdString()
, settings.value("project/mysql/pwd").toString().toStdString()
)
);
Con->setSchema(settings.value("project/mysql/db").toString().toStdString());
return Con;
}
}
namespace mysql
{
bool hasMaxUIDStoredDB(std::size_t mapID)
{
auto Con(connect());
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("SELECT * FROM UIDs WHERE MapId=(?)"));
pstmt->setInt(1, mapID);
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
return res->rowsCount();
}
std::uint32_t getGUIDFromDB(std::size_t mapID)
{
auto Con(connect());
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("SELECT UID FROM UIDs WHERE MapId=(?)"));
pstmt->setInt(1, mapID);
std::unique_ptr<sql::ResultSet> res(pstmt->executeQuery());
std::uint32_t highGUID(0);
if (res->rowsCount() == 0)
{
return 0;
}
while (res->next())
{
highGUID = res->getInt(1);
}
return highGUID;
}
void insertUIDinDB(std::size_t mapID, std::uint32_t NewUID)
{
auto Con(connect());
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("INSERT INTO UIDs SET MapId=(?), UID=(?)"));
pstmt->setInt(1, mapID);
pstmt->setInt(2, NewUID);
pstmt->executeUpdate();
}
void updateUIDinDB (std::size_t mapID, std::uint32_t NewUID)
{
auto Con(connect());
std::unique_ptr<sql::PreparedStatement> pstmt(Con->prepareStatement("UPDATE UIDs SET UID=(?) WHERE MapId=(?)"));
pstmt->setInt(1, NewUID);
pstmt->setInt(2, mapID);
pstmt->executeUpdate();
}
}

14
src/mysql/mysql.h Normal file
View File

@@ -0,0 +1,14 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <cinttypes>
#include <cstddef>
namespace mysql
{
bool hasMaxUIDStoredDB(std::size_t mapID);
std::uint32_t getGUIDFromDB(std::size_t mapID);
void insertUIDinDB(std::size_t mapID, std::uint32_t NewUID);
void updateUIDinDB (std::size_t mapID, std::uint32_t NewUID);
};

261
src/noggit/Animated.h Normal file
View File

@@ -0,0 +1,261 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/quaternion.hpp>
#include <noggit/MPQ.h>
#include <noggit/ModelHeaders.h>
#include <cassert>
#include <map>
#include <vector>
#include <memory>
namespace Animation
{
namespace Interpolation
{
//! \todo C++0x: Change namespace to "enum class Type : int16_t", remove typedef.
namespace Type
{
typedef int16_t Type_t;
enum
{
NONE,
LINEAR,
HERMITE
};
}
}
template<class FROM, class TO>
struct Conversion
{
inline TO operator()(const FROM& value)
{
return TO(value);
}
};
template<>
inline math::quaternion Conversion<math::packed_quaternion, math::quaternion>::operator()(const math::packed_quaternion& value)
{
//! \todo Check if this is really correct.
return math::quaternion(
static_cast<float>((value.x > 0 ? value.x - 32767 : value.x + 32767) / 32767.0f),
static_cast<float>((value.y > 0 ? value.y - 32767 : value.y + 32767) / 32767.0f),
static_cast<float>((value.z > 0 ? value.z - 32767 : value.z + 32767) / 32767.0f),
static_cast<float>((value.w > 0 ? value.w - 32767 : value.w + 32767) / 32767.0f));
}
template<>
inline float Conversion<int16_t, float>::operator()(const int16_t& value)
{
return value / 32767.0f;
}
//! \note AnimatedType is the type of data getting animated.
//! \note DataType is the type of data stored.
//! \note The conversion from DataType to AnimatedType is done via Animation::Conversion.
template<class AnimatedType, class DataType = AnimatedType>
class M2Value
{
private:
typedef uint32_t TimestampType;
typedef uint32_t AnimationIdType;
typedef std::vector<AnimatedType> AnimatedTypeVectorType;
typedef std::vector<TimestampType> TimestampTypeVectorType;
Animation::Conversion<DataType, AnimatedType> _conversion;
static const int32_t NO_GLOBAL_SEQUENCE = -1;
int32_t _globalSequenceID;
int32_t* _globalSequences;
Animation::Interpolation::Type::Type_t _interpolationType;
std::map<AnimationIdType, TimestampTypeVectorType> times;
std::map<AnimationIdType, AnimatedTypeVectorType> data;
// for nonlinear interpolations:
std::map<AnimationIdType, AnimatedTypeVectorType> in;
std::map<AnimationIdType, AnimatedTypeVectorType> out;
public:
bool uses(AnimationIdType anim)
{
if (_globalSequenceID != NO_GLOBAL_SEQUENCE)
{
anim = AnimationIdType();
}
return !data[anim].empty();
}
AnimatedType getValue (AnimationIdType anim, TimestampType time, int animtime)
{
if (_globalSequenceID != NO_GLOBAL_SEQUENCE)
{
if (_globalSequences[_globalSequenceID])
{
time = animtime % _globalSequences[_globalSequenceID];
}
else
{
time = TimestampType();
}
anim = AnimationIdType();
}
TimestampTypeVectorType& timestampVector = times[anim];
AnimatedTypeVectorType& dataVector = data[anim];
AnimatedTypeVectorType& inVector = in[anim];
AnimatedTypeVectorType& outVector = out[anim];
if (dataVector.empty())
{
return AnimatedType();
}
AnimatedType result = dataVector[0];
if (!timestampVector.empty())
{
TimestampType max_time = timestampVector.back();
if (max_time > 0)
{
time %= max_time;
}
else
{
time = TimestampType();
}
size_t pos = 0;
for (size_t i = 0; i < timestampVector.size() - 1; ++i)
{
if (time >= timestampVector[i] && time < timestampVector[i + 1])
{
pos = i;
break;
}
}
if (pos == timestampVector.size() - 1 || _interpolationType == Animation::Interpolation::Type::NONE)
{
result = dataVector[pos];
}
else
{
TimestampType t1 = timestampVector[pos];
TimestampType t2 = timestampVector[pos + 1];
const float percentage = (time - t1) / static_cast<float>(t2 - t1);
switch (_interpolationType)
{
case Animation::Interpolation::Type::LINEAR:
{
result = math::interpolation::linear (percentage, dataVector[pos], dataVector[pos + 1]);
}
break;
case Animation::Interpolation::Type::HERMITE:
{
result = math::interpolation::hermite(percentage, dataVector[pos], dataVector[pos + 1], inVector[pos], outVector[pos]);
}
break;
}
}
}
return result;
}
//! \todo Use a vector of MPQFile& for the anim files instead for safety.
M2Value (const AnimationBlock& animationBlock, const MPQFile& file, int32_t* globalSequences, const std::vector<std::unique_ptr<MPQFile>>& animation_files = std::vector<std::unique_ptr<MPQFile>>())
{
assert(animationBlock.nTimes == animationBlock.nKeys);
_interpolationType = animationBlock.type;
_globalSequences = globalSequences;
_globalSequenceID = animationBlock.seq;
if (_globalSequenceID != NO_GLOBAL_SEQUENCE)
{
assert(_globalSequences && "Animation said to have global sequence, but pointer to global sequence data is nullptr");
}
const AnimationBlockHeader* timestampHeaders = file.get<AnimationBlockHeader>(animationBlock.ofsTimes);
const AnimationBlockHeader* keyHeaders = file.get<AnimationBlockHeader>(animationBlock.ofsKeys);
for (size_t j = 0; j < animationBlock.nTimes; ++j)
{
const TimestampType* timestamps = j < animation_files.size() && animation_files[j] ?
animation_files[j]->get<TimestampType>(timestampHeaders[j].ofsEntries) :
file.get<TimestampType>(timestampHeaders[j].ofsEntries);
for (size_t i = 0; i < timestampHeaders[j].nEntries; ++i)
{
times[j].push_back(timestamps[i]);
}
}
for (size_t j = 0; j < animationBlock.nKeys; ++j)
{
const DataType* keys = j < animation_files.size() && animation_files[j] ?
animation_files[j]->get<DataType>(keyHeaders[j].ofsEntries) :
file.get<DataType>(keyHeaders[j].ofsEntries);
switch (_interpolationType)
{
case Animation::Interpolation::Type::NONE:
case Animation::Interpolation::Type::LINEAR:
for (size_t i = 0; i < keyHeaders[j].nEntries; ++i)
{
data[j].push_back(_conversion(keys[i]));
}
break;
case Animation::Interpolation::Type::HERMITE:
for (size_t i = 0; i < keyHeaders[j].nEntries; ++i)
{
data[j].push_back(_conversion(keys[i * 3]));
in[j].push_back(_conversion(keys[i * 3 + 1]));
out[j].push_back(_conversion(keys[i * 3 + 2]));
}
break;
}
}
}
void apply(AnimatedType function(const AnimatedType))
{
switch (_interpolationType)
{
case Animation::Interpolation::Type::NONE:
case Animation::Interpolation::Type::LINEAR:
for (size_t i = 0; i < data.size(); ++i)
{
for (size_t j = 0; j < data[i].size(); ++j)
{
data[i][j] = function(data[i][j]);
}
}
break;
case Animation::Interpolation::Type::HERMITE:
for (size_t i = 0; i < data.size(); ++i)
{
for (size_t j = 0; j < data[i].size(); ++j)
{
data[i][j] = function(data[i][j]);
in[i][j] = function(in[i][j]);
out[i][j] = function(out[i][j]);
}
}
break;
}
}
};
};

137
src/noggit/AsyncLoader.cpp Normal file
View File

@@ -0,0 +1,137 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/AsyncLoader.h>
#include <noggit/errorHandling.h>
#include <QtCore/QSettings>
#include <algorithm>
#include <list>
void AsyncLoader::process()
{
AsyncObject* object = nullptr;
QSettings settings;
bool additional_log = settings.value("additional_file_loading_log", false).toBool();
while (!_stop)
{
{
std::unique_lock<std::mutex> lock (_guard);
_state_changed.wait
( lock
, [&]
{
return !!_stop || std::any_of ( _to_load.begin(), _to_load.end()
, [](auto const& to_load) { return !to_load.empty(); }
);
}
);
if (_stop)
{
return;
}
for (auto& to_load : _to_load)
{
if (to_load.empty())
{
continue;
}
object = to_load.front();
_currently_loading.emplace_back (object);
to_load.pop_front();
break;
}
}
try
{
if (additional_log)
{
std::lock_guard<std::mutex> const lock(_guard);
LogDebug << "Loading '" << object->filename << "'" << std::endl;
}
object->finishLoading();
if (additional_log)
{
std::lock_guard<std::mutex> const lock(_guard);
LogDebug << "Loaded '" << object->filename << "'" << std::endl;
}
{
std::lock_guard<std::mutex> const lock (_guard);
_currently_loading.remove (object);
_state_changed.notify_all();
}
}
catch (...)
{
std::lock_guard<std::mutex> const lock(_guard);
object->error_on_loading();
if (object->is_required_when_saving())
{
_important_object_failed_loading = true;
}
}
}
}
void AsyncLoader::queue_for_load (AsyncObject* object)
{
std::lock_guard<std::mutex> const lock (_guard);
_to_load[(size_t)object->loading_priority()].push_back (object);
_state_changed.notify_one();
}
void AsyncLoader::ensure_deletable (AsyncObject* object)
{
std::unique_lock<std::mutex> lock (_guard);
_state_changed.wait
( lock
, [&]
{
auto& to_load = _to_load[(size_t)object->loading_priority()];
auto const& it = std::find (to_load.begin(), to_load.end(), object);
// don't load it if it's just to delete it afterward
if (it != to_load.end())
{
to_load.erase(it);
return true;
}
else
{
return std::find (_currently_loading.begin(), _currently_loading.end(), object) == _currently_loading.end();
}
}
);
}
AsyncLoader::AsyncLoader(int numThreads)
: _stop (false)
{
for (int i = 0; i < numThreads; ++i)
{
_threads.emplace_back (&AsyncLoader::process, this);
}
}
AsyncLoader::~AsyncLoader()
{
_stop = true;
_state_changed.notify_all();
for (auto& thread : _threads)
{
thread.join();
}
}

45
src/noggit/AsyncLoader.h Normal file
View File

@@ -0,0 +1,45 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <noggit/AsyncObject.h>
#include <array>
#include <atomic>
#include <condition_variable>
#include <list>
#include <memory>
#include <thread>
class AsyncLoader
{
public:
static AsyncLoader& instance()
{
static AsyncLoader async_loader(2);
return async_loader;
}
//! Ownership is _not_ transferred. Call ensure_deletable to ensure
//! that a previously enqueued object can be destroyed.
void queue_for_load (AsyncObject*);
void ensure_deletable (AsyncObject*);
AsyncLoader(int numThreads);
~AsyncLoader();
bool important_object_failed_loading() const { return _important_object_failed_loading; }
void reset_object_fail() { _important_object_failed_loading = false; }
private:
void process();
std::mutex _guard;
std::condition_variable _state_changed;
std::atomic<bool> _stop;
std::array<std::list<AsyncObject*>, (size_t)async_priority::count> _to_load;
std::list<AsyncObject*> _currently_loading;
std::list<std::thread> _threads;
bool _important_object_failed_loading = false;
};

84
src/noggit/AsyncObject.h Normal file
View File

@@ -0,0 +1,84 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <noggit/Log.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <string>
enum class async_priority : int
{
high,
medium,
low,
count
};
class AsyncObject
{
private:
bool _loading_failed = false;
protected:
std::atomic<bool> finished = {false};
std::mutex _mutex;
std::condition_variable _state_changed;
AsyncObject(std::string filename) : filename(filename) {}
public:
std::string const filename;
AsyncObject() = delete;
virtual ~AsyncObject() = default;
virtual bool finishedLoading() const
{
return finished.load();
}
bool loading_failed() const
{
return _loading_failed;
}
void wait_until_loaded()
{
if (finished.load())
{
return;
}
std::unique_lock<std::mutex> lock (_mutex);
_state_changed.wait
( lock
, [&]
{
return finished.load();
}
);
}
void error_on_loading()
{
LogError << filename << " could not be loaded" << std::endl;
_loading_failed = true;
finished = true;
_state_changed.notify_all();
}
virtual bool is_required_when_saving() const
{
return false;
}
virtual async_priority loading_priority() const
{
return async_priority::medium;
}
virtual void finishLoading() = 0;
};

40
src/noggit/Brush.cpp Normal file
View File

@@ -0,0 +1,40 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/Brush.h>
void Brush::init()
{
radius = 15;
hardness = 0.5f;
iradius = hardness * radius;
oradius = radius - iradius;
}
void Brush::setHardness(float H)
{
hardness = H;
iradius = hardness * radius;
oradius = radius - iradius;
}
void Brush::setRadius(float R)
{
radius = R;
iradius = hardness * radius;
oradius = radius - iradius;
}
float Brush::getHardness() const
{
return hardness;
}
float Brush::getRadius() const
{
return radius;
}
float Brush::getValue(float dist) const
{
if (dist > radius)
return 0.0f;
if (dist < iradius)
return 1.0f;
return(1.0f - (dist - iradius) / oradius);
}

20
src/noggit/Brush.h Normal file
View File

@@ -0,0 +1,20 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
class Brush
{
private:
float hardness;
float iradius;
float oradius;
float radius;
public:
void setHardness(float H);
void setRadius(float R);
float getHardness() const;
float getRadius() const;
float getValue(float dist) const;
void init();
};

341
src/noggit/ChunkWater.cpp Normal file
View File

@@ -0,0 +1,341 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/ChunkWater.hpp>
#include <noggit/liquid_layer.hpp>
#include <noggit/MPQ.h>
#include <noggit/MapChunk.h>
#include <noggit/Misc.h>
ChunkWater::ChunkWater(float x, float z, bool use_mclq_green_lava)
: xbase(x)
, zbase(z)
, vmin(x, 0.f, z)
, vmax(x + CHUNKSIZE, 0.f, z + CHUNKSIZE)
, _use_mclq_green_lava(use_mclq_green_lava)
{
}
void ChunkWater::from_mclq(std::vector<mclq>& layers)
{
math::vector_3d pos(xbase, 0.0f, zbase);
for (mclq& liquid : layers)
{
std::uint8_t mclq_liquid_type = 0;
for (int z = 0; z < 8; ++z)
{
for (int x = 0; x < 8; ++x)
{
mclq_tile const& tile = liquid.tiles[z * 8 + x];
misc::bit_or(Render.fishable, x, z, tile.fishable);
misc::bit_or(Render.fatigue, x, z, tile.fatigue);
if (!tile.dont_render)
{
mclq_liquid_type = tile.liquid_type;
}
}
}
switch (mclq_liquid_type)
{
case 1:_layers.emplace_back(pos, liquid, 2); break;
case 3:_layers.emplace_back(pos, liquid, 4); break;
case 4:_layers.emplace_back(pos, liquid, 1); break;
case 6:_layers.emplace_back(pos, liquid, (_use_mclq_green_lava ? 15 : 3)); break;
default:
LogError << "Invalid/unhandled MCLQ liquid type" << std::endl;
break;
}
}
update_layers();
}
void ChunkWater::fromFile(MPQFile &f, size_t basePos)
{
MH2O_Header header;
f.read(&header, sizeof(MH2O_Header));
if (!header.nLayers)
{
return;
}
//render
if (header.ofsRenderMask)
{
f.seek(basePos + header.ofsRenderMask + sizeof(MH2O_Render));
f.read(&Render, sizeof(MH2O_Render));
}
for (std::size_t k = 0; k < header.nLayers; ++k)
{
MH2O_Information info;
uint64_t infoMask = 0xFFFFFFFFFFFFFFFF; // default = all water
//info
f.seek(basePos + header.ofsInformation + sizeof(MH2O_Information)* k);
f.read(&info, sizeof(MH2O_Information));
//mask
if (info.ofsInfoMask > 0 && info.height > 0)
{
size_t bitmask_size = static_cast<size_t>(std::ceil(info.height * info.width / 8.0f));
f.seek(info.ofsInfoMask + basePos);
// only read the relevant data
f.read(&infoMask, bitmask_size);
}
math::vector_3d pos(xbase, 0.0f, zbase);
_layers.emplace_back(f, basePos, pos, info, infoMask);
}
update_layers();
}
void ChunkWater::save(sExtendableArray& adt, int base_pos, int& header_pos, int& current_pos)
{
MH2O_Header header;
// remove empty layers
cleanup();
if (hasData(0))
{
header.nLayers = _layers.size();
header.ofsRenderMask = current_pos - base_pos;
adt.Insert(current_pos, sizeof(MH2O_Render), reinterpret_cast<char*>(&Render));
current_pos += sizeof(MH2O_Render);
header.ofsInformation = current_pos - base_pos;
int info_pos = current_pos;
std::size_t info_size = sizeof(MH2O_Information) * _layers.size();
current_pos += info_size;
adt.Extend(info_size);
for (liquid_layer& layer : _layers)
{
layer.save(adt, base_pos, info_pos, current_pos);
}
}
memcpy(adt.GetPointer<char>(header_pos), &header, sizeof(MH2O_Header));
header_pos += sizeof(MH2O_Header);
}
void ChunkWater::autoGen(MapChunk *chunk, float factor)
{
for (liquid_layer& layer : _layers)
{
layer.update_opacity(chunk, factor);
}
update_layers();
}
void ChunkWater::CropWater(MapChunk* chunkTerrain)
{
for (liquid_layer& layer : _layers)
{
layer.crop(chunkTerrain);
}
update_layers();
}
int ChunkWater::getType(size_t layer) const
{
return hasData(layer) ? _layers[layer].liquidID() : 0;
}
void ChunkWater::setType(int type, size_t layer)
{
if(hasData(layer))
{
_layers[layer].changeLiquidID(type);
}
}
void ChunkWater::draw ( math::frustum const& frustum
, const float& cull_distance
, const math::vector_3d& camera
, bool camera_moved
, liquid_render& render
, opengl::scoped::use_program& water_shader
, int animtime
, int layer
, display_mode display
)
{
if (!is_visible (cull_distance, frustum, camera, display))
{
return;
}
if (layer == -1)
{
for (liquid_layer& lq_layer : _layers)
{
lq_layer.draw (render, water_shader, camera, camera_moved, animtime);
}
}
else if (layer < _layers.size())
{
_layers[layer].draw (render, water_shader, camera, camera_moved, animtime);
}
}
bool ChunkWater::is_visible ( const float& cull_distance
, const math::frustum& frustum
, const math::vector_3d& camera
, display_mode display
) const
{
static const float chunk_radius = std::sqrt (CHUNKSIZE * CHUNKSIZE / 2.0f);
float dist = display == display_mode::in_3D
? (camera - vcenter).length() - chunk_radius
: std::abs(camera.y - vmax.y);
return frustum.intersects (_intersect_points)
&& dist < cull_distance;
}
void ChunkWater::update_layers()
{
for (liquid_layer& layer : _layers)
{
layer.update_indices();
vmin.y = std::min (vmin.y, layer.min());
vmax.y = std::max (vmax.y, layer.max());
}
vcenter = (vmin + vmax) * 0.5f;
_intersect_points.clear();
_intersect_points = misc::intersection_points(vmin, vmax);
}
bool ChunkWater::hasData(size_t layer) const
{
return _layers.size() > layer;
}
void ChunkWater::paintLiquid( math::vector_3d const& pos
, float radius
, int liquid_id
, bool add
, math::radians const& angle
, math::radians const& orientation
, bool lock
, math::vector_3d const& origin
, bool override_height
, bool override_liquid_id
, MapChunk* chunk
, float opacity_factor
)
{
if (override_liquid_id && !override_height)
{
bool layer_found = false;
for (liquid_layer& layer : _layers)
{
if (layer.liquidID() == liquid_id)
{
copy_height_to_layer(layer, pos, radius);
layer_found = true;
break;
}
}
if (!layer_found)
{
liquid_layer layer(math::vector_3d(xbase, 0.0f, zbase), pos.y, liquid_id);
copy_height_to_layer(layer, pos, radius);
_layers.push_back(layer);
}
}
bool painted = false;
for (liquid_layer& layer : _layers)
{
// remove the water on all layers or paint the layer with selected id
if (!add || layer.liquidID() == liquid_id || !override_liquid_id)
{
layer.paintLiquid(pos, radius, add, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
painted = true;
}
else
{
layer.paintLiquid(pos, radius, false, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
}
}
cleanup();
if (!add || painted)
{
update_layers();
return;
}
if (hasData(0))
{
liquid_layer layer(_layers[0]);
layer.clear(); // remove the liquid to not override the other layer
layer.paintLiquid(pos, radius, true, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
layer.changeLiquidID(liquid_id);
_layers.push_back(layer);
}
else
{
liquid_layer layer(math::vector_3d(xbase, 0.0f, zbase), pos.y, liquid_id);
layer.paintLiquid(pos, radius, true, angle, orientation, lock, origin, override_height, chunk, opacity_factor);
_layers.push_back(layer);
}
update_layers();
}
void ChunkWater::cleanup()
{
for (int i = _layers.size() - 1; i >= 0; --i)
{
if (_layers[i].empty())
{
_layers.erase(_layers.begin() + i);
}
}
}
void ChunkWater::copy_height_to_layer(liquid_layer& target, math::vector_3d const& pos, float radius)
{
for (liquid_layer& layer : _layers)
{
if (layer.liquidID() == target.liquidID())
{
continue;
}
for (int z = 0; z < 8; ++z)
{
for (int x = 0; x < 8; ++x)
{
if (misc::getShortestDist(pos.x, pos.z, xbase + x*UNITSIZE, zbase + z*UNITSIZE, UNITSIZE) <= radius)
{
if (layer.hasSubchunk(x, z))
{
target.copy_subchunk_height(x, z, layer);
}
}
}
}
}
}

91
src/noggit/ChunkWater.hpp Normal file
View File

@@ -0,0 +1,91 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/frustum.hpp>
#include <math/vector_3d.hpp>
#include <noggit/liquid_layer.hpp>
#include <noggit/MapHeaders.h>
#include <noggit/tool_enums.hpp>
#include <vector>
#include <set>
class MPQFile;
class sExtendableArray;
class MapChunk;
class ChunkWater
{
public:
ChunkWater() = delete;
explicit ChunkWater(float x, float z, bool use_mclq_green_lava);
ChunkWater (ChunkWater const&) = delete;
ChunkWater (ChunkWater&&) = delete;
ChunkWater& operator= (ChunkWater const&) = delete;
ChunkWater& operator= (ChunkWater&&) = delete;
void from_mclq(std::vector<mclq>& layers);
void fromFile(MPQFile &f, size_t basePos);
void save(sExtendableArray& adt, int base_pos, int& header_pos, int& current_pos);
void draw ( math::frustum const& frustum
, const float& cull_distance
, const math::vector_3d& camera
, bool camera_moved
, liquid_render& render
, opengl::scoped::use_program& water_shader
, int animtime
, int layer
, display_mode display
);
bool is_visible ( const float& cull_distance
, const math::frustum& frustum
, const math::vector_3d& camera
, display_mode display
) const;
void autoGen(MapChunk* chunk, float factor);
void CropWater(MapChunk* chunkTerrain);
void setType(int type, size_t layer);
int getType(size_t layer) const;
bool hasData(size_t layer) const;
void paintLiquid( math::vector_3d const& pos
, float radius
, int liquid_id
, bool add
, math::radians const& angle
, math::radians const& orientation
, bool lock
, math::vector_3d const& origin
, bool override_height
, bool override_liquid_id
, MapChunk* chunk
, float opacity_factor
);
float xbase, zbase;
private:
std::vector<math::vector_3d> _intersect_points;
math::vector_3d vmin, vmax, vcenter;
bool _use_mclq_green_lava;
// remove empty layers
void cleanup();
// update every layer's render
void update_layers();
void copy_height_to_layer(liquid_layer& target, math::vector_3d const& pos, float radius);
MH2O_Render Render;
std::vector<liquid_layer> _layers;
};

152
src/noggit/DBC.cpp Normal file
View File

@@ -0,0 +1,152 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/DBC.h>
#include <noggit/Log.h>
#include <noggit/Misc.h>
#include <string>
AreaDB gAreaDB;
MapDB gMapDB;
LoadingScreensDB gLoadingScreensDB;
LightDB gLightDB;
LightParamsDB gLightParamsDB;
LightSkyboxDB gLightSkyboxDB;
LightIntBandDB gLightIntBandDB;
LightFloatBandDB gLightFloatBandDB;
GroundEffectDoodadDB gGroundEffectDoodadDB;
GroundEffectTextureDB gGroundEffectTextureDB;
LiquidTypeDB gLiquidTypeDB;
void OpenDBs()
{
gAreaDB.open();
gMapDB.open();
gLoadingScreensDB.open();
gLightDB.open();
gLightParamsDB.open();
gLightSkyboxDB.open();
gLightIntBandDB.open();
gLightFloatBandDB.open();
gGroundEffectDoodadDB.open();
gGroundEffectTextureDB.open();
gLiquidTypeDB.open();
}
std::string AreaDB::getAreaName(int pAreaID)
{
if (!pAreaID || pAreaID == -1)
{
return "Unknown location";
}
unsigned int regionID = 0;
std::string areaName = "";
try
{
AreaDB::Record rec = gAreaDB.getByID(pAreaID);
areaName = rec.getLocalizedString(AreaDB::Name);
regionID = rec.getUInt(AreaDB::Region);
}
catch (AreaDB::NotFound)
{
areaName = "Unknown location";
}
if (regionID != 0)
{
try
{
AreaDB::Record rec = gAreaDB.getByID(regionID);
areaName = std::string(rec.getLocalizedString(AreaDB::Name)) + std::string(": ") + areaName;
}
catch (AreaDB::NotFound)
{
areaName = "Unknown location";
}
}
return areaName;
}
std::uint32_t AreaDB::get_area_parent(int area_id)
{
// todo: differentiate between no parent and error ?
if (!area_id || area_id == -1)
{
return 0;
}
try
{
AreaDB::Record rec = gAreaDB.getByID(area_id);
return rec.getUInt(AreaDB::Region);
}
catch (AreaDB::NotFound)
{
return 0;
}
}
std::string MapDB::getMapName(int pMapID)
{
if (pMapID<0) return "Unknown map";
std::string mapName = "";
try
{
MapDB::Record rec = gMapDB.getByID(pMapID);
mapName = std::string(rec.getLocalizedString(MapDB::Name));
}
catch (MapDB::NotFound)
{
mapName = "Unknown map";
}
return mapName;
}
const char * getGroundEffectDoodad(unsigned int effectID, int DoodadNum)
{
try
{
unsigned int doodadId = gGroundEffectTextureDB.getByID(effectID).getUInt(GroundEffectTextureDB::Doodads + DoodadNum);
return gGroundEffectDoodadDB.getByID(doodadId).getString(GroundEffectDoodadDB::Filename);
}
catch (DBCFile::NotFound)
{
LogError << "Tried to get a not existing row in GroundEffectTextureDB or GroundEffectDoodadDB ( effectID = " << effectID << ", DoodadNum = " << DoodadNum << " )!" << std::endl;
return 0;
}
}
int LiquidTypeDB::getLiquidType(int pID)
{
int type = 0;
try
{
LiquidTypeDB::Record rec = gLiquidTypeDB.getByID(pID);
type = rec.getUInt(LiquidTypeDB::Type);
}
catch (LiquidTypeDB::NotFound)
{
type = 0;
}
return type;
}
std::string LiquidTypeDB::getLiquidName(int pID)
{
std::string type = "";
try
{
LiquidTypeDB::Record rec = gLiquidTypeDB.getByID(pID);
type = std::string(rec.getString(LiquidTypeDB::Name));
}
catch (MapDB::NotFound)
{
type = "Unknown type";
}
return type;
}

196
src/noggit/DBC.h Normal file
View File

@@ -0,0 +1,196 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <noggit/DBCFile.h>
#include <string>
class AreaDB : public DBCFile
{
public:
AreaDB() :
DBCFile("DBFilesClient\\AreaTable.dbc")
{ }
/// Fields
static const size_t AreaID = 0; // uint
static const size_t Continent = 1; // uint
static const size_t Region = 2; // uint [AreaID]
static const size_t Flags = 4; // bit field
static const size_t Name = 11; // localisation string
static std::string getAreaName(int pAreaID);
static std::uint32_t get_area_parent(int area_id);
};
class MapDB : public DBCFile
{
public:
MapDB() :
DBCFile("DBFilesClient\\Map.dbc")
{ }
/// Fields
static const size_t MapID = 0; // uint
static const size_t InternalName = 1; // string
static const size_t AreaType = 2; // uint
static const size_t IsBattleground = 4; // uint
static const size_t Name = 5; // loc
static const size_t LoadingScreen = 57; // uint [LoadingScreen]
static std::string getMapName(int pMapID);
};
class LoadingScreensDB : public DBCFile
{
public:
LoadingScreensDB() :
DBCFile("DBFilesClient\\LoadingScreens.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Name = 1; // string
static const size_t Path = 2; // string
};
class LightDB : public DBCFile
{
public:
LightDB() :
DBCFile("DBFilesClient\\Light.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Map = 1; // uint
static const size_t PositionX = 2; // float
static const size_t PositionY = 3; // float
static const size_t PositionZ = 4; // float
static const size_t RadiusInner = 5; // float
static const size_t RadiusOuter = 6; // float
static const size_t DataIDs = 7; // uint[8]
};
class LightParamsDB : public DBCFile{
public:
LightParamsDB() :
DBCFile("DBFilesClient\\LightParams.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t skybox = 2; // uint ref to LightSkyBox
static const size_t water_shallow_alpha = 5;
static const size_t water_deep_alpha = 6;
static const size_t ocean_shallow_alpha = 7;
static const size_t ocean_deep_alpha = 8;
};
class LightSkyboxDB : public DBCFile
{
public:
LightSkyboxDB() :
DBCFile("DBFilesClient\\LightSkybox.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t filename = 1; // string
static const size_t flags = 2; // uint
};
class LightIntBandDB : public DBCFile
{
public:
LightIntBandDB() :
DBCFile("DBFilesClient\\LightIntBand.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Entries = 1; // uint
static const size_t Times = 2; // uint
static const size_t Values = 18; // uint
};
class LightFloatBandDB : public DBCFile
{
public:
LightFloatBandDB() :
DBCFile("DBFilesClient\\LightFloatBand.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Entries = 1; // uint
static const size_t Times = 2; // uint
static const size_t Values = 18; // float
};
class GroundEffectTextureDB : public DBCFile
{
public:
GroundEffectTextureDB() :
DBCFile("DBFilesClient\\GroundEffectTexture.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Doodads = 1; // uint[4]
static const size_t Weights = 5; // uint[4]
static const size_t Amount = 9; // uint
static const size_t TerrainType = 10; // uint
};
class GroundEffectDoodadDB : public DBCFile
{
public:
GroundEffectDoodadDB() :
DBCFile("DBFilesClient\\GroundEffectDoodad.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t InternalID = 1; // uint
static const size_t Filename = 2; // string
};
class LiquidTypeDB : public DBCFile
{
public:
LiquidTypeDB() :
DBCFile("DBFilesClient\\LiquidType.dbc")
{ }
/// Fields
static const size_t ID = 0; // uint
static const size_t Name = 1; // string
static const size_t Type = 3; // uint
static const size_t ShaderType = 14; // uint
static const size_t TextureFilenames = 15; // string[6]
static const size_t TextureTilesPerBlock = 23; // uint
static const size_t Rotation = 24; // uint
static const size_t AnimationX = 23; // uint
static const size_t AnimationY = 24; // uint
static int getLiquidType(int pID);
static std::string getLiquidName(int pID);
};
void OpenDBs();
const char * getGroundEffectDoodad(unsigned int effectID, int DoodadNum);
extern AreaDB gAreaDB;
extern MapDB gMapDB;
extern LoadingScreensDB gLoadingScreensDB;
extern LightDB gLightDB;
extern LightParamsDB gLightParamsDB;
extern LightSkyboxDB gLightSkyboxDB;
extern LightIntBandDB gLightIntBandDB;
extern LightFloatBandDB gLightFloatBandDB;
extern GroundEffectDoodadDB gGroundEffectDoodadDB;
extern GroundEffectTextureDB gGroundEffectTextureDB;
extern LiquidTypeDB gLiquidTypeDB;

45
src/noggit/DBCFile.cpp Normal file
View File

@@ -0,0 +1,45 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/DBCFile.h>
#include <noggit/Log.h>
#include <noggit/MPQ.h>
#include <string>
DBCFile::DBCFile(const std::string& _filename)
: filename(_filename)
{}
void DBCFile::open()
{
MPQFile f (filename);
if (f.isEof())
{
LogError << "The DBC file \"" << filename << "\" could not be opened. This application may crash soon as the file is most likely needed." << std::endl;
return;
}
LogDebug << "Opening DBC \"" << filename << "\"" << std::endl;
char header[4];
f.read(header, 4); // Number of records
assert(header[0] == 'W' && header[1] == 'D' && header[2] == 'B' && header[3] == 'C');
f.read(&recordCount, 4);
f.read(&fieldCount, 4);
f.read(&recordSize, 4);
f.read(&stringSize, 4);
if (fieldCount * 4 != recordSize)
{
throw std::logic_error ("non four-byte-columns not supported");
}
data.resize (recordSize * recordCount);
f.read (data.data(), data.size());
stringTable.resize (stringSize);
f.read (stringTable.data(), stringTable.size());
f.close();
}

142
src/noggit/DBCFile.h Normal file
View File

@@ -0,0 +1,142 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <cassert>
#include <string>
#include <vector>
#include <stdexcept>
class DBCFile
{
public:
explicit DBCFile(const std::string& filename);
// Open database. It must be openened before it can be used.
void open();
class NotFound : public std::runtime_error
{
public:
NotFound() : std::runtime_error("Key was not found")
{ }
};
class Iterator;
class Record
{
public:
const float& getFloat(size_t field) const
{
assert(field < file.fieldCount);
return *reinterpret_cast<float*>(offset + field * 4);
}
const unsigned int& getUInt(size_t field) const
{
assert(field < file.fieldCount);
return *reinterpret_cast<unsigned int*>(offset + field * 4);
}
const int& getInt(size_t field) const
{
assert(field < file.fieldCount);
return *reinterpret_cast<int*>(offset + field * 4);
}
const char *getString(size_t field) const
{
assert(field < file.fieldCount);
size_t stringOffset = getUInt(field);
assert(stringOffset < file.stringSize);
return file.stringTable.data() + stringOffset;
}
const char *getLocalizedString(size_t field, int locale = -1) const
{
int loc = locale;
if (locale == -1)
{
assert(field < file.fieldCount - 8);
for (loc = 0; loc < 9; loc++)
{
size_t stringOffset = getUInt(field + loc);
if (stringOffset != 0)
break;
}
}
assert(field + loc < file.fieldCount);
size_t stringOffset = getUInt(field + loc);
assert(stringOffset < file.stringSize);
return file.stringTable.data() + stringOffset;
}
private:
Record(const DBCFile &pfile, unsigned char *poffset) : file(pfile), offset(poffset) {}
const DBCFile &file;
unsigned char *offset;
friend class DBCFile;
friend class DBCFile::Iterator;
};
/** Iterator that iterates over records
*/
class Iterator
{
public:
Iterator(const DBCFile &file, unsigned char *offset) :
record(file, offset) {}
/// Advance (prefix only)
Iterator & operator++() {
record.offset += record.file.recordSize;
return *this;
}
/// Return address of current instance
Record const & operator*() const { return record; }
const Record* operator->() const {
return &record;
}
/// Comparison
bool operator==(const Iterator &b) const
{
return record.offset == b.record.offset;
}
bool operator!=(const Iterator &b) const
{
return record.offset != b.record.offset;
}
private:
Record record;
};
inline Record getRecord(size_t id)
{
return Record(*this, data.data() + id*recordSize);
}
inline Iterator begin()
{
return Iterator(*this, data.data());
}
inline Iterator end()
{
return Iterator(*this, data.data() + data.size());
}
inline size_t getRecordCount() const { return recordCount; }
inline size_t getFieldCount() const { return fieldCount; }
inline Record getByID(unsigned int id, size_t field = 0)
{
for (Iterator i = begin(); i != end(); ++i)
{
if (i->getUInt(field) == id)
return (*i);
}
throw NotFound();
}
private:
std::string filename;
size_t recordSize;
size_t recordCount;
size_t fieldCount;
size_t stringSize;
std::vector<unsigned char> data;
std::vector<char> stringTable;
};

43
src/noggit/Log.cpp Normal file
View File

@@ -0,0 +1,43 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/Log.h>
#include <cstring>
#include <ctime>
#include <fstream>
std::ostream& _LogError(const char * pFile, int pLine)
{
return std::cerr << clock() * 1000 / CLOCKS_PER_SEC << " - (" << ((strrchr(pFile, '/') ? strrchr(pFile, '/') : (strrchr(pFile, '\\') ? strrchr(pFile, '\\') : pFile - 1)) + 1) << ":" << pLine << "): [Error] ";
}
std::ostream& _LogDebug(const char * pFile, int pLine)
{
return std::clog << clock() * 1000 / CLOCKS_PER_SEC << " - (" << ((strrchr(pFile, '/') ? strrchr(pFile, '/') : (strrchr(pFile, '\\') ? strrchr(pFile, '\\') : pFile - 1)) + 1) << ":" << pLine << "): [Debug] ";
}
std::ostream& _Log(const char * pFile, int pLine)
{
return std::cout << clock() * 1000 / CLOCKS_PER_SEC << " - (" << ((strrchr(pFile, '/') ? strrchr(pFile, '/') : (strrchr(pFile, '\\') ? strrchr(pFile, '\\') : pFile - 1)) + 1) << ":" << pLine << "): ";
}
#if DEBUG__LOGGINGTOCONSOLE
void InitLogging()
{
LogDebug << "Logging to console window." << std::endl;
}
#else
namespace
{
std::ofstream gLogStream;
}
void InitLogging()
{
// Set up log.
gLogStream.open("log.txt", std::ios_base::out | std::ios_base::trunc);
if (gLogStream)
{
std::cout.rdbuf(gLogStream.rdbuf());
std::clog.rdbuf(gLogStream.rdbuf());
std::cerr.rdbuf(gLogStream.rdbuf());
}
}
#endif

15
src/noggit/Log.h Normal file
View File

@@ -0,0 +1,15 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <iostream>
std::ostream& _LogError(const char * pFile, int pLine);
std::ostream& _LogDebug(const char * pFile, int pLine);
std::ostream& _Log(const char * pFile, int pLine);
#define LogError _LogError( __FILE__, __LINE__ )
#define LogDebug _LogDebug( __FILE__, __LINE__ )
#define Log _Log( __FILE__, __LINE__ )
void InitLogging();

354
src/noggit/MPQ.cpp Normal file
View File

@@ -0,0 +1,354 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#include <noggit/AsyncLoader.h> // AsyncLoader
#include <noggit/Log.h>
#include <noggit/MPQ.h>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <QtCore/QSettings>
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <list>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_set>
namespace
{
typedef std::pair<std::string, std::unique_ptr<MPQArchive>> ArchiveEntry;
typedef std::list<ArchiveEntry> ArchivesMap;
ArchivesMap _openArchives;
boost::mutex gListfileLoadingMutex;
boost::mutex gMPQFileMutex;
}
std::unordered_set<std::string> gListfile;
void MPQArchive::loadMPQ (AsyncLoader* loader, std::string const& filename, bool doListfile)
{
_openArchives.emplace_back (filename, std::make_unique<MPQArchive> (filename, doListfile));
loader->queue_for_load(_openArchives.back().second.get());
}
MPQArchive::MPQArchive(std::string const& filename, bool doListfile)
: AsyncObject(filename)
,_archiveHandle(nullptr)
{
if (!SFileOpenArchive (filename.c_str(), 0, MPQ_OPEN_NO_LISTFILE | STREAM_FLAG_READ_ONLY, &_archiveHandle))
{
LogError << "Error opening archive: " << filename << std::endl;
return;
}
else
{
LogDebug << "Opened archive " << filename << std::endl;
}
finished = !doListfile;
}
void MPQArchive::finishLoading()
{
if (finished)
return;
HANDLE fh;
boost::mutex::scoped_lock lock2(gMPQFileMutex);
boost::mutex::scoped_lock lock(gListfileLoadingMutex);
if (SFileOpenFileEx(_archiveHandle, "(listfile)", 0, &fh))
{
size_t filesize = SFileGetFileSize(fh, nullptr); //last nullptr for newer version of StormLib
std::vector<char> readbuffer (filesize);
SFileReadFile(fh, readbuffer.data(), filesize, nullptr, nullptr); //last nullptrs for newer version of StormLib
SFileCloseFile(fh);
std::string current;
for (char c : readbuffer)
{
if (c == '\r')
{
continue;
}
if (c == '\n')
{
gListfile.emplace (noggit::mpq::normalized_filename (current));
current.resize (0);
}
else
{
current += c;
}
}
if (!current.empty())
{
gListfile.emplace (noggit::mpq::normalized_filename (current));
}
}
finished = true;
_state_changed.notify_all();
if (MPQArchive::allFinishedLoading())
{
LogDebug << "Completed listfile loading: " << gListfile.size() << " files\n";
}
}
MPQArchive::~MPQArchive()
{
if (_archiveHandle)
SFileCloseArchive(_archiveHandle);
}
bool MPQArchive::allFinishedLoading()
{
return std::all_of ( _openArchives.begin(), _openArchives.end()
, [] (ArchiveEntry const& archive)
{
return archive.second->finishedLoading();
}
);
}
void MPQArchive::allFinishLoading()
{
for (auto& archive : _openArchives)
{
archive.second->finishLoading();
}
}
void MPQArchive::unloadAllMPQs()
{
_openArchives.clear();
}
bool MPQArchive::hasFile(std::string const& filename) const
{
return SFileHasFile(_archiveHandle, noggit::mpq::normalized_filename_insane (filename).c_str());
}
void MPQArchive::unloadMPQ(std::string const& filename)
{
for (auto it = _openArchives.begin(); it != _openArchives.end(); ++it)
{
if (it->first == filename)
{
_openArchives.erase(it);
}
}
}
bool MPQArchive::openFile(std::string const& filename, HANDLE* fileHandle) const
{
assert(fileHandle);
return SFileOpenFileEx(_archiveHandle, noggit::mpq::normalized_filename_insane (filename).c_str(), 0, fileHandle);
}
namespace
{
boost::filesystem::path getDiskPath (std::string const& pFilename)
{
QSettings settings;
return boost::filesystem::path (settings.value ("project/path").toString().toStdString())
/ noggit::mpq::normalized_filename (pFilename);
}
bool existsInMPQ (std::string const& filename)
{
return std::any_of ( _openArchives.begin(), _openArchives.end()
, [&] (ArchiveEntry const& archive)
{
return archive.second->hasFile (filename);
}
);
}
}
/*
* basic constructor to save the file to project path
*/
MPQFile::MPQFile(std::string const& filename)
: eof(true)
, pointer(0)
, External(false)
, _disk_path (getDiskPath (filename))
{
if (filename.empty())
throw std::runtime_error("MPQFile: filename empty");
boost::mutex::scoped_lock lock(gMPQFileMutex);
std::ifstream input(_disk_path.string(), std::ios_base::binary | std::ios_base::in);
if (input.is_open())
{
External = true;
eof = false;
input.seekg(0, std::ios::end);
buffer.resize (input.tellg());
input.seekg(0, std::ios::beg);
input.read(buffer.data(), buffer.size());
input.close();
return;
}
for (ArchivesMap::reverse_iterator i = _openArchives.rbegin(); i != _openArchives.rend(); ++i)
{
HANDLE fileHandle;
if (!i->second->openFile(filename, &fileHandle))
continue;
eof = false;
buffer.resize (SFileGetFileSize(fileHandle, nullptr));
SFileReadFile(fileHandle, buffer.data(), buffer.size(), nullptr, nullptr); //last nullptrs for newer version of StormLib
SFileCloseFile(fileHandle);
return;
}
throw std::invalid_argument ("File '" + filename + "' does not exist.");
}
MPQFile::~MPQFile()
{
close();
}
bool MPQFile::exists (std::string const& filename)
{
return existsOnDisk (filename) || existsInMPQ (filename);
}
bool MPQFile::existsOnDisk (std::string const& filename)
{
return boost::filesystem::exists (getDiskPath (filename));
}
size_t MPQFile::read(void* dest, size_t bytes)
{
if (eof || !bytes)
return 0;
size_t rpos = pointer + bytes;
if (rpos > buffer.size()) {
bytes = buffer.size() - pointer;
eof = true;
}
memcpy(dest, &(buffer[pointer]), bytes);
pointer = rpos;
return bytes;
}
bool MPQFile::isEof() const
{
return eof;
}
void MPQFile::seek(size_t offset)
{
pointer = offset;
eof = (pointer >= buffer.size());
}
void MPQFile::seekRelative(size_t offset)
{
pointer += offset;
eof = (pointer >= buffer.size());
}
void MPQFile::close()
{
eof = true;
}
size_t MPQFile::getSize() const
{
return buffer.size();
}
size_t MPQFile::getPos() const
{
return pointer;
}
char const* MPQFile::getBuffer() const
{
return buffer.data();
}
char const* MPQFile::getPointer() const
{
return buffer.data() + pointer;
}
void MPQFile::SaveFile()
{
LogDebug << "Save file to: " << _disk_path << std::endl;
auto const directory_name (_disk_path.parent_path());
boost::system::error_code ec;
boost::filesystem::create_directories (directory_name, ec);
if (ec)
{
LogError << "Creating directory \"" << directory_name << "\" failed: " << ec << ". Saving is highly likely to fail." << std::endl;
}
std::ofstream output(_disk_path.string(), std::ios_base::binary | std::ios_base::out);
if (output.is_open())
{
Log << "Saving file \"" << _disk_path << "\"." << std::endl;
output.write(buffer.data(), buffer.size());
output.close();
External = true;
}
}
namespace noggit
{
namespace mpq
{
std::string normalized_filename (std::string filename)
{
std::transform (filename.begin(), filename.end(), filename.begin(), ::tolower);
std::transform ( filename.begin(), filename.end(), filename.begin()
, [] (char c)
{
return c == '\\' ? '/' : c;
}
);
return filename;
}
std::string normalized_filename_insane (std::string filename)
{
std::transform (filename.begin(), filename.end(), filename.begin(), ::toupper);
std::transform ( filename.begin(), filename.end(), filename.begin()
, [] (char c)
{
return c == '/' ? '\\' : c;
}
);
return filename;
}
}
}

108
src/noggit/MPQ.h Normal file
View File

@@ -0,0 +1,108 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <noggit/AsyncObject.h>
#include <StormLib.h>
#include <boost/filesystem/path.hpp>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
class AsyncLoader;
class MPQArchive;
class MPQFile;
extern std::unordered_set<std::string> gListfile;
class MPQArchive : public AsyncObject
{
HANDLE _archiveHandle;
public:
MPQArchive(const std::string& filename, bool doListfile);
~MPQArchive();
bool hasFile(const std::string& filename) const;
bool openFile(const std::string& filename, HANDLE* fileHandle) const;
void finishLoading();
static bool allFinishedLoading();
static void allFinishLoading();
static void loadMPQ (AsyncLoader*, const std::string& filename, bool doListfile = false);
static void unloadAllMPQs();
static void unloadMPQ(const std::string& filename);
friend class MPQFile;
};
class MPQFile
{
bool eof;
std::vector<char> buffer;
size_t pointer;
bool External;
boost::filesystem::path _disk_path;
std::string _mpq_path;
public:
explicit MPQFile(const std::string& pFilename); // filenames are not case sensitive, the are if u dont use a filesystem which is kinda shitty...
MPQFile() = delete;
~MPQFile();
MPQFile(MPQFile const&) = delete;
MPQFile(MPQFile&&) = delete;
MPQFile& operator=(MPQFile const&) = delete;
MPQFile& operator=(MPQFile&&) = delete;
size_t read(void* dest, size_t bytes);
size_t getSize() const;
size_t getPos() const;
char const* getBuffer() const;
char const* getPointer() const;
bool isEof() const;
void seek(size_t offset);
void seekRelative(size_t offset);
void close();
bool isExternal() const
{
return External;
}
template<typename T>
const T* get(size_t offset) const
{
return reinterpret_cast<T const*>(buffer.data() + offset);
}
void setBuffer (std::vector<char> const& vec)
{
buffer = vec;
}
void SaveFile();
static bool exists (std::string const& filename);
static bool existsOnDisk (std::string const& filename);
friend class MPQArchive;
};
namespace noggit
{
namespace mpq
{
std::string normalized_filename (std::string filename);
std::string normalized_filename_insane (std::string filename);
}
}

1492
src/noggit/MapChunk.cpp Normal file

File diff suppressed because it is too large Load Diff

197
src/noggit/MapChunk.h Normal file
View File

@@ -0,0 +1,197 @@
// This file is part of Noggit3, licensed under GNU General Public License (version 3).
#pragma once
#include <math/quaternion.hpp> // math::vector_4d
#include <noggit/map_enums.hpp>
#include <noggit/MapTile.h> // MapTile
#include <noggit/ModelInstance.h>
#include <noggit/Selection.h>
#include <noggit/TextureManager.h>
#include <noggit/WMOInstance.h>
#include <noggit/texture_set.hpp>
#include <noggit/tool_enums.hpp>
#include <opengl/scoped.hpp>
#include <opengl/texture.hpp>
#include <noggit/Misc.h>
#include <map>
#include <memory>
class MPQFile;
namespace math
{
class frustum;
struct vector_4d;
}
class Brush;
class ChunkWater;
class sExtendableArray;
using StripType = uint16_t;
static const int mapbufsize = 9 * 9 + 8 * 8; // chunk size
class MapChunk
{
private:
tile_mode _mode;
bool hasMCCV;
int holes;
unsigned int areaID;
void update_shadows();
uint8_t _shadow_map[64 * 64];
opengl::texture shadow;
std::vector<StripType> strip_with_holes;
std::vector<StripType> strip_without_holes;
std::map<int, std::vector<StripType>> strip_lods;
math::vector_3d mNormals[mapbufsize];
math::vector_3d mccv[mapbufsize];
std::vector<uint8_t> compressed_shadow_map() const;
bool has_shadows() const;
void initStrip();
int indexNoLoD(int x, int y);
int indexLoD(int x, int y);
std::vector<math::vector_3d> _intersect_points;
void update_intersect_points();
boost::optional<int> get_lod_level( math::vector_3d const& camera_pos
, display_mode display
) const;
bool _uploaded = false;
bool _need_indice_buffer_update = true;
bool _need_vao_update = true;
void upload();
void update_indices_buffer();
void update_vao(opengl::scoped::use_program& mcnk_shader, GLuint const& tex_coord_vbo);
opengl::scoped::deferred_upload_vertex_arrays<1> _vertex_array;
GLuint const& _vao = _vertex_array[0];
opengl::scoped::deferred_upload_buffers<4> _buffers;
GLuint const& _vertices_vbo = _buffers[0];
GLuint const& _normals_vbo = _buffers[1];
GLuint const& _indices_buffer = _buffers[2];
GLuint const& _mccv_vbo = _buffers[3];
opengl::scoped::deferred_upload_buffers<4> lod_indices;
public:
MapChunk(MapTile* mt, MPQFile* f, bool bigAlpha, tile_mode mode);
MapTile *mt;
math::vector_3d vmin, vmax, vcenter;
int px, py;
MapChunkHeader header;
float xbase, ybase, zbase;
mcnk_flags header_flags;
bool use_big_alphamap;
std::unique_ptr<TextureSet> texture_set;
math::vector_3d mVertices[mapbufsize];
bool is_visible ( const float& cull_distance
, const math::frustum& frustum
, const math::vector_3d& camera
, display_mode display
) const;
private:
// return true if the lod level changed
bool update_visibility ( const float& cull_distance
, const math::frustum& frustum
, const math::vector_3d& camera
, display_mode display
);
bool _is_visible = true; // visible by default
bool _need_visibility_update = true;
boost::optional<int> _lod_level = boost::none; // none = no lod
size_t _lod_level_indice_count = 0;
public:
void draw ( math::frustum const& frustum
, opengl::scoped::use_program& mcnk_shader
, GLuint const& tex_coord_vbo
, const float& cull_distance
, const math::vector_3d& camera
, bool need_visibility_update
, bool show_unpaintable_chunks
, bool draw_paintability_overlay
, bool draw_chunk_flag_overlay
, bool draw_areaid_overlay
, std::map<int, misc::random_color>& area_id_colors
, int animtime
, display_mode display
);
//! \todo only this function should be public, all others should be called from it
void intersect (math::ray const&, selection_result*);
bool ChangeMCCV(math::vector_3d const& pos, math::vector_4d const& color, float change, float radius, bool editMode);
math::vector_3d pickMCCV(math::vector_3d const& pos);
ChunkWater* liquid_chunk() const;
void updateVerticesData();
void recalcNorms (std::function<boost::optional<float> (float, float)> height);
//! \todo implement Action stack for these
bool changeTerrain(math::vector_3d const& pos, float change, float radius, int BrushType, float inner_radius);
bool flattenTerrain(math::vector_3d const& pos, float remain, float radius, int BrushType, flatten_mode const& mode, const math::vector_3d& origin, math::degrees angle, math::degrees orientation);
bool blurTerrain ( math::vector_3d const& pos, float remain, float radius, int BrushType, flatten_mode const& mode
, std::function<boost::optional<float> (float, float)> height
);
void selectVertex(math::vector_3d const& pos, float radius, std::set<math::vector_3d*>& vertices);
void fixVertices(std::set<math::vector_3d*>& selected);
// for the vertex tool
bool isBorderChunk(std::set<math::vector_3d*>& selected);
//! \todo implement Action stack for these
bool paintTexture(math::vector_3d const& pos, Brush *brush, float strength, float pressure, scoped_blp_texture_reference texture);
bool replaceTexture(math::vector_3d const& pos, float radius, scoped_blp_texture_reference const& old_texture, scoped_blp_texture_reference new_texture);
bool canPaintTexture(scoped_blp_texture_reference texture);
int addTexture(scoped_blp_texture_reference texture);
void switchTexture(scoped_blp_texture_reference const& oldTexture, scoped_blp_texture_reference newTexture);
void eraseTextures();
void change_texture_flag(scoped_blp_texture_reference const& tex, std::size_t flag, bool add);
void clear_shadows();
//! \todo implement Action stack for these
bool isHole(int i, int j);
void setHole(math::vector_3d const& pos, bool big, bool add);
void setFlag(bool value, uint32_t);
int getAreaID();
void setAreaID(int ID);
bool GetVertex(float x, float z, math::vector_3d *V);
float getHeight(int x, int z);
float getMinHeight();
void clearHeight();
//! \todo this is ugly create a build struct or sth
void save(sExtendableArray &lADTFile, int &lCurrentPosition, int &lMCIN_Position, std::map<std::string, int> &lTextures, std::vector<WMOInstance> &lObjectInstances, std::vector<ModelInstance>& lModelInstances);
// fix the gaps with the chunk to the left
bool fixGapLeft(const MapChunk* chunk);
// fix the gaps with the chunk above
bool fixGapAbove(const MapChunk* chunk);
};

Some files were not shown because too many files have changed in this diff Show More