This reverts commit 3f1c018bcc
.
Signed-off-by: falkTX <falktx@gmail.com>
tags/v2.1-alpha1-winvst
@@ -0,0 +1,119 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_data_structures # | |||||
# --------------------------------- # | |||||
# Created by falkTX | |||||
# | |||||
CWD=../.. | |||||
MODULENAME=juce_data_structures | |||||
include ../Makefile.mk | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_DATA_STRUCTURES_FLAGS) -I.. | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
ifeq ($(MACOS),true) | |||||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||||
else | |||||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||||
OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
all: $(MODULEDIR)/$(MODULENAME).a | |||||
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||||
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||||
win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||||
win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
clean: | |||||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
-include $(OBJS:%.o=%.d) | |||||
-include $(OBJS_posix32:%.o=%.d) | |||||
-include $(OBJS_posix64:%.o=%.d) | |||||
-include $(OBJS_win32:%.o=%.d) | |||||
-include $(OBJS_win64:%.o=%.d) | |||||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -0,0 +1,109 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ApplicationProperties::ApplicationProperties() | |||||
: commonSettingsAreReadOnly (0) | |||||
{ | |||||
} | |||||
ApplicationProperties::~ApplicationProperties() | |||||
{ | |||||
closeFiles(); | |||||
} | |||||
//============================================================================== | |||||
void ApplicationProperties::setStorageParameters (const PropertiesFile::Options& newOptions) | |||||
{ | |||||
options = newOptions; | |||||
} | |||||
//============================================================================== | |||||
void ApplicationProperties::openFiles() | |||||
{ | |||||
// You need to call setStorageParameters() before trying to get hold of the properties! | |||||
jassert (options.applicationName.isNotEmpty()); | |||||
if (options.applicationName.isNotEmpty()) | |||||
{ | |||||
PropertiesFile::Options o (options); | |||||
if (userProps == nullptr) | |||||
{ | |||||
o.commonToAllUsers = false; | |||||
userProps = new PropertiesFile (o); | |||||
} | |||||
if (commonProps == nullptr) | |||||
{ | |||||
o.commonToAllUsers = true; | |||||
commonProps = new PropertiesFile (o); | |||||
} | |||||
userProps->setFallbackPropertySet (commonProps); | |||||
} | |||||
} | |||||
PropertiesFile* ApplicationProperties::getUserSettings() | |||||
{ | |||||
if (userProps == nullptr) | |||||
openFiles(); | |||||
return userProps; | |||||
} | |||||
PropertiesFile* ApplicationProperties::getCommonSettings (const bool returnUserPropsIfReadOnly) | |||||
{ | |||||
if (commonProps == nullptr) | |||||
openFiles(); | |||||
if (returnUserPropsIfReadOnly) | |||||
{ | |||||
if (commonSettingsAreReadOnly == 0) | |||||
commonSettingsAreReadOnly = commonProps->save() ? -1 : 1; | |||||
if (commonSettingsAreReadOnly > 0) | |||||
return userProps; | |||||
} | |||||
return commonProps; | |||||
} | |||||
bool ApplicationProperties::saveIfNeeded() | |||||
{ | |||||
return (userProps == nullptr || userProps->saveIfNeeded()) | |||||
&& (commonProps == nullptr || commonProps->saveIfNeeded()); | |||||
} | |||||
void ApplicationProperties::closeFiles() | |||||
{ | |||||
userProps = nullptr; | |||||
commonProps = nullptr; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,132 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Manages a collection of properties. | |||||
This is a slightly higher-level wrapper for managing PropertiesFile objects. | |||||
It holds two different PropertiesFile objects internally, one for user-specific | |||||
settings (stored in your user directory), and one for settings that are common to | |||||
all users (stored in a folder accessible to all users). | |||||
The class manages the creation of these files on-demand, allowing access via the | |||||
getUserSettings() and getCommonSettings() methods. | |||||
After creating an instance of an ApplicationProperties object, you should first | |||||
of all call setStorageParameters() to tell it the parameters to use to create | |||||
its files. | |||||
@see PropertiesFile | |||||
*/ | |||||
class JUCE_API ApplicationProperties | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** | |||||
Creates an ApplicationProperties object. | |||||
Before using it, you must call setStorageParameters() to give it the info | |||||
it needs to create the property files. | |||||
*/ | |||||
ApplicationProperties(); | |||||
/** Destructor. */ | |||||
~ApplicationProperties(); | |||||
//============================================================================== | |||||
/** Gives the object the information it needs to create the appropriate properties files. | |||||
See the PropertiesFile::Options class for details about what options you need to set. | |||||
*/ | |||||
void setStorageParameters (const PropertiesFile::Options& options); | |||||
/** Returns the current storage parameters. | |||||
@see setStorageParameters | |||||
*/ | |||||
const PropertiesFile::Options& getStorageParameters() const noexcept { return options; } | |||||
//============================================================================== | |||||
/** Returns the user settings file. | |||||
The first time this is called, it will create and load the properties file. | |||||
Note that when you search the user PropertiesFile for a value that it doesn't contain, | |||||
the common settings are used as a second-chance place to look. This is done via the | |||||
PropertySet::setFallbackPropertySet() method - by default the common settings are set | |||||
to the fallback for the user settings. | |||||
@see getCommonSettings | |||||
*/ | |||||
PropertiesFile* getUserSettings(); | |||||
/** Returns the common settings file. | |||||
The first time this is called, it will create and load the properties file. | |||||
@param returnUserPropsIfReadOnly if this is true, and the common properties file is | |||||
read-only (e.g. because the user doesn't have permission to write | |||||
to shared files), then this will return the user settings instead, | |||||
(like getUserSettings() would do). This is handy if you'd like to | |||||
write a value to the common settings, but if that's no possible, | |||||
then you'd rather write to the user settings than none at all. | |||||
If returnUserPropsIfReadOnly is false, this method will always return | |||||
the common settings, even if any changes to them can't be saved. | |||||
@see getUserSettings | |||||
*/ | |||||
PropertiesFile* getCommonSettings (bool returnUserPropsIfReadOnly); | |||||
//============================================================================== | |||||
/** Saves both files if they need to be saved. | |||||
@see PropertiesFile::saveIfNeeded | |||||
*/ | |||||
bool saveIfNeeded(); | |||||
/** Flushes and closes both files if they are open. | |||||
This flushes any pending changes to disk with PropertiesFile::saveIfNeeded() | |||||
and closes both files. They will then be re-opened the next time getUserSettings() | |||||
or getCommonSettings() is called. | |||||
*/ | |||||
void closeFiles(); | |||||
private: | |||||
//============================================================================== | |||||
PropertiesFile::Options options; | |||||
ScopedPointer<PropertiesFile> userProps, commonProps; | |||||
int commonSettingsAreReadOnly; | |||||
void openFiles(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationProperties) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,363 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace PropertyFileConstants | |||||
{ | |||||
JUCE_CONSTEXPR static const int magicNumber = (int) ByteOrder::littleEndianInt ('P', 'R', 'O', 'P'); | |||||
JUCE_CONSTEXPR static const int magicNumberCompressed = (int) ByteOrder::littleEndianInt ('C', 'P', 'R', 'P'); | |||||
JUCE_CONSTEXPR static const char* const fileTag = "PROPERTIES"; | |||||
JUCE_CONSTEXPR static const char* const valueTag = "VALUE"; | |||||
JUCE_CONSTEXPR static const char* const nameAttribute = "name"; | |||||
JUCE_CONSTEXPR static const char* const valueAttribute = "val"; | |||||
} | |||||
//============================================================================== | |||||
PropertiesFile::Options::Options() | |||||
: commonToAllUsers (false), | |||||
ignoreCaseOfKeyNames (false), | |||||
doNotSave (false), | |||||
millisecondsBeforeSaving (3000), | |||||
storageFormat (PropertiesFile::storeAsXML), | |||||
processLock (nullptr) | |||||
{ | |||||
} | |||||
File PropertiesFile::Options::getDefaultFile() const | |||||
{ | |||||
// mustn't have illegal characters in this name.. | |||||
jassert (applicationName == File::createLegalFileName (applicationName)); | |||||
#if JUCE_MAC || JUCE_IOS | |||||
File dir (commonToAllUsers ? "/Library/" | |||||
: "~/Library/"); | |||||
if (osxLibrarySubFolder != "Preferences" && ! osxLibrarySubFolder.startsWith ("Application Support")) | |||||
{ | |||||
/* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple | |||||
have changed their advice, and now stipulate that settings should go in "Library/Application Support". | |||||
Because older apps would be broken by a silent change in this class's behaviour, you must now | |||||
explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||||
In newer apps, you should always set this to "Application Support" | |||||
or "Application Support/YourSubFolderName". | |||||
If your app needs to load settings files that were created by older versions of juce and | |||||
you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||||
But.. for better Apple-compliance, the recommended approach would be to write some code that | |||||
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, | |||||
and then uses the new path. | |||||
*/ | |||||
jassertfalse; | |||||
dir = dir.getChildFile ("Application Support"); | |||||
} | |||||
else | |||||
{ | |||||
dir = dir.getChildFile (osxLibrarySubFolder); | |||||
} | |||||
if (folderName.isNotEmpty()) | |||||
dir = dir.getChildFile (folderName); | |||||
#elif JUCE_LINUX || JUCE_ANDROID | |||||
const File dir (File (commonToAllUsers ? "/var" : "~") | |||||
.getChildFile (folderName.isNotEmpty() ? folderName | |||||
: ("." + applicationName))); | |||||
#elif JUCE_WINDOWS | |||||
File dir (File::getSpecialLocation (commonToAllUsers ? File::commonApplicationDataDirectory | |||||
: File::userApplicationDataDirectory)); | |||||
if (dir == File()) | |||||
return {}; | |||||
dir = dir.getChildFile (folderName.isNotEmpty() ? folderName | |||||
: applicationName); | |||||
#endif | |||||
return (filenameSuffix.startsWithChar (L'.') | |||||
? dir.getChildFile (applicationName).withFileExtension (filenameSuffix) | |||||
: dir.getChildFile (applicationName + "." + filenameSuffix)); | |||||
} | |||||
//============================================================================== | |||||
PropertiesFile::PropertiesFile (const File& f, const Options& o) | |||||
: PropertySet (o.ignoreCaseOfKeyNames), | |||||
file (f), options (o), | |||||
loadedOk (false), needsWriting (false) | |||||
{ | |||||
reload(); | |||||
} | |||||
PropertiesFile::PropertiesFile (const Options& o) | |||||
: PropertySet (o.ignoreCaseOfKeyNames), | |||||
file (o.getDefaultFile()), options (o), | |||||
loadedOk (false), needsWriting (false) | |||||
{ | |||||
reload(); | |||||
} | |||||
bool PropertiesFile::reload() | |||||
{ | |||||
ProcessScopedLock pl (createProcessLock()); | |||||
if (pl != nullptr && ! pl->isLocked()) | |||||
return false; // locking failure.. | |||||
loadedOk = (! file.exists()) || loadAsBinary() || loadAsXml(); | |||||
return loadedOk; | |||||
} | |||||
PropertiesFile::~PropertiesFile() | |||||
{ | |||||
saveIfNeeded(); | |||||
} | |||||
InterProcessLock::ScopedLockType* PropertiesFile::createProcessLock() const | |||||
{ | |||||
return options.processLock != nullptr ? new InterProcessLock::ScopedLockType (*options.processLock) : nullptr; | |||||
} | |||||
bool PropertiesFile::saveIfNeeded() | |||||
{ | |||||
const ScopedLock sl (getLock()); | |||||
return (! needsWriting) || save(); | |||||
} | |||||
bool PropertiesFile::needsToBeSaved() const | |||||
{ | |||||
const ScopedLock sl (getLock()); | |||||
return needsWriting; | |||||
} | |||||
void PropertiesFile::setNeedsToBeSaved (const bool needsToBeSaved_) | |||||
{ | |||||
const ScopedLock sl (getLock()); | |||||
needsWriting = needsToBeSaved_; | |||||
} | |||||
bool PropertiesFile::save() | |||||
{ | |||||
const ScopedLock sl (getLock()); | |||||
stopTimer(); | |||||
if (options.doNotSave | |||||
|| file == File() | |||||
|| file.isDirectory() | |||||
|| ! file.getParentDirectory().createDirectory()) | |||||
return false; | |||||
if (options.storageFormat == storeAsXML) | |||||
return saveAsXml(); | |||||
return saveAsBinary(); | |||||
} | |||||
bool PropertiesFile::loadAsXml() | |||||
{ | |||||
XmlDocument parser (file); | |||||
ScopedPointer<XmlElement> doc (parser.getDocumentElement (true)); | |||||
if (doc != nullptr && doc->hasTagName (PropertyFileConstants::fileTag)) | |||||
{ | |||||
doc = parser.getDocumentElement(); | |||||
if (doc != nullptr) | |||||
{ | |||||
forEachXmlChildElementWithTagName (*doc, e, PropertyFileConstants::valueTag) | |||||
{ | |||||
const String name (e->getStringAttribute (PropertyFileConstants::nameAttribute)); | |||||
if (name.isNotEmpty()) | |||||
{ | |||||
getAllProperties().set (name, | |||||
e->getFirstChildElement() != nullptr | |||||
? e->getFirstChildElement()->createDocument ("", true) | |||||
: e->getStringAttribute (PropertyFileConstants::valueAttribute)); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
// must be a pretty broken XML file we're trying to parse here, | |||||
// or a sign that this object needs an InterProcessLock, | |||||
// or just a failure reading the file. This last reason is why | |||||
// we don't jassertfalse here. | |||||
} | |||||
return false; | |||||
} | |||||
bool PropertiesFile::saveAsXml() | |||||
{ | |||||
XmlElement doc (PropertyFileConstants::fileTag); | |||||
const StringPairArray& props = getAllProperties(); | |||||
for (int i = 0; i < props.size(); ++i) | |||||
{ | |||||
XmlElement* const e = doc.createNewChildElement (PropertyFileConstants::valueTag); | |||||
e->setAttribute (PropertyFileConstants::nameAttribute, props.getAllKeys() [i]); | |||||
// if the value seems to contain xml, store it as such.. | |||||
if (XmlElement* const childElement = XmlDocument::parse (props.getAllValues() [i])) | |||||
e->addChildElement (childElement); | |||||
else | |||||
e->setAttribute (PropertyFileConstants::valueAttribute, props.getAllValues() [i]); | |||||
} | |||||
ProcessScopedLock pl (createProcessLock()); | |||||
if (pl != nullptr && ! pl->isLocked()) | |||||
return false; // locking failure.. | |||||
if (doc.writeToFile (file, String())) | |||||
{ | |||||
needsWriting = false; | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool PropertiesFile::loadAsBinary() | |||||
{ | |||||
FileInputStream fileStream (file); | |||||
if (fileStream.openedOk()) | |||||
{ | |||||
const int magicNumber = fileStream.readInt(); | |||||
if (magicNumber == PropertyFileConstants::magicNumberCompressed) | |||||
{ | |||||
SubregionStream subStream (&fileStream, 4, -1, false); | |||||
GZIPDecompressorInputStream gzip (subStream); | |||||
return loadAsBinary (gzip); | |||||
} | |||||
if (magicNumber == PropertyFileConstants::magicNumber) | |||||
return loadAsBinary (fileStream); | |||||
} | |||||
return false; | |||||
} | |||||
bool PropertiesFile::loadAsBinary (InputStream& input) | |||||
{ | |||||
BufferedInputStream in (input, 2048); | |||||
int numValues = in.readInt(); | |||||
while (--numValues >= 0 && ! in.isExhausted()) | |||||
{ | |||||
const String key (in.readString()); | |||||
const String value (in.readString()); | |||||
jassert (key.isNotEmpty()); | |||||
if (key.isNotEmpty()) | |||||
getAllProperties().set (key, value); | |||||
} | |||||
return true; | |||||
} | |||||
bool PropertiesFile::saveAsBinary() | |||||
{ | |||||
ProcessScopedLock pl (createProcessLock()); | |||||
if (pl != nullptr && ! pl->isLocked()) | |||||
return false; // locking failure.. | |||||
TemporaryFile tempFile (file); | |||||
ScopedPointer<OutputStream> out (tempFile.getFile().createOutputStream()); | |||||
if (out != nullptr) | |||||
{ | |||||
if (options.storageFormat == storeAsCompressedBinary) | |||||
{ | |||||
out->writeInt (PropertyFileConstants::magicNumberCompressed); | |||||
out->flush(); | |||||
out = new GZIPCompressorOutputStream (out.release(), 9, true); | |||||
} | |||||
else | |||||
{ | |||||
// have you set up the storage option flags correctly? | |||||
jassert (options.storageFormat == storeAsBinary); | |||||
out->writeInt (PropertyFileConstants::magicNumber); | |||||
} | |||||
const StringPairArray& props = getAllProperties(); | |||||
const int numProperties = props.size(); | |||||
const StringArray& keys = props.getAllKeys(); | |||||
const StringArray& values = props.getAllValues(); | |||||
out->writeInt (numProperties); | |||||
for (int i = 0; i < numProperties; ++i) | |||||
{ | |||||
out->writeString (keys[i]); | |||||
out->writeString (values[i]); | |||||
} | |||||
out = nullptr; | |||||
if (tempFile.overwriteTargetFileWithTemporary()) | |||||
{ | |||||
needsWriting = false; | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void PropertiesFile::timerCallback() | |||||
{ | |||||
saveIfNeeded(); | |||||
} | |||||
void PropertiesFile::propertyChanged() | |||||
{ | |||||
sendChangeMessage(); | |||||
needsWriting = true; | |||||
if (options.millisecondsBeforeSaving > 0) | |||||
startTimer (options.millisecondsBeforeSaving); | |||||
else if (options.millisecondsBeforeSaving == 0) | |||||
saveIfNeeded(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,252 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** Wrapper on a file that stores a list of key/value data pairs. | |||||
Useful for storing application settings, etc. See the PropertySet class for | |||||
the interfaces that read and write values. | |||||
Not designed for very large amounts of data, as it keeps all the values in | |||||
memory and writes them out to disk lazily when they are changed. | |||||
Because this class derives from ChangeBroadcaster, ChangeListeners can be registered | |||||
with it, and these will be signalled when a value changes. | |||||
@see PropertySet | |||||
*/ | |||||
class JUCE_API PropertiesFile : public PropertySet, | |||||
public ChangeBroadcaster, | |||||
private Timer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
enum StorageFormat | |||||
{ | |||||
storeAsBinary, | |||||
storeAsCompressedBinary, | |||||
storeAsXML | |||||
}; | |||||
//============================================================================== | |||||
struct JUCE_API Options | |||||
{ | |||||
/** Creates an empty Options structure. | |||||
You'll need to fill-in the data members appropriately before using this structure. | |||||
*/ | |||||
Options(); | |||||
/** The name of your application - this is used to help generate the path and filename | |||||
at which the properties file will be stored. */ | |||||
String applicationName; | |||||
/** The suffix to use for your properties file. | |||||
It doesn't really matter what this is - you may want to use ".settings" or | |||||
".properties" or something. If the suffix includes the prefixing dot (for example | |||||
".settings") then the suffix of applicationName will be replaced with your suffix | |||||
("MyApp.exe" -> "MyApp.settings"). If your filenameSuffix does NOT include the dot, | |||||
then the suffix will be appended to the applicationName ("MyApp.exe" -> | |||||
"MyApp.exe.settings"). | |||||
*/ | |||||
String filenameSuffix; | |||||
/** The name of a subfolder in which you'd like your properties file to live. | |||||
See the getDefaultFile() method for more details about how this is used. | |||||
*/ | |||||
String folderName; | |||||
/** If you're using properties files on a Mac, you must set this value - failure to | |||||
do so will cause a runtime assertion. | |||||
The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple | |||||
have changed their advice, and now stipulate that settings should go in "Library/Application Support". | |||||
Because older apps would be broken by a silent change in this class's behaviour, you must now | |||||
explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||||
In newer apps, you should always set this to "Application Support" or | |||||
"Application Support/YourSubFolderName". | |||||
If your app needs to load settings files that were created by older versions of juce and | |||||
you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||||
But.. for better Apple-compliance, the recommended approach would be to write some code that | |||||
finds your old settings files in ~/Library/Preferences, moves them to ~/Library/Application Support, | |||||
and then uses the new path. | |||||
*/ | |||||
String osxLibrarySubFolder; | |||||
/** If true, the file will be created in a location that's shared between users. | |||||
The default constructor initialises this value to false. | |||||
*/ | |||||
bool commonToAllUsers; | |||||
/** If true, this means that property names are matched in a case-insensitive manner. | |||||
See the PropertySet constructor for more info. | |||||
The default constructor initialises this value to false. | |||||
*/ | |||||
bool ignoreCaseOfKeyNames; | |||||
/** If set to true, this prevents the file from being written to disk. */ | |||||
bool doNotSave; | |||||
/** If this is zero or greater, then after a value is changed, the object will wait | |||||
for this amount of time and then save the file. If this zero, the file will be | |||||
written to disk immediately on being changed (which might be slow, as it'll re-write | |||||
synchronously each time a value-change method is called). If it is less than zero, | |||||
the file won't be saved until save() or saveIfNeeded() are explicitly called. | |||||
The default constructor sets this to a reasonable value of a few seconds, so you | |||||
only need to change it if you need a special case. | |||||
*/ | |||||
int millisecondsBeforeSaving; | |||||
/** Specifies whether the file should be written as XML, binary, etc. | |||||
The default constructor sets this to storeAsXML, so you only need to set it explicitly | |||||
if you want to use a different format. | |||||
*/ | |||||
StorageFormat storageFormat; | |||||
/** An optional InterprocessLock object that will be used to prevent multiple threads or | |||||
processes from writing to the file at the same time. The PropertiesFile will keep a | |||||
pointer to this object but will not take ownership of it - the caller is responsible for | |||||
making sure that the lock doesn't get deleted before the PropertiesFile has been deleted. | |||||
The default constructor initialises this value to nullptr, so you don't need to touch it | |||||
unless you want to use a lock. | |||||
*/ | |||||
InterProcessLock* processLock; | |||||
/** This can be called to suggest a file that should be used, based on the values | |||||
in this structure. | |||||
So on a Mac, this will return a file called: | |||||
~/Library/[osxLibrarySubFolder]/[folderName]/[applicationName].[filenameSuffix] | |||||
On Windows it'll return something like: | |||||
C:\\Documents and Settings\\username\\Application Data\\[folderName]\\[applicationName].[filenameSuffix] | |||||
On Linux it'll return | |||||
~/[folderName]/[applicationName].[filenameSuffix] | |||||
If the folderName variable is empty, it'll use the app name for this (or omit the | |||||
folder name on the Mac). | |||||
The paths will also vary depending on whether commonToAllUsers is true. | |||||
*/ | |||||
File getDefaultFile() const; | |||||
}; | |||||
//============================================================================== | |||||
/** Creates a PropertiesFile object. | |||||
The file used will be chosen by calling PropertiesFile::Options::getDefaultFile() | |||||
for the options provided. To set the file explicitly, use the other constructor. | |||||
*/ | |||||
explicit PropertiesFile (const Options& options); | |||||
/** Creates a PropertiesFile object. | |||||
Unlike the other constructor, this one allows you to explicitly set the file that you | |||||
want to be used, rather than using the default one. | |||||
*/ | |||||
PropertiesFile (const File& file, | |||||
const Options& options); | |||||
/** Destructor. | |||||
When deleted, the file will first call saveIfNeeded() to flush any changes to disk. | |||||
*/ | |||||
~PropertiesFile(); | |||||
//============================================================================== | |||||
/** Returns true if this file was created from a valid (or non-existent) file. | |||||
If the file failed to load correctly because it was corrupt or had insufficient | |||||
access, this will be false. | |||||
*/ | |||||
bool isValidFile() const noexcept { return loadedOk; } | |||||
//============================================================================== | |||||
/** This will flush all the values to disk if they've changed since the last | |||||
time they were saved. | |||||
Returns false if it fails to write to the file for some reason (maybe because | |||||
it's read-only or the directory doesn't exist or something). | |||||
@see save | |||||
*/ | |||||
bool saveIfNeeded(); | |||||
/** This will force a write-to-disk of the current values, regardless of whether | |||||
anything has changed since the last save. | |||||
Returns false if it fails to write to the file for some reason (maybe because | |||||
it's read-only or the directory doesn't exist or something). | |||||
@see saveIfNeeded | |||||
*/ | |||||
bool save(); | |||||
/** Returns true if the properties have been altered since the last time they were saved. | |||||
The file is flagged as needing to be saved when you change a value, but you can | |||||
explicitly set this flag with setNeedsToBeSaved(). | |||||
*/ | |||||
bool needsToBeSaved() const; | |||||
/** Explicitly sets the flag to indicate whether the file needs saving or not. | |||||
@see needsToBeSaved | |||||
*/ | |||||
void setNeedsToBeSaved (bool needsToBeSaved); | |||||
/** Attempts to reload the settings from the file. */ | |||||
bool reload(); | |||||
//============================================================================== | |||||
/** Returns the file that's being used. */ | |||||
const File& getFile() const noexcept { return file; } | |||||
protected: | |||||
/** @internal */ | |||||
void propertyChanged() override; | |||||
private: | |||||
//============================================================================== | |||||
File file; | |||||
Options options; | |||||
bool loadedOk, needsWriting; | |||||
typedef const ScopedPointer<InterProcessLock::ScopedLockType> ProcessScopedLock; | |||||
InterProcessLock::ScopedLockType* createProcessLock() const; | |||||
void timerCallback() override; | |||||
bool saveAsXml(); | |||||
bool saveAsBinary(); | |||||
bool loadAsXml(); | |||||
bool loadAsBinary(); | |||||
bool loadAsBinary (InputStream&); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesFile) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,44 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
#ifdef JUCE_DATA_STRUCTURES_H_INCLUDED | |||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||||
already included any other headers - just put it inside a file on its own, possibly with your config | |||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||||
header files that the compiler may be using. | |||||
*/ | |||||
#error "Incorrect use of JUCE cpp file" | |||||
#endif | |||||
#include "juce_data_structures.h" | |||||
#include "values/juce_Value.cpp" | |||||
#include "values/juce_ValueTree.cpp" | |||||
#include "values/juce_ValueTreeSynchroniser.cpp" | |||||
#include "values/juce_CachedValue.cpp" | |||||
#include "undomanager/juce_UndoManager.cpp" | |||||
#include "app_properties/juce_ApplicationProperties.cpp" | |||||
#include "app_properties/juce_PropertiesFile.cpp" |
@@ -0,0 +1,64 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_data_structures | |||||
vendor: juce | |||||
version: 5.1.2 | |||||
name: JUCE data model helper classes | |||||
description: Classes for undo/redo management, and smart data structures. | |||||
website: http://www.juce.com/juce | |||||
license: GPL/Commercial | |||||
dependencies: juce_events | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#pragma once | |||||
#define JUCE_DATA_STRUCTURES_H_INCLUDED | |||||
//============================================================================== | |||||
#include <juce_events/juce_events.h> | |||||
#include "undomanager/juce_UndoableAction.h" | |||||
#include "undomanager/juce_UndoManager.h" | |||||
#include "values/juce_Value.h" | |||||
#include "values/juce_ValueTree.h" | |||||
#include "values/juce_ValueTreeSynchroniser.h" | |||||
#include "values/juce_CachedValue.h" | |||||
#include "app_properties/juce_PropertiesFile.h" | |||||
#include "app_properties/juce_ApplicationProperties.h" |
@@ -0,0 +1,352 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct UndoManager::ActionSet | |||||
{ | |||||
ActionSet (const String& transactionName) | |||||
: name (transactionName), | |||||
time (Time::getCurrentTime()) | |||||
{} | |||||
bool perform() const | |||||
{ | |||||
for (int i = 0; i < actions.size(); ++i) | |||||
if (! actions.getUnchecked(i)->perform()) | |||||
return false; | |||||
return true; | |||||
} | |||||
bool undo() const | |||||
{ | |||||
for (int i = actions.size(); --i >= 0;) | |||||
if (! actions.getUnchecked(i)->undo()) | |||||
return false; | |||||
return true; | |||||
} | |||||
int getTotalSize() const | |||||
{ | |||||
int total = 0; | |||||
for (int i = actions.size(); --i >= 0;) | |||||
total += actions.getUnchecked(i)->getSizeInUnits(); | |||||
return total; | |||||
} | |||||
OwnedArray<UndoableAction> actions; | |||||
String name; | |||||
Time time; | |||||
}; | |||||
//============================================================================== | |||||
UndoManager::UndoManager (const int maxNumberOfUnitsToKeep, | |||||
const int minimumTransactions) | |||||
: totalUnitsStored (0), | |||||
nextIndex (0), | |||||
newTransaction (true), | |||||
reentrancyCheck (false) | |||||
{ | |||||
setMaxNumberOfStoredUnits (maxNumberOfUnitsToKeep, | |||||
minimumTransactions); | |||||
} | |||||
UndoManager::~UndoManager() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void UndoManager::clearUndoHistory() | |||||
{ | |||||
transactions.clear(); | |||||
totalUnitsStored = 0; | |||||
nextIndex = 0; | |||||
sendChangeMessage(); | |||||
} | |||||
int UndoManager::getNumberOfUnitsTakenUpByStoredCommands() const | |||||
{ | |||||
return totalUnitsStored; | |||||
} | |||||
void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, | |||||
const int minimumTransactions) | |||||
{ | |||||
maxNumUnitsToKeep = jmax (1, maxNumberOfUnitsToKeep); | |||||
minimumTransactionsToKeep = jmax (1, minimumTransactions); | |||||
} | |||||
//============================================================================== | |||||
bool UndoManager::perform (UndoableAction* const newAction, const String& actionName) | |||||
{ | |||||
if (perform (newAction)) | |||||
{ | |||||
if (actionName.isNotEmpty()) | |||||
setCurrentTransactionName (actionName); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool UndoManager::perform (UndoableAction* const newAction) | |||||
{ | |||||
if (newAction != nullptr) | |||||
{ | |||||
ScopedPointer<UndoableAction> action (newAction); | |||||
if (reentrancyCheck) | |||||
{ | |||||
jassertfalse; // don't call perform() recursively from the UndoableAction::perform() | |||||
// or undo() methods, or else these actions will be discarded! | |||||
return false; | |||||
} | |||||
if (action->perform()) | |||||
{ | |||||
ActionSet* actionSet = getCurrentSet(); | |||||
if (actionSet != nullptr && ! newTransaction) | |||||
{ | |||||
if (UndoableAction* const lastAction = actionSet->actions.getLast()) | |||||
{ | |||||
if (UndoableAction* const coalescedAction = lastAction->createCoalescedAction (action)) | |||||
{ | |||||
action = coalescedAction; | |||||
totalUnitsStored -= lastAction->getSizeInUnits(); | |||||
actionSet->actions.removeLast(); | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
actionSet = new ActionSet (newTransactionName); | |||||
transactions.insert (nextIndex, actionSet); | |||||
++nextIndex; | |||||
} | |||||
totalUnitsStored += action->getSizeInUnits(); | |||||
actionSet->actions.add (action.release()); | |||||
newTransaction = false; | |||||
moveFutureTransactionsToStash(); | |||||
dropOldTransactionsIfTooLarge(); | |||||
sendChangeMessage(); | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void UndoManager::moveFutureTransactionsToStash() | |||||
{ | |||||
if (nextIndex < transactions.size()) | |||||
{ | |||||
stashedFutureTransactions.clear(); | |||||
while (nextIndex < transactions.size()) | |||||
{ | |||||
ActionSet* removed = transactions.removeAndReturn (nextIndex); | |||||
stashedFutureTransactions.add (removed); | |||||
totalUnitsStored -= removed->getTotalSize(); | |||||
} | |||||
} | |||||
} | |||||
void UndoManager::restoreStashedFutureTransactions() | |||||
{ | |||||
while (nextIndex < transactions.size()) | |||||
{ | |||||
totalUnitsStored -= transactions.getUnchecked (nextIndex)->getTotalSize(); | |||||
transactions.remove (nextIndex); | |||||
} | |||||
for (int i = 0; i < stashedFutureTransactions.size(); ++i) | |||||
{ | |||||
ActionSet* action = stashedFutureTransactions.removeAndReturn (i); | |||||
totalUnitsStored += action->getTotalSize(); | |||||
transactions.add (action); | |||||
} | |||||
stashedFutureTransactions.clearQuick (false); | |||||
} | |||||
void UndoManager::dropOldTransactionsIfTooLarge() | |||||
{ | |||||
while (nextIndex > 0 | |||||
&& totalUnitsStored > maxNumUnitsToKeep | |||||
&& transactions.size() > minimumTransactionsToKeep) | |||||
{ | |||||
totalUnitsStored -= transactions.getFirst()->getTotalSize(); | |||||
transactions.remove (0); | |||||
--nextIndex; | |||||
// if this fails, then some actions may not be returning | |||||
// consistent results from their getSizeInUnits() method | |||||
jassert (totalUnitsStored >= 0); | |||||
} | |||||
} | |||||
void UndoManager::beginNewTransaction() noexcept | |||||
{ | |||||
beginNewTransaction (String()); | |||||
} | |||||
void UndoManager::beginNewTransaction (const String& actionName) noexcept | |||||
{ | |||||
newTransaction = true; | |||||
newTransactionName = actionName; | |||||
} | |||||
void UndoManager::setCurrentTransactionName (const String& newName) noexcept | |||||
{ | |||||
if (newTransaction) | |||||
newTransactionName = newName; | |||||
else if (ActionSet* action = getCurrentSet()) | |||||
action->name = newName; | |||||
} | |||||
String UndoManager::getCurrentTransactionName() const noexcept | |||||
{ | |||||
if (ActionSet* action = getCurrentSet()) | |||||
return action->name; | |||||
return newTransactionName; | |||||
} | |||||
//============================================================================== | |||||
UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; } | |||||
UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; } | |||||
bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; } | |||||
bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; } | |||||
bool UndoManager::undo() | |||||
{ | |||||
if (const ActionSet* const s = getCurrentSet()) | |||||
{ | |||||
const ScopedValueSetter<bool> setter (reentrancyCheck, true); | |||||
if (s->undo()) | |||||
--nextIndex; | |||||
else | |||||
clearUndoHistory(); | |||||
beginNewTransaction(); | |||||
sendChangeMessage(); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool UndoManager::redo() | |||||
{ | |||||
if (const ActionSet* const s = getNextSet()) | |||||
{ | |||||
const ScopedValueSetter<bool> setter (reentrancyCheck, true); | |||||
if (s->perform()) | |||||
++nextIndex; | |||||
else | |||||
clearUndoHistory(); | |||||
beginNewTransaction(); | |||||
sendChangeMessage(); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
String UndoManager::getUndoDescription() const | |||||
{ | |||||
if (auto* s = getCurrentSet()) | |||||
return s->name; | |||||
return {}; | |||||
} | |||||
String UndoManager::getRedoDescription() const | |||||
{ | |||||
if (auto* s = getNextSet()) | |||||
return s->name; | |||||
return {}; | |||||
} | |||||
Time UndoManager::getTimeOfUndoTransaction() const | |||||
{ | |||||
if (auto* s = getCurrentSet()) | |||||
return s->time; | |||||
return {}; | |||||
} | |||||
Time UndoManager::getTimeOfRedoTransaction() const | |||||
{ | |||||
if (auto* s = getNextSet()) | |||||
return s->time; | |||||
return Time::getCurrentTime(); | |||||
} | |||||
bool UndoManager::undoCurrentTransactionOnly() | |||||
{ | |||||
if ((! newTransaction) && undo()) | |||||
{ | |||||
restoreStashedFutureTransactions(); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const | |||||
{ | |||||
if (! newTransaction) | |||||
if (const ActionSet* const s = getCurrentSet()) | |||||
for (int i = 0; i < s->actions.size(); ++i) | |||||
actionsFound.add (s->actions.getUnchecked(i)); | |||||
} | |||||
int UndoManager::getNumActionsInCurrentTransaction() const | |||||
{ | |||||
if (! newTransaction) | |||||
if (const ActionSet* const s = getCurrentSet()) | |||||
return s->actions.size(); | |||||
return 0; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,246 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Manages a list of undo/redo commands. | |||||
An UndoManager object keeps a list of past actions and can use these actions | |||||
to move backwards and forwards through an undo history. | |||||
To use it, create subclasses of UndoableAction which perform all the | |||||
actions you need, then when you need to actually perform an action, create one | |||||
and pass it to the UndoManager's perform() method. | |||||
The manager also uses the concept of 'transactions' to group the actions | |||||
together - all actions performed between calls to beginNewTransaction() are | |||||
grouped together and are all undone/redone as a group. | |||||
The UndoManager is a ChangeBroadcaster, so listeners can register to be told | |||||
when actions are performed or undone. | |||||
@see UndoableAction | |||||
*/ | |||||
class JUCE_API UndoManager : public ChangeBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an UndoManager. | |||||
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value | |||||
to indicate how much storage it takes up | |||||
(UndoableAction::getSizeInUnits()), so this | |||||
lets you specify the maximum total number of | |||||
units that the undomanager is allowed to | |||||
keep in memory before letting the older actions | |||||
drop off the end of the list. | |||||
@param minimumTransactionsToKeep this specifies the minimum number of transactions | |||||
that will be kept, even if this involves exceeding | |||||
the amount of space specified in maxNumberOfUnitsToKeep | |||||
*/ | |||||
UndoManager (int maxNumberOfUnitsToKeep = 30000, | |||||
int minimumTransactionsToKeep = 30); | |||||
/** Destructor. */ | |||||
~UndoManager(); | |||||
//============================================================================== | |||||
/** Deletes all stored actions in the list. */ | |||||
void clearUndoHistory(); | |||||
/** Returns the current amount of space to use for storing UndoableAction objects. | |||||
@see setMaxNumberOfStoredUnits | |||||
*/ | |||||
int getNumberOfUnitsTakenUpByStoredCommands() const; | |||||
/** Sets the amount of space that can be used for storing UndoableAction objects. | |||||
@param maxNumberOfUnitsToKeep each UndoableAction object returns a value | |||||
to indicate how much storage it takes up | |||||
(UndoableAction::getSizeInUnits()), so this | |||||
lets you specify the maximum total number of | |||||
units that the undomanager is allowed to | |||||
keep in memory before letting the older actions | |||||
drop off the end of the list. | |||||
@param minimumTransactionsToKeep this specifies the minimum number of transactions | |||||
that will be kept, even if this involves exceeding | |||||
the amount of space specified in maxNumberOfUnitsToKeep | |||||
@see getNumberOfUnitsTakenUpByStoredCommands | |||||
*/ | |||||
void setMaxNumberOfStoredUnits (int maxNumberOfUnitsToKeep, | |||||
int minimumTransactionsToKeep); | |||||
//============================================================================== | |||||
/** Performs an action and adds it to the undo history list. | |||||
@param action the action to perform - this object will be deleted by | |||||
the UndoManager when no longer needed | |||||
@returns true if the command succeeds - see UndoableAction::perform | |||||
@see beginNewTransaction | |||||
*/ | |||||
bool perform (UndoableAction* action); | |||||
/** Performs an action and also gives it a name. | |||||
@param action the action to perform - this object will be deleted by | |||||
the UndoManager when no longer needed | |||||
@param actionName if this string is non-empty, the current transaction will be | |||||
given this name; if it's empty, the current transaction name will | |||||
be left unchanged. See setCurrentTransactionName() | |||||
@returns true if the command succeeds - see UndoableAction::perform | |||||
@see beginNewTransaction | |||||
*/ | |||||
bool perform (UndoableAction* action, const String& actionName); | |||||
/** Starts a new group of actions that together will be treated as a single transaction. | |||||
All actions that are passed to the perform() method between calls to this | |||||
method are grouped together and undone/redone together by a single call to | |||||
undo() or redo(). | |||||
*/ | |||||
void beginNewTransaction() noexcept; | |||||
/** Starts a new group of actions that together will be treated as a single transaction. | |||||
All actions that are passed to the perform() method between calls to this | |||||
method are grouped together and undone/redone together by a single call to | |||||
undo() or redo(). | |||||
@param actionName a description of the transaction that is about to be | |||||
performed | |||||
*/ | |||||
void beginNewTransaction (const String& actionName) noexcept; | |||||
/** Changes the name stored for the current transaction. | |||||
Each transaction is given a name when the beginNewTransaction() method is | |||||
called, but this can be used to change that name without starting a new | |||||
transaction. | |||||
*/ | |||||
void setCurrentTransactionName (const String& newName) noexcept; | |||||
/** Returns the name of the current transaction. | |||||
@see setCurrentTransactionName | |||||
*/ | |||||
String getCurrentTransactionName() const noexcept; | |||||
//============================================================================== | |||||
/** Returns true if there's at least one action in the list to undo. | |||||
@see getUndoDescription, undo, canRedo | |||||
*/ | |||||
bool canUndo() const noexcept; | |||||
/** Returns the name of the transaction that will be rolled-back when undo() is called. | |||||
@see undo | |||||
*/ | |||||
String getUndoDescription() const; | |||||
/** Tries to roll-back the last transaction. | |||||
@returns true if the transaction can be undone, and false if it fails, or | |||||
if there aren't any transactions to undo | |||||
*/ | |||||
bool undo(); | |||||
/** Tries to roll-back any actions that were added to the current transaction. | |||||
This will perform an undo() only if there are some actions in the undo list | |||||
that were added after the last call to beginNewTransaction(). | |||||
This is useful because it lets you call beginNewTransaction(), then | |||||
perform an operation which may or may not actually perform some actions, and | |||||
then call this method to get rid of any actions that might have been done | |||||
without it rolling back the previous transaction if nothing was actually | |||||
done. | |||||
@returns true if any actions were undone. | |||||
*/ | |||||
bool undoCurrentTransactionOnly(); | |||||
/** Returns a list of the UndoableAction objects that have been performed during the | |||||
transaction that is currently open. | |||||
Effectively, this is the list of actions that would be undone if undoCurrentTransactionOnly() | |||||
were to be called now. | |||||
The first item in the list is the earliest action performed. | |||||
*/ | |||||
void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const; | |||||
/** Returns the number of UndoableAction objects that have been performed during the | |||||
transaction that is currently open. | |||||
@see getActionsInCurrentTransaction | |||||
*/ | |||||
int getNumActionsInCurrentTransaction() const; | |||||
/** Returns the time to which the state would be restored if undo() was to be called. | |||||
If an undo isn't currently possible, it'll return Time(). | |||||
*/ | |||||
Time getTimeOfUndoTransaction() const; | |||||
/** Returns the time to which the state would be restored if redo() was to be called. | |||||
If a redo isn't currently possible, it'll return Time::getCurrentTime(). | |||||
*/ | |||||
Time getTimeOfRedoTransaction() const; | |||||
//============================================================================== | |||||
/** Returns true if there's at least one action in the list to redo. | |||||
@see getRedoDescription, redo, canUndo | |||||
*/ | |||||
bool canRedo() const noexcept; | |||||
/** Returns the name of the transaction that will be redone when redo() is called. | |||||
@see redo | |||||
*/ | |||||
String getRedoDescription() const; | |||||
/** Tries to redo the last transaction that was undone. | |||||
@returns true if the transaction can be redone, and false if it fails, or | |||||
if there aren't any transactions to redo | |||||
*/ | |||||
bool redo(); | |||||
private: | |||||
//============================================================================== | |||||
struct ActionSet; | |||||
friend struct ContainerDeletePolicy<ActionSet>; | |||||
OwnedArray<ActionSet> transactions, stashedFutureTransactions; | |||||
String newTransactionName; | |||||
int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex; | |||||
bool newTransaction, reentrancyCheck; | |||||
ActionSet* getCurrentSet() const noexcept; | |||||
ActionSet* getNextSet() const noexcept; | |||||
void moveFutureTransactionsToStash(); | |||||
void restoreStashedFutureTransactions(); | |||||
void dropOldTransactionsIfTooLarge(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UndoManager) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,99 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Used by the UndoManager class to store an action which can be done | |||||
and undone. | |||||
@see UndoManager | |||||
*/ | |||||
class JUCE_API UndoableAction | |||||
{ | |||||
protected: | |||||
/** Creates an action. */ | |||||
UndoableAction() noexcept {} | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~UndoableAction() {} | |||||
//============================================================================== | |||||
/** Overridden by a subclass to perform the action. | |||||
This method is called by the UndoManager, and shouldn't be used directly by | |||||
applications. | |||||
Be careful not to make any calls in a perform() method that could call | |||||
recursively back into the UndoManager::perform() method | |||||
@returns true if the action could be performed. | |||||
@see UndoManager::perform | |||||
*/ | |||||
virtual bool perform() = 0; | |||||
/** Overridden by a subclass to undo the action. | |||||
This method is called by the UndoManager, and shouldn't be used directly by | |||||
applications. | |||||
Be careful not to make any calls in an undo() method that could call | |||||
recursively back into the UndoManager::perform() method | |||||
@returns true if the action could be undone without any errors. | |||||
@see UndoManager::perform | |||||
*/ | |||||
virtual bool undo() = 0; | |||||
//============================================================================== | |||||
/** Returns a value to indicate how much memory this object takes up. | |||||
Because the UndoManager keeps a list of UndoableActions, this is used | |||||
to work out how much space each one will take up, so that the UndoManager | |||||
can work out how many to keep. | |||||
The default value returned here is 10 - units are arbitrary and | |||||
don't have to be accurate. | |||||
@see UndoManager::getNumberOfUnitsTakenUpByStoredCommands, | |||||
UndoManager::setMaxNumberOfStoredUnits | |||||
*/ | |||||
virtual int getSizeInUnits() { return 10; } | |||||
/** Allows multiple actions to be coalesced into a single action object, to reduce storage space. | |||||
If possible, this method should create and return a single action that does the same job as | |||||
this one followed by the supplied action. | |||||
If it's not possible to merge the two actions, the method should return a nullptr. | |||||
*/ | |||||
virtual UndoableAction* createCoalescedAction (UndoableAction* nextAction) { ignoreUnused (nextAction); return nullptr; } | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,159 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_UNIT_TESTS | |||||
class CachedValueTests : public UnitTest | |||||
{ | |||||
public: | |||||
CachedValueTests() : UnitTest ("CachedValues", "Values") {} | |||||
void runTest() override | |||||
{ | |||||
beginTest ("default constructor"); | |||||
{ | |||||
CachedValue<String> cv; | |||||
expect (cv.isUsingDefault()); | |||||
expect (cv.get() == String()); | |||||
} | |||||
beginTest ("without default value"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", "testvalue", nullptr); | |||||
CachedValue<String> cv (t, "testkey", nullptr); | |||||
expect (! cv.isUsingDefault()); | |||||
expect (cv.get() == "testvalue"); | |||||
cv.resetToDefault(); | |||||
expect (cv.isUsingDefault()); | |||||
expect (cv.get() == String()); | |||||
} | |||||
beginTest ("with default value"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", "testvalue", nullptr); | |||||
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||||
expect (! cv.isUsingDefault()); | |||||
expect (cv.get() == "testvalue"); | |||||
cv.resetToDefault(); | |||||
expect (cv.isUsingDefault()); | |||||
expect (cv.get() == "defaultvalue"); | |||||
} | |||||
beginTest ("with default value (int)"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", 23, nullptr); | |||||
CachedValue<int> cv (t, "testkey", nullptr, 34); | |||||
expect (! cv.isUsingDefault()); | |||||
expect (cv == 23); | |||||
expectEquals (cv.get(), 23); | |||||
cv.resetToDefault(); | |||||
expect (cv.isUsingDefault()); | |||||
expect (cv == 34); | |||||
} | |||||
beginTest ("with void value"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", var(), nullptr); | |||||
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||||
expect (! cv.isUsingDefault()); | |||||
expect (cv == ""); | |||||
expectEquals (cv.get(), String()); | |||||
} | |||||
beginTest ("with non-existent value"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||||
expect (cv.isUsingDefault()); | |||||
expect (cv == "defaultvalue"); | |||||
expect (cv.get() == "defaultvalue"); | |||||
} | |||||
beginTest ("with value changing"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", "oldvalue", nullptr); | |||||
CachedValue<String> cv (t, "testkey", nullptr, "defaultvalue"); | |||||
expect (cv == "oldvalue"); | |||||
t.setProperty ("testkey", "newvalue", nullptr); | |||||
expect (cv != "oldvalue"); | |||||
expect (cv == "newvalue"); | |||||
} | |||||
beginTest ("set value"); | |||||
{ | |||||
ValueTree t ("root"); | |||||
t.setProperty ("testkey", 23, nullptr); | |||||
CachedValue<int> cv (t, "testkey", nullptr, 45); | |||||
cv = 34; | |||||
expectEquals ((int) t["testkey"], 34); | |||||
cv.resetToDefault(); | |||||
expect (cv == 45); | |||||
expectEquals (cv.get(), 45); | |||||
expect (t["testkey"] == var()); | |||||
} | |||||
beginTest ("reset value"); | |||||
{ | |||||
} | |||||
} | |||||
}; | |||||
static CachedValueTests cachedValueTests; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,312 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This class acts as a typed wrapper around a property inside a ValueTree. | |||||
A CachedValue provides an easy way to read and write a ValueTree property with | |||||
a chosen type. So for example a CachedValue<int> allows you to read or write the | |||||
property as an int, and a CachedValue<String> lets you work with it as a String. | |||||
It also allows efficient access to the value, by caching a copy of it in the | |||||
type that is being used. | |||||
You can give the CachedValue an optional UndoManager which it will use when writing | |||||
to the underlying ValueTree. | |||||
If the property inside the ValueTree is missing, the CachedValue will automatically | |||||
return an optional default value, which can be specified when initialising the CachedValue. | |||||
To create one, you can either use the constructor to attach the CachedValue to a | |||||
ValueTree, or can create an uninitialised CachedValue with its default constructor and | |||||
then attach it later with the referTo() methods. | |||||
Common types like String, int, double which can be easily converted to a var should work | |||||
out-of-the-box, but if you want to use more complex custom types, you may need to implement | |||||
some template specialisations of VariantConverter which this class uses to convert between | |||||
the type and the ValueTree's internal var. | |||||
*/ | |||||
template <typename Type> | |||||
class CachedValue : private ValueTree::Listener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Default constructor. | |||||
Creates a default CachedValue not referring to any property. To initialise the | |||||
object, call one of the referTo() methods. | |||||
*/ | |||||
CachedValue(); | |||||
/** Constructor. | |||||
Creates a CachedValue referring to a Value property inside a ValueTree. | |||||
If you use this constructor, the fallback value will be a default-constructed | |||||
instance of Type. | |||||
@param tree The ValueTree containing the property | |||||
@param propertyID The identifier of the property | |||||
@param undoManager The UndoManager to use when writing to the property | |||||
*/ | |||||
CachedValue (ValueTree& tree, const Identifier& propertyID, | |||||
UndoManager* undoManager); | |||||
/** Constructor. | |||||
Creates a default Cached Value referring to a Value property inside a ValueTree, | |||||
and specifies a fallback value to use if the property does not exist. | |||||
@param tree The ValueTree containing the property | |||||
@param propertyID The identifier of the property | |||||
@param undoManager The UndoManager to use when writing to the property | |||||
@param defaultToUse The fallback default value to use. | |||||
*/ | |||||
CachedValue (ValueTree& tree, const Identifier& propertyID, | |||||
UndoManager* undoManager, const Type& defaultToUse); | |||||
//============================================================================== | |||||
/** Returns the current value of the property. If the property does not exist, | |||||
returns the fallback default value. | |||||
This is the same as calling get(). | |||||
*/ | |||||
operator Type() const noexcept { return cachedValue; } | |||||
/** Returns the current value of the property. If the property does not exist, | |||||
returns the fallback default value. | |||||
*/ | |||||
Type get() const noexcept { return cachedValue; } | |||||
/** Dereference operator. Provides direct access to the property. */ | |||||
Type& operator*() noexcept { return cachedValue; } | |||||
/** Dereference operator. Provides direct access to members of the property | |||||
if it is of object type. | |||||
*/ | |||||
Type* operator->() noexcept { return &cachedValue; } | |||||
/** Returns true if the current value of the property (or the fallback value) | |||||
is equal to other. | |||||
*/ | |||||
template <typename OtherType> | |||||
bool operator== (const OtherType& other) const { return cachedValue == other; } | |||||
/** Returns true if the current value of the property (or the fallback value) | |||||
is not equal to other. | |||||
*/ | |||||
template <typename OtherType> | |||||
bool operator!= (const OtherType& other) const { return cachedValue != other; } | |||||
//============================================================================== | |||||
/** Returns the current property as a Value object. */ | |||||
Value getPropertyAsValue(); | |||||
/** Returns true if the current property does not exist and the CachedValue is using | |||||
the fallback default value instead. | |||||
*/ | |||||
bool isUsingDefault() const; | |||||
/** Returns the current fallback default value. */ | |||||
Type getDefault() const { return defaultValue; } | |||||
//============================================================================== | |||||
/** Sets the property. This will actually modify the property in the referenced ValueTree. */ | |||||
CachedValue& operator= (const Type& newValue); | |||||
/** Sets the property. This will actually modify the property in the referenced ValueTree. */ | |||||
void setValue (const Type& newValue, UndoManager* undoManagerToUse); | |||||
/** Removes the property from the referenced ValueTree and makes the CachedValue | |||||
return the fallback default value instead. | |||||
*/ | |||||
void resetToDefault(); | |||||
/** Removes the property from the referenced ValueTree and makes the CachedValue | |||||
return the fallback default value instead. | |||||
*/ | |||||
void resetToDefault (UndoManager* undoManagerToUse); | |||||
/** Resets the fallback default value. */ | |||||
void setDefault (const Type& value) { defaultValue = value; } | |||||
//============================================================================== | |||||
/** Makes the CachedValue refer to the specified property inside the given ValueTree. */ | |||||
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um); | |||||
/** Makes the CachedValue refer to the specified property inside the given ValueTree, | |||||
and specifies a fallback value to use if the property does not exist. | |||||
*/ | |||||
void referTo (ValueTree& tree, const Identifier& property, UndoManager* um, const Type& defaultVal); | |||||
/** Force an update in case the referenced property has been changed from elsewhere. | |||||
Note: The CachedValue is a ValueTree::Listener and therefore will be informed of | |||||
changes of the referenced property anyway (and update itself). But this may happen | |||||
asynchronously. forceUpdateOfCachedValue() forces an update immediately. | |||||
*/ | |||||
void forceUpdateOfCachedValue(); | |||||
//============================================================================== | |||||
/** Returns a reference to the ValueTree containing the referenced property. */ | |||||
ValueTree& getValueTree() noexcept { return targetTree; } | |||||
/** Returns the property ID of the referenced property. */ | |||||
const Identifier& getPropertyID() const noexcept { return targetProperty; } | |||||
private: | |||||
//============================================================================== | |||||
ValueTree targetTree; | |||||
Identifier targetProperty; | |||||
UndoManager* undoManager; | |||||
Type defaultValue; | |||||
Type cachedValue; | |||||
//============================================================================== | |||||
void referToWithDefault (ValueTree&, const Identifier&, UndoManager*, const Type&); | |||||
Type getTypedValue() const; | |||||
void valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) override; | |||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override {} | |||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override {} | |||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override {} | |||||
void valueTreeParentChanged (ValueTree&) override {} | |||||
JUCE_DECLARE_NON_COPYABLE (CachedValue) | |||||
}; | |||||
//============================================================================== | |||||
template <typename Type> | |||||
inline CachedValue<Type>::CachedValue() : undoManager (nullptr) {} | |||||
template <typename Type> | |||||
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um) | |||||
: targetTree (v), targetProperty (i), undoManager (um), | |||||
defaultValue(), cachedValue (getTypedValue()) | |||||
{ | |||||
targetTree.addListener (this); | |||||
} | |||||
template <typename Type> | |||||
inline CachedValue<Type>::CachedValue (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultToUse) | |||||
: targetTree (v), targetProperty (i), undoManager (um), | |||||
defaultValue (defaultToUse), cachedValue (getTypedValue()) | |||||
{ | |||||
targetTree.addListener (this); | |||||
} | |||||
template <typename Type> | |||||
inline Value CachedValue<Type>::getPropertyAsValue() | |||||
{ | |||||
return targetTree.getPropertyAsValue (targetProperty, undoManager); | |||||
} | |||||
template <typename Type> | |||||
inline bool CachedValue<Type>::isUsingDefault() const | |||||
{ | |||||
return ! targetTree.hasProperty (targetProperty); | |||||
} | |||||
template <typename Type> | |||||
inline CachedValue<Type>& CachedValue<Type>::operator= (const Type& newValue) | |||||
{ | |||||
setValue (newValue, undoManager); | |||||
return *this; | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::setValue (const Type& newValue, UndoManager* undoManagerToUse) | |||||
{ | |||||
if (cachedValue != newValue || isUsingDefault()) | |||||
{ | |||||
cachedValue = newValue; | |||||
targetTree.setProperty (targetProperty, VariantConverter<Type>::toVar (newValue), undoManagerToUse); | |||||
} | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::resetToDefault() | |||||
{ | |||||
resetToDefault (undoManager); | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::resetToDefault (UndoManager* undoManagerToUse) | |||||
{ | |||||
targetTree.removeProperty (targetProperty, undoManagerToUse); | |||||
forceUpdateOfCachedValue(); | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um) | |||||
{ | |||||
referToWithDefault (v, i, um, Type()); | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::referTo (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) | |||||
{ | |||||
referToWithDefault (v, i, um, defaultVal); | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::forceUpdateOfCachedValue() | |||||
{ | |||||
cachedValue = getTypedValue(); | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::referToWithDefault (ValueTree& v, const Identifier& i, UndoManager* um, const Type& defaultVal) | |||||
{ | |||||
targetTree.removeListener (this); | |||||
targetTree = v; | |||||
targetProperty = i; | |||||
undoManager = um; | |||||
defaultValue = defaultVal; | |||||
cachedValue = getTypedValue(); | |||||
targetTree.addListener (this); | |||||
} | |||||
template <typename Type> | |||||
inline Type CachedValue<Type>::getTypedValue() const | |||||
{ | |||||
if (const var* property = targetTree.getPropertyPointer (targetProperty)) | |||||
return VariantConverter<Type>::fromVar (*property); | |||||
return defaultValue; | |||||
} | |||||
template <typename Type> | |||||
inline void CachedValue<Type>::valueTreePropertyChanged (ValueTree& changedTree, const Identifier& changedProperty) | |||||
{ | |||||
if (changedProperty == targetProperty && targetTree == changedTree) | |||||
forceUpdateOfCachedValue(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,242 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
Value::ValueSource::ValueSource() | |||||
{ | |||||
} | |||||
Value::ValueSource::~ValueSource() | |||||
{ | |||||
cancelPendingUpdate(); | |||||
} | |||||
void Value::ValueSource::handleAsyncUpdate() | |||||
{ | |||||
sendChangeMessage (true); | |||||
} | |||||
void Value::ValueSource::sendChangeMessage (const bool synchronous) | |||||
{ | |||||
const int numListeners = valuesWithListeners.size(); | |||||
if (numListeners > 0) | |||||
{ | |||||
if (synchronous) | |||||
{ | |||||
const ReferenceCountedObjectPtr<ValueSource> localRef (this); | |||||
cancelPendingUpdate(); | |||||
for (int i = numListeners; --i >= 0;) | |||||
if (Value* const v = valuesWithListeners[i]) | |||||
v->callListeners(); | |||||
} | |||||
else | |||||
{ | |||||
triggerAsyncUpdate(); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class SimpleValueSource : public Value::ValueSource | |||||
{ | |||||
public: | |||||
SimpleValueSource() | |||||
{ | |||||
} | |||||
SimpleValueSource (const var& initialValue) | |||||
: value (initialValue) | |||||
{ | |||||
} | |||||
var getValue() const override | |||||
{ | |||||
return value; | |||||
} | |||||
void setValue (const var& newValue) override | |||||
{ | |||||
if (! newValue.equalsWithSameType (value)) | |||||
{ | |||||
value = newValue; | |||||
sendChangeMessage (false); | |||||
} | |||||
} | |||||
private: | |||||
var value; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SimpleValueSource) | |||||
}; | |||||
//============================================================================== | |||||
Value::Value() : value (new SimpleValueSource()) | |||||
{ | |||||
} | |||||
Value::Value (ValueSource* const v) : value (v) | |||||
{ | |||||
jassert (v != nullptr); | |||||
} | |||||
Value::Value (const var& initialValue) : value (new SimpleValueSource (initialValue)) | |||||
{ | |||||
} | |||||
Value::Value (const Value& other) : value (other.value) | |||||
{ | |||||
} | |||||
Value::Value (Value&& other) noexcept | |||||
{ | |||||
// moving a Value with listeners will lose those listeners, which | |||||
// probably isn't what you wanted to happen! | |||||
jassert (other.listeners.size() == 0); | |||||
other.removeFromListenerList(); | |||||
value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value); | |||||
} | |||||
Value& Value::operator= (Value&& other) noexcept | |||||
{ | |||||
// moving a Value with listeners will lose those listeners, which | |||||
// probably isn't what you wanted to happen! | |||||
jassert (other.listeners.size() == 0); | |||||
other.removeFromListenerList(); | |||||
value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value); | |||||
return *this; | |||||
} | |||||
Value::~Value() | |||||
{ | |||||
removeFromListenerList(); | |||||
} | |||||
void Value::removeFromListenerList() | |||||
{ | |||||
if (listeners.size() > 0 && value != nullptr) // may be nullptr after a move operation | |||||
value->valuesWithListeners.removeValue (this); | |||||
} | |||||
//============================================================================== | |||||
var Value::getValue() const | |||||
{ | |||||
return value->getValue(); | |||||
} | |||||
Value::operator var() const | |||||
{ | |||||
return value->getValue(); | |||||
} | |||||
void Value::setValue (const var& newValue) | |||||
{ | |||||
value->setValue (newValue); | |||||
} | |||||
String Value::toString() const | |||||
{ | |||||
return value->getValue().toString(); | |||||
} | |||||
Value& Value::operator= (const var& newValue) | |||||
{ | |||||
value->setValue (newValue); | |||||
return *this; | |||||
} | |||||
void Value::referTo (const Value& valueToReferTo) | |||||
{ | |||||
if (valueToReferTo.value != value) | |||||
{ | |||||
if (listeners.size() > 0) | |||||
{ | |||||
value->valuesWithListeners.removeValue (this); | |||||
valueToReferTo.value->valuesWithListeners.add (this); | |||||
} | |||||
value = valueToReferTo.value; | |||||
callListeners(); | |||||
} | |||||
} | |||||
bool Value::refersToSameSourceAs (const Value& other) const | |||||
{ | |||||
return value == other.value; | |||||
} | |||||
bool Value::operator== (const Value& other) const | |||||
{ | |||||
return value == other.value || value->getValue() == other.getValue(); | |||||
} | |||||
bool Value::operator!= (const Value& other) const | |||||
{ | |||||
return value != other.value && value->getValue() != other.getValue(); | |||||
} | |||||
//============================================================================== | |||||
void Value::addListener (ValueListener* const listener) | |||||
{ | |||||
if (listener != nullptr) | |||||
{ | |||||
if (listeners.size() == 0) | |||||
value->valuesWithListeners.add (this); | |||||
listeners.add (listener); | |||||
} | |||||
} | |||||
void Value::removeListener (ValueListener* const listener) | |||||
{ | |||||
listeners.remove (listener); | |||||
if (listeners.size() == 0) | |||||
value->valuesWithListeners.removeValue (this); | |||||
} | |||||
void Value::callListeners() | |||||
{ | |||||
if (listeners.size() > 0) | |||||
{ | |||||
Value v (*this); // (create a copy in case this gets deleted by a callback) | |||||
listeners.call (&ValueListener::valueChanged, v); | |||||
} | |||||
} | |||||
OutputStream& JUCE_CALLTYPE operator<< (OutputStream& stream, const Value& value) | |||||
{ | |||||
return stream << value.toString(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,243 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a shared variant value. | |||||
A Value object contains a reference to a var object, and can get and set its value. | |||||
Listeners can be attached to be told when the value is changed. | |||||
The Value class is a wrapper around a shared, reference-counted underlying data | |||||
object - this means that multiple Value objects can all refer to the same piece of | |||||
data, allowing all of them to be notified when any of them changes it. | |||||
When you create a Value with its default constructor, it acts as a wrapper around a | |||||
simple var object, but by creating a Value that refers to a custom subclass of ValueSource, | |||||
you can map the Value onto any kind of underlying data. | |||||
Important note! The Value class is not thread-safe! If you're accessing one from | |||||
multiple threads, then you'll need to use your own synchronisation around any code | |||||
that accesses it. | |||||
*/ | |||||
class JUCE_API Value | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty Value, containing a void var. */ | |||||
Value(); | |||||
/** Creates a Value that refers to the same value as another one. | |||||
Note that this doesn't make a copy of the other value - both this and the other | |||||
Value will share the same underlying value, so that when either one alters it, both | |||||
will see it change. | |||||
*/ | |||||
Value (const Value& other); | |||||
/** Creates a Value that is set to the specified value. */ | |||||
explicit Value (const var& initialValue); | |||||
/** Move constructor */ | |||||
Value (Value&&) noexcept; | |||||
/** Destructor. */ | |||||
~Value(); | |||||
//============================================================================== | |||||
/** Returns the current value. */ | |||||
var getValue() const; | |||||
/** Returns the current value. */ | |||||
operator var() const; | |||||
/** Returns the value as a string. | |||||
This is a shortcut for "myValue.getValue().toString()". | |||||
*/ | |||||
String toString() const; | |||||
/** Sets the current value. | |||||
You can also use operator= to set the value. | |||||
If there are any listeners registered, they will be notified of the | |||||
change asynchronously. | |||||
*/ | |||||
void setValue (const var& newValue); | |||||
/** Sets the current value. | |||||
This is the same as calling setValue(). | |||||
If there are any listeners registered, they will be notified of the | |||||
change asynchronously. | |||||
*/ | |||||
Value& operator= (const var& newValue); | |||||
/** Move assignment operator */ | |||||
Value& operator= (Value&&) noexcept; | |||||
/** Makes this object refer to the same underlying ValueSource as another one. | |||||
Once this object has been connected to another one, changing either one | |||||
will update the other. | |||||
Existing listeners will still be registered after you call this method, and | |||||
they'll continue to receive messages when the new value changes. | |||||
*/ | |||||
void referTo (const Value& valueToReferTo); | |||||
/** Returns true if this value and the other one are references to the same value. | |||||
*/ | |||||
bool refersToSameSourceAs (const Value& other) const; | |||||
/** Compares two values. | |||||
This is a compare-by-value comparison, so is effectively the same as | |||||
saying (this->getValue() == other.getValue()). | |||||
*/ | |||||
bool operator== (const Value& other) const; | |||||
/** Compares two values. | |||||
This is a compare-by-value comparison, so is effectively the same as | |||||
saying (this->getValue() != other.getValue()). | |||||
*/ | |||||
bool operator!= (const Value& other) const; | |||||
//============================================================================== | |||||
/** Receives callbacks when a Value object changes. | |||||
@see Value::addListener | |||||
*/ | |||||
class JUCE_API Listener | |||||
{ | |||||
public: | |||||
Listener() {} | |||||
virtual ~Listener() {} | |||||
/** Called when a Value object is changed. | |||||
Note that the Value object passed as a parameter may not be exactly the same | |||||
object that you registered the listener with - it might be a copy that refers | |||||
to the same underlying ValueSource. To find out, you can call Value::refersToSameSourceAs(). | |||||
*/ | |||||
virtual void valueChanged (Value& value) = 0; | |||||
}; | |||||
/** Adds a listener to receive callbacks when the value changes. | |||||
The listener is added to this specific Value object, and not to the shared | |||||
object that it refers to. When this object is deleted, all the listeners will | |||||
be lost, even if other references to the same Value still exist. So when you're | |||||
adding a listener, make sure that you add it to a Value instance that will last | |||||
for as long as you need the listener. In general, you'd never want to add a listener | |||||
to a local stack-based Value, but more likely to one that's a member variable. | |||||
@see removeListener | |||||
*/ | |||||
void addListener (Listener* listener); | |||||
/** Removes a listener that was previously added with addListener(). */ | |||||
void removeListener (Listener* listener); | |||||
//============================================================================== | |||||
/** | |||||
Used internally by the Value class as the base class for its shared value objects. | |||||
The Value class is essentially a reference-counted pointer to a shared instance | |||||
of a ValueSource object. If you're feeling adventurous, you can create your own custom | |||||
ValueSource classes to allow Value objects to represent your own custom data items. | |||||
*/ | |||||
class JUCE_API ValueSource : public ReferenceCountedObject, | |||||
private AsyncUpdater | |||||
{ | |||||
public: | |||||
ValueSource(); | |||||
virtual ~ValueSource(); | |||||
/** Returns the current value of this object. */ | |||||
virtual var getValue() const = 0; | |||||
/** Changes the current value. | |||||
This must also trigger a change message if the value actually changes. | |||||
*/ | |||||
virtual void setValue (const var& newValue) = 0; | |||||
/** Delivers a change message to all the listeners that are registered with | |||||
this value. | |||||
If dispatchSynchronously is true, the method will call all the listeners | |||||
before returning; otherwise it'll dispatch a message and make the call later. | |||||
*/ | |||||
void sendChangeMessage (bool dispatchSynchronously); | |||||
protected: | |||||
//============================================================================== | |||||
friend class Value; | |||||
SortedSet<Value*> valuesWithListeners; | |||||
private: | |||||
void handleAsyncUpdate() override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource) | |||||
}; | |||||
//============================================================================== | |||||
/** Creates a Value object that uses this valueSource object as its underlying data. */ | |||||
explicit Value (ValueSource* valueSource); | |||||
/** Returns the ValueSource that this value is referring to. */ | |||||
ValueSource& getValueSource() noexcept { return *value; } | |||||
private: | |||||
//============================================================================== | |||||
friend class ValueSource; | |||||
ReferenceCountedObjectPtr<ValueSource> value; | |||||
ListenerList<Listener> listeners; | |||||
void callListeners(); | |||||
void removeFromListenerList(); | |||||
// This is disallowed to avoid confusion about whether it should | |||||
// do a by-value or by-reference copy. | |||||
Value& operator= (const Value&) JUCE_DELETED_FUNCTION; | |||||
// This declaration prevents accidental construction from an integer of 0, | |||||
// which is possible in some compilers via an implicit cast to a pointer. | |||||
explicit Value (void*) JUCE_DELETED_FUNCTION; | |||||
}; | |||||
/** Writes a Value to an OutputStream as a UTF8 string. */ | |||||
OutputStream& JUCE_CALLTYPE operator<< (OutputStream&, const Value&); | |||||
/** This typedef is just for compatibility with old code - newer code should use the Value::Listener class directly. */ | |||||
typedef Value::Listener ValueListener; | |||||
} // namespace juce |
@@ -0,0 +1,578 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A powerful tree structure that can be used to hold free-form data, and which can | |||||
handle its own undo and redo behaviour. | |||||
A ValueTree contains a list of named properties as var objects, and also holds | |||||
any number of sub-trees. | |||||
Create ValueTree objects on the stack, and don't be afraid to copy them around, as | |||||
they're simply a lightweight reference to a shared data container. Creating a copy | |||||
of another ValueTree simply creates a new reference to the same underlying object - to | |||||
make a separate, deep copy of a tree you should explicitly call createCopy(). | |||||
Each ValueTree has a type name, in much the same way as an XmlElement has a tag name, | |||||
and much of the structure of a ValueTree is similar to an XmlElement tree. | |||||
You can convert a ValueTree to and from an XmlElement, and as long as the XML doesn't | |||||
contain text elements, the conversion works well and makes a good serialisation | |||||
format. They can also be serialised to a binary format, which is very fast and compact. | |||||
All the methods that change data take an optional UndoManager, which will be used | |||||
to track any changes to the object. For this to work, you have to be careful to | |||||
consistently always use the same UndoManager for all operations to any node inside | |||||
the tree. | |||||
A ValueTree can only be a child of one parent at a time, so if you're moving one from | |||||
one tree to another, be careful to always remove it first, before adding it. This | |||||
could also mess up your undo/redo chain, so be wary! In a debug build you should hit | |||||
assertions if you try to do anything dangerous, but there are still plenty of ways it | |||||
could go wrong. | |||||
Note that although the children in a tree have a fixed order, the properties are not | |||||
guaranteed to be stored in any particular order, so don't expect that a property's index | |||||
will correspond to the order in which the property was added, or that it will remain | |||||
constant when other properties are added or removed. | |||||
Listeners can be added to a ValueTree to be told when properies change and when | |||||
nodes are added or removed. | |||||
@see var, XmlElement | |||||
*/ | |||||
class JUCE_API ValueTree | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty, invalid ValueTree. | |||||
A ValueTree that is created with this constructor can't actually be used for anything, | |||||
it's just a default 'null' ValueTree that can be returned to indicate some sort of failure. | |||||
To create a real one, use the constructor that takes a string. | |||||
*/ | |||||
ValueTree() noexcept; | |||||
/** Creates an empty ValueTree with the given type name. | |||||
Like an XmlElement, each ValueTree node has a type, which you can access with | |||||
getType() and hasType(). | |||||
*/ | |||||
explicit ValueTree (const Identifier& type); | |||||
/** Creates a reference to another ValueTree. */ | |||||
ValueTree (const ValueTree&) noexcept; | |||||
/** Makes this object reference another node. */ | |||||
ValueTree& operator= (const ValueTree&); | |||||
/** Move constructor */ | |||||
ValueTree (ValueTree&&) noexcept; | |||||
/** Destructor. */ | |||||
~ValueTree(); | |||||
/** Returns true if both this and the other tree node refer to the same underlying structure. | |||||
Note that this isn't a value comparison - two independently-created trees which | |||||
contain identical data are not considered equal. | |||||
*/ | |||||
bool operator== (const ValueTree&) const noexcept; | |||||
/** Returns true if this and the other node refer to different underlying structures. | |||||
Note that this isn't a value comparison - two independently-created trees which | |||||
contain identical data are not considered equal. | |||||
*/ | |||||
bool operator!= (const ValueTree&) const noexcept; | |||||
/** Performs a deep comparison between the properties and children of two trees. | |||||
If all the properties and children of the two trees are the same (recursively), this | |||||
returns true. | |||||
The normal operator==() only checks whether two trees refer to the same shared data | |||||
structure, so use this method if you need to do a proper value comparison. | |||||
*/ | |||||
bool isEquivalentTo (const ValueTree&) const; | |||||
//============================================================================== | |||||
/** Returns true if this node refers to some valid data. | |||||
It's hard to create an invalid node, but you might get one returned, e.g. by an out-of-range | |||||
call to getChild(). | |||||
*/ | |||||
bool isValid() const noexcept { return object != nullptr; } | |||||
/** Returns a deep copy of this tree and all its sub-nodes. */ | |||||
ValueTree createCopy() const; | |||||
//============================================================================== | |||||
/** Returns the type of this node. | |||||
The type is specified when the ValueTree is created. | |||||
@see hasType | |||||
*/ | |||||
Identifier getType() const noexcept; | |||||
/** Returns true if the node has this type. | |||||
The comparison is case-sensitive. | |||||
*/ | |||||
bool hasType (const Identifier& typeName) const noexcept; | |||||
//============================================================================== | |||||
/** Returns the value of a named property. | |||||
If no such property has been set, this will return a void variant. | |||||
You can also use operator[] to get a property. | |||||
@see var, setProperty, getPropertyPointer, hasProperty | |||||
*/ | |||||
const var& getProperty (const Identifier& name) const noexcept; | |||||
/** Returns the value of a named property, or the value of defaultReturnValue | |||||
if the property doesn't exist. | |||||
You can also use operator[] and getProperty to get a property. | |||||
@see var, getProperty, getPropertyPointer, setProperty, hasProperty | |||||
*/ | |||||
var getProperty (const Identifier& name, const var& defaultReturnValue) const; | |||||
/** Returns a pointer to the value of a named property, or nullptr if the property | |||||
doesn't exist. | |||||
@see var, getProperty, setProperty, hasProperty | |||||
*/ | |||||
const var* getPropertyPointer (const Identifier& name) const noexcept; | |||||
/** Returns the value of a named property. | |||||
If no such property has been set, this will return a void variant. This is the same as | |||||
calling getProperty(). | |||||
@see getProperty | |||||
*/ | |||||
const var& operator[] (const Identifier& name) const noexcept; | |||||
/** Changes a named property of the node. | |||||
The name identifier must not be an empty string. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
@see var, getProperty, removeProperty | |||||
@returns a reference to the value tree, so that you can daisy-chain calls to this method. | |||||
*/ | |||||
ValueTree& setProperty (const Identifier& name, const var& newValue, UndoManager* undoManager); | |||||
/** Returns true if the node contains a named property. */ | |||||
bool hasProperty (const Identifier& name) const noexcept; | |||||
/** Removes a property from the node. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void removeProperty (const Identifier& name, UndoManager* undoManager); | |||||
/** Removes all properties from the node. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void removeAllProperties (UndoManager* undoManager); | |||||
/** Returns the total number of properties that the node contains. | |||||
@see getProperty. | |||||
*/ | |||||
int getNumProperties() const noexcept; | |||||
/** Returns the identifier of the property with a given index. | |||||
Note that properties are not guaranteed to be stored in any particular order, so don't | |||||
expect that the index will correspond to the order in which the property was added, or | |||||
that it will remain constant when other properties are added or removed. | |||||
@see getNumProperties | |||||
*/ | |||||
Identifier getPropertyName (int index) const noexcept; | |||||
/** Returns a Value object that can be used to control and respond to one of the tree's properties. | |||||
The Value object will maintain a reference to this tree, and will use the undo manager when | |||||
it needs to change the value. Attaching a Value::Listener to the value object will provide | |||||
callbacks whenever the property changes. | |||||
*/ | |||||
Value getPropertyAsValue (const Identifier& name, UndoManager* undoManager); | |||||
/** Overwrites all the properties in this tree with the properties of the source tree. | |||||
Any properties that already exist will be updated; and new ones will be added, and | |||||
any that are not present in the source tree will be removed. | |||||
*/ | |||||
void copyPropertiesFrom (const ValueTree& source, UndoManager* undoManager); | |||||
//============================================================================== | |||||
/** Returns the number of child nodes belonging to this one. | |||||
@see getChild | |||||
*/ | |||||
int getNumChildren() const noexcept; | |||||
/** Returns one of this node's child nodes. | |||||
If the index is out of range, it'll return an invalid node. (See isValid() to find out | |||||
whether a node is valid). | |||||
*/ | |||||
ValueTree getChild (int index) const; | |||||
/** Returns the first child node with the specified type name. | |||||
If no such node is found, it'll return an invalid node. (See isValid() to find out | |||||
whether a node is valid). | |||||
@see getOrCreateChildWithName | |||||
*/ | |||||
ValueTree getChildWithName (const Identifier& type) const; | |||||
/** Returns the first child node with the specified type name, creating and adding | |||||
a child with this name if there wasn't already one there. | |||||
The only time this will return an invalid object is when the object that you're calling | |||||
the method on is itself invalid. | |||||
@see getChildWithName | |||||
*/ | |||||
ValueTree getOrCreateChildWithName (const Identifier& type, UndoManager* undoManager); | |||||
/** Looks for the first child node that has the specified property value. | |||||
This will scan the child nodes in order, until it finds one that has property that matches | |||||
the specified value. | |||||
If no such node is found, it'll return an invalid node. (See isValid() to find out | |||||
whether a node is valid). | |||||
*/ | |||||
ValueTree getChildWithProperty (const Identifier& propertyName, const var& propertyValue) const; | |||||
/** Adds a child to this node. | |||||
Make sure that the child is removed from any former parent node before calling this, or | |||||
you'll hit an assertion. | |||||
If the index is < 0 or greater than the current number of child nodes, the new node will | |||||
be added at the end of the list. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void addChild (const ValueTree& child, int index, UndoManager* undoManager); | |||||
/** Removes the specified child from this node's child-list. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void removeChild (const ValueTree& child, UndoManager* undoManager); | |||||
/** Removes a child from this node's child-list. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void removeChild (int childIndex, UndoManager* undoManager); | |||||
/** Removes all child-nodes from this node. | |||||
If the undoManager parameter is non-null, its UndoManager::perform() method will be used, | |||||
so that this change can be undone. | |||||
*/ | |||||
void removeAllChildren (UndoManager* undoManager); | |||||
/** Moves one of the children to a different index. | |||||
This will move the child to a specified index, shuffling along any intervening | |||||
items as required. So for example, if you have a list of { 0, 1, 2, 3, 4, 5 }, then | |||||
calling move (2, 4) would result in { 0, 1, 3, 4, 2, 5 }. | |||||
@param currentIndex the index of the item to be moved. If this isn't a | |||||
valid index, then nothing will be done | |||||
@param newIndex the index at which you'd like this item to end up. If this | |||||
is less than zero, the value will be moved to the end | |||||
of the list | |||||
@param undoManager the optional UndoManager to use to store this transaction | |||||
*/ | |||||
void moveChild (int currentIndex, int newIndex, UndoManager* undoManager); | |||||
/** Returns true if this node is anywhere below the specified parent node. | |||||
This returns true if the node is a child-of-a-child, as well as a direct child. | |||||
*/ | |||||
bool isAChildOf (const ValueTree& possibleParent) const noexcept; | |||||
/** Returns the index of a child item in this parent. | |||||
If the child isn't found, this returns -1. | |||||
*/ | |||||
int indexOf (const ValueTree& child) const noexcept; | |||||
/** Returns the parent node that contains this one. | |||||
If the node has no parent, this will return an invalid node. (See isValid() to find out | |||||
whether a node is valid). | |||||
*/ | |||||
ValueTree getParent() const noexcept; | |||||
/** Recusrively finds the highest-level parent node that contains this one. | |||||
If the node has no parent, this will return itself. | |||||
*/ | |||||
ValueTree getRoot() const noexcept; | |||||
/** Returns one of this node's siblings in its parent's child list. | |||||
The delta specifies how far to move through the list, so a value of 1 would return the node | |||||
that follows this one, -1 would return the node before it, 0 will return this node itself, etc. | |||||
If the requested position is beyond the range of available nodes, this will return an empty ValueTree(). | |||||
*/ | |||||
ValueTree getSibling (int delta) const noexcept; | |||||
//============================================================================== | |||||
struct Iterator | |||||
{ | |||||
Iterator (const ValueTree&, bool isEnd) noexcept; | |||||
Iterator& operator++() noexcept; | |||||
bool operator!= (const Iterator&) const noexcept; | |||||
ValueTree operator*() const; | |||||
private: | |||||
void* internal; | |||||
}; | |||||
/** Returns a start iterator for the children in this tree. */ | |||||
Iterator begin() const noexcept; | |||||
/** Returns an end iterator for the children in this tree. */ | |||||
Iterator end() const noexcept; | |||||
//============================================================================== | |||||
/** Creates an XmlElement that holds a complete image of this node and all its children. | |||||
If this node is invalid, this may return nullptr. Otherwise, the XML that is produced can | |||||
be used to recreate a similar node by calling fromXml(). | |||||
The caller must delete the object that is returned. | |||||
@see fromXml | |||||
*/ | |||||
XmlElement* createXml() const; | |||||
/** Tries to recreate a node from its XML representation. | |||||
This isn't designed to cope with random XML data - for a sensible result, it should only | |||||
be fed XML that was created by the createXml() method. | |||||
*/ | |||||
static ValueTree fromXml (const XmlElement& xml); | |||||
/** This returns a string containing an XML representation of the tree. | |||||
This is quite handy for debugging purposes, as it provides a quick way to view a tree. | |||||
*/ | |||||
String toXmlString() const; | |||||
//============================================================================== | |||||
/** Stores this tree (and all its children) in a binary format. | |||||
Once written, the data can be read back with readFromStream(). | |||||
It's much faster to load/save your tree in binary form than as XML, but | |||||
obviously isn't human-readable. | |||||
*/ | |||||
void writeToStream (OutputStream& output) const; | |||||
/** Reloads a tree from a stream that was written with writeToStream(). */ | |||||
static ValueTree readFromStream (InputStream& input); | |||||
/** Reloads a tree from a data block that was written with writeToStream(). */ | |||||
static ValueTree readFromData (const void* data, size_t numBytes); | |||||
/** Reloads a tree from a data block that was written with writeToStream() and | |||||
then zipped using GZIPCompressorOutputStream. | |||||
*/ | |||||
static ValueTree readFromGZIPData (const void* data, size_t numBytes); | |||||
//============================================================================== | |||||
/** Listener class for events that happen to a ValueTree. | |||||
To get events from a ValueTree, make your class implement this interface, and use | |||||
ValueTree::addListener() and ValueTree::removeListener() to register it. | |||||
*/ | |||||
class JUCE_API Listener | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~Listener() {} | |||||
/** This method is called when a property of this node (or of one of its sub-nodes) has | |||||
changed. | |||||
The tree parameter indicates which tree has had its property changed, and the property | |||||
parameter indicates the property. | |||||
Note that when you register a listener to a tree, it will receive this callback for | |||||
property changes in that tree, and also for any of its children, (recursively, at any depth). | |||||
If your tree has sub-trees but you only want to know about changes to the top level tree, | |||||
simply check the tree parameter in this callback to make sure it's the tree you're interested in. | |||||
*/ | |||||
virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, | |||||
const Identifier& property) = 0; | |||||
/** This method is called when a child sub-tree is added. | |||||
Note that when you register a listener to a tree, it will receive this callback for | |||||
child changes in both that tree and any of its children, (recursively, at any depth). | |||||
If your tree has sub-trees but you only want to know about changes to the top level tree, | |||||
just check the parentTree parameter to make sure it's the one that you're interested in. | |||||
*/ | |||||
virtual void valueTreeChildAdded (ValueTree& parentTree, | |||||
ValueTree& childWhichHasBeenAdded) = 0; | |||||
/** This method is called when a child sub-tree is removed. | |||||
Note that when you register a listener to a tree, it will receive this callback for | |||||
child changes in both that tree and any of its children, (recursively, at any depth). | |||||
If your tree has sub-trees but you only want to know about changes to the top level tree, | |||||
just check the parentTree parameter to make sure it's the one that you're interested in. | |||||
*/ | |||||
virtual void valueTreeChildRemoved (ValueTree& parentTree, | |||||
ValueTree& childWhichHasBeenRemoved, | |||||
int indexFromWhichChildWasRemoved) = 0; | |||||
/** This method is called when a tree's children have been re-shuffled. | |||||
Note that when you register a listener to a tree, it will receive this callback for | |||||
child changes in both that tree and any of its children, (recursively, at any depth). | |||||
If your tree has sub-trees but you only want to know about changes to the top level tree, | |||||
just check the parameter to make sure it's the tree that you're interested in. | |||||
*/ | |||||
virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved, | |||||
int oldIndex, int newIndex) = 0; | |||||
/** This method is called when a tree has been added or removed from a parent node. | |||||
This callback happens when the tree to which the listener was registered is added or | |||||
removed from a parent. Unlike the other callbacks, it applies only to the tree to which | |||||
the listener is registered, and not to any of its children. | |||||
*/ | |||||
virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) = 0; | |||||
/** This method is called when a tree is made to point to a different internal shared object. | |||||
When operator= is used to make a ValueTree refer to a different object, this callback | |||||
will be made. | |||||
*/ | |||||
virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged); | |||||
}; | |||||
/** Adds a listener to receive callbacks when this node is changed. | |||||
The listener is added to this specific ValueTree object, and not to the shared | |||||
object that it refers to. When this object is deleted, all the listeners will | |||||
be lost, even if other references to the same ValueTree still exist. And if you | |||||
use the operator= to make this refer to a different ValueTree, any listeners will | |||||
begin listening to changes to the new tree instead of the old one. | |||||
When you're adding a listener, make sure that you add it to a ValueTree instance that | |||||
will last for as long as you need the listener. In general, you'd never want to add a | |||||
listener to a local stack-based ValueTree, and would usually add one to a member variable. | |||||
@see removeListener | |||||
*/ | |||||
void addListener (Listener* listener); | |||||
/** Removes a listener that was previously added with addListener(). */ | |||||
void removeListener (Listener* listener); | |||||
/** Changes a named property of the node, but will not notify a specified listener of the change. | |||||
@see setProperty | |||||
*/ | |||||
ValueTree& setPropertyExcludingListener (Listener* listenerToExclude, | |||||
const Identifier& name, const var& newValue, | |||||
UndoManager* undoManager); | |||||
/** Causes a property-change callback to be triggered for the specified property, | |||||
calling any listeners that are registered. | |||||
*/ | |||||
void sendPropertyChangeMessage (const Identifier& property); | |||||
//============================================================================== | |||||
/** This method uses a comparator object to sort the tree's children into order. | |||||
The object provided must have a method of the form: | |||||
@code | |||||
int compareElements (const ValueTree& first, const ValueTree& second); | |||||
@endcode | |||||
..and this method must return: | |||||
- a value of < 0 if the first comes before the second | |||||
- a value of 0 if the two objects are equivalent | |||||
- a value of > 0 if the second comes before the first | |||||
To improve performance, the compareElements() method can be declared as static or const. | |||||
@param comparator the comparator to use for comparing elements. | |||||
@param undoManager optional UndoManager for storing the changes | |||||
@param retainOrderOfEquivalentItems if this is true, then items which the comparator says are | |||||
equivalent will be kept in the order in which they currently appear in the array. | |||||
This is slower to perform, but may be important in some cases. If it's false, a | |||||
faster algorithm is used, but equivalent elements may be rearranged. | |||||
*/ | |||||
template <typename ElementComparator> | |||||
void sort (ElementComparator& comparator, UndoManager* undoManager, bool retainOrderOfEquivalentItems) | |||||
{ | |||||
if (object != nullptr) | |||||
{ | |||||
OwnedArray<ValueTree> sortedList; | |||||
createListOfChildren (sortedList); | |||||
ComparatorAdapter<ElementComparator> adapter (comparator); | |||||
sortedList.sort (adapter, retainOrderOfEquivalentItems); | |||||
reorderChildren (sortedList, undoManager); | |||||
} | |||||
} | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
/** An invalid ValueTree that can be used if you need to return one as an error condition, etc. | |||||
This invalid object is equivalent to ValueTree created with its default constructor, but | |||||
you should always prefer to avoid it and use ValueTree() or {} instead. | |||||
*/ | |||||
static const ValueTree invalid; | |||||
#endif | |||||
/** Returns the total number of references to the shared underlying data structure that this | |||||
ValueTree is using. | |||||
*/ | |||||
int getReferenceCount() const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_PUBLIC_IN_DLL_BUILD (class SharedObject) | |||||
friend class SharedObject; | |||||
ReferenceCountedObjectPtr<SharedObject> object; | |||||
ListenerList<Listener> listeners; | |||||
template <typename ElementComparator> | |||||
struct ComparatorAdapter | |||||
{ | |||||
ComparatorAdapter (ElementComparator& comp) noexcept : comparator (comp) {} | |||||
int compareElements (const ValueTree* const first, const ValueTree* const second) | |||||
{ | |||||
return comparator.compareElements (*first, *second); | |||||
} | |||||
private: | |||||
ElementComparator& comparator; | |||||
JUCE_DECLARE_NON_COPYABLE (ComparatorAdapter) | |||||
}; | |||||
void createListOfChildren (OwnedArray<ValueTree>&) const; | |||||
void reorderChildren (const OwnedArray<ValueTree>&, UndoManager*); | |||||
explicit ValueTree (SharedObject*) noexcept; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,242 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace ValueTreeSynchroniserHelpers | |||||
{ | |||||
enum ChangeType | |||||
{ | |||||
propertyChanged = 1, | |||||
fullSync = 2, | |||||
childAdded = 3, | |||||
childRemoved = 4, | |||||
childMoved = 5, | |||||
propertyRemoved = 6 | |||||
}; | |||||
static void getValueTreePath (ValueTree v, const ValueTree& topLevelTree, Array<int>& path) | |||||
{ | |||||
while (v != topLevelTree) | |||||
{ | |||||
ValueTree parent (v.getParent()); | |||||
if (! parent.isValid()) | |||||
break; | |||||
path.add (parent.indexOf (v)); | |||||
v = parent; | |||||
} | |||||
} | |||||
static void writeHeader (MemoryOutputStream& stream, ChangeType type) | |||||
{ | |||||
stream.writeByte ((char) type); | |||||
} | |||||
static void writeHeader (ValueTreeSynchroniser& target, MemoryOutputStream& stream, | |||||
ChangeType type, ValueTree v) | |||||
{ | |||||
writeHeader (stream, type); | |||||
Array<int> path; | |||||
getValueTreePath (v, target.getRoot(), path); | |||||
stream.writeCompressedInt (path.size()); | |||||
for (int i = path.size(); --i >= 0;) | |||||
stream.writeCompressedInt (path.getUnchecked(i)); | |||||
} | |||||
static ValueTree readSubTreeLocation (MemoryInputStream& input, ValueTree v) | |||||
{ | |||||
const int numLevels = input.readCompressedInt(); | |||||
if (! isPositiveAndBelow (numLevels, 65536)) // sanity-check | |||||
return {}; | |||||
for (int i = numLevels; --i >= 0;) | |||||
{ | |||||
const int index = input.readCompressedInt(); | |||||
if (! isPositiveAndBelow (index, v.getNumChildren())) | |||||
return {}; | |||||
v = v.getChild (index); | |||||
} | |||||
return v; | |||||
} | |||||
} | |||||
ValueTreeSynchroniser::ValueTreeSynchroniser (const ValueTree& tree) : valueTree (tree) | |||||
{ | |||||
valueTree.addListener (this); | |||||
} | |||||
ValueTreeSynchroniser::~ValueTreeSynchroniser() | |||||
{ | |||||
valueTree.removeListener (this); | |||||
} | |||||
void ValueTreeSynchroniser::sendFullSyncCallback() | |||||
{ | |||||
MemoryOutputStream m; | |||||
writeHeader (m, ValueTreeSynchroniserHelpers::fullSync); | |||||
valueTree.writeToStream (m); | |||||
stateChanged (m.getData(), m.getDataSize()); | |||||
} | |||||
void ValueTreeSynchroniser::valueTreePropertyChanged (ValueTree& vt, const Identifier& property) | |||||
{ | |||||
MemoryOutputStream m; | |||||
if (auto* value = vt.getPropertyPointer (property)) | |||||
{ | |||||
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyChanged, vt); | |||||
m.writeString (property.toString()); | |||||
value->writeToStream (m); | |||||
} | |||||
else | |||||
{ | |||||
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::propertyRemoved, vt); | |||||
m.writeString (property.toString()); | |||||
} | |||||
stateChanged (m.getData(), m.getDataSize()); | |||||
} | |||||
void ValueTreeSynchroniser::valueTreeChildAdded (ValueTree& parentTree, ValueTree& childTree) | |||||
{ | |||||
const int index = parentTree.indexOf (childTree); | |||||
jassert (index >= 0); | |||||
MemoryOutputStream m; | |||||
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childAdded, parentTree); | |||||
m.writeCompressedInt (index); | |||||
childTree.writeToStream (m); | |||||
stateChanged (m.getData(), m.getDataSize()); | |||||
} | |||||
void ValueTreeSynchroniser::valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int oldIndex) | |||||
{ | |||||
MemoryOutputStream m; | |||||
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childRemoved, parentTree); | |||||
m.writeCompressedInt (oldIndex); | |||||
stateChanged (m.getData(), m.getDataSize()); | |||||
} | |||||
void ValueTreeSynchroniser::valueTreeChildOrderChanged (ValueTree& parent, int oldIndex, int newIndex) | |||||
{ | |||||
MemoryOutputStream m; | |||||
ValueTreeSynchroniserHelpers::writeHeader (*this, m, ValueTreeSynchroniserHelpers::childMoved, parent); | |||||
m.writeCompressedInt (oldIndex); | |||||
m.writeCompressedInt (newIndex); | |||||
stateChanged (m.getData(), m.getDataSize()); | |||||
} | |||||
void ValueTreeSynchroniser::valueTreeParentChanged (ValueTree&) {} // (No action needed here) | |||||
bool ValueTreeSynchroniser::applyChange (ValueTree& root, const void* data, size_t dataSize, UndoManager* undoManager) | |||||
{ | |||||
MemoryInputStream input (data, dataSize, false); | |||||
const ValueTreeSynchroniserHelpers::ChangeType type = (ValueTreeSynchroniserHelpers::ChangeType) input.readByte(); | |||||
if (type == ValueTreeSynchroniserHelpers::fullSync) | |||||
{ | |||||
root = ValueTree::readFromStream (input); | |||||
return true; | |||||
} | |||||
ValueTree v (ValueTreeSynchroniserHelpers::readSubTreeLocation (input, root)); | |||||
if (! v.isValid()) | |||||
return false; | |||||
switch (type) | |||||
{ | |||||
case ValueTreeSynchroniserHelpers::propertyChanged: | |||||
{ | |||||
Identifier property (input.readString()); | |||||
v.setProperty (property, var::readFromStream (input), undoManager); | |||||
return true; | |||||
} | |||||
case ValueTreeSynchroniserHelpers::propertyRemoved: | |||||
{ | |||||
Identifier property (input.readString()); | |||||
v.removeProperty (property, undoManager); | |||||
return true; | |||||
} | |||||
case ValueTreeSynchroniserHelpers::childAdded: | |||||
{ | |||||
const int index = input.readCompressedInt(); | |||||
v.addChild (ValueTree::readFromStream (input), index, undoManager); | |||||
return true; | |||||
} | |||||
case ValueTreeSynchroniserHelpers::childRemoved: | |||||
{ | |||||
const int index = input.readCompressedInt(); | |||||
if (isPositiveAndBelow (index, v.getNumChildren())) | |||||
{ | |||||
v.removeChild (index, undoManager); | |||||
return true; | |||||
} | |||||
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync | |||||
break; | |||||
} | |||||
case ValueTreeSynchroniserHelpers::childMoved: | |||||
{ | |||||
const int oldIndex = input.readCompressedInt(); | |||||
const int newIndex = input.readCompressedInt(); | |||||
if (isPositiveAndBelow (oldIndex, v.getNumChildren()) | |||||
&& isPositiveAndBelow (newIndex, v.getNumChildren())) | |||||
{ | |||||
v.moveChild (oldIndex, newIndex, undoManager); | |||||
return true; | |||||
} | |||||
jassertfalse; // Either received some corrupt data, or the trees have drifted out of sync | |||||
break; | |||||
} | |||||
default: | |||||
jassertfalse; // Seem to have received some corrupt data? | |||||
break; | |||||
} | |||||
return false; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,97 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
This class can be used to watch for all changes to the state of a ValueTree, | |||||
and to convert them to a transmittable binary encoding. | |||||
The purpose of this class is to allow two or more ValueTrees to be remotely | |||||
synchronised by transmitting encoded changes over some kind of transport | |||||
mechanism. | |||||
To use it, you'll need to implement a subclass of ValueTreeSynchroniser | |||||
and implement the stateChanged() method to transmit the encoded change (maybe | |||||
via a network or other means) to a remote destination, where it can be | |||||
applied to a target tree. | |||||
*/ | |||||
class JUCE_API ValueTreeSynchroniser : private ValueTree::Listener | |||||
{ | |||||
public: | |||||
/** Creates a ValueTreeSynchroniser that watches the given tree. | |||||
After creating an instance of this class and somehow attaching it to | |||||
a target tree, you probably want to call sendFullSyncCallback() to | |||||
get them into a common starting state. | |||||
*/ | |||||
ValueTreeSynchroniser (const ValueTree& tree); | |||||
/** Destructor. */ | |||||
virtual ~ValueTreeSynchroniser(); | |||||
/** This callback happens when the ValueTree changes and the given state-change message | |||||
needs to be applied to any other trees that need to stay in sync with it. | |||||
The data is an opaque blob of binary that you should transmit to wherever your | |||||
target tree lives, and use applyChange() to apply this to the target tree. | |||||
*/ | |||||
virtual void stateChanged (const void* encodedChange, size_t encodedChangeSize) = 0; | |||||
/** Forces the sending of a full state message, which may be large, as it | |||||
encodes the entire ValueTree. | |||||
This will internally invoke stateChanged() with the encoded version of the state. | |||||
*/ | |||||
void sendFullSyncCallback(); | |||||
/** Applies an encoded change to the given destination tree. | |||||
When you implement a receiver for changes that were sent by the stateChanged() | |||||
message, this is the function that you'll need to call to apply them to the | |||||
target tree that you want to be synced. | |||||
*/ | |||||
static bool applyChange (ValueTree& target, | |||||
const void* encodedChangeData, size_t encodedChangeDataSize, | |||||
UndoManager* undoManager); | |||||
/** Returns the root ValueTree that is being observed. */ | |||||
const ValueTree& getRoot() noexcept { return valueTree; } | |||||
private: | |||||
ValueTree valueTree; | |||||
void valueTreePropertyChanged (ValueTree&, const Identifier&) override; | |||||
void valueTreeChildAdded (ValueTree&, ValueTree&) override; | |||||
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override; | |||||
void valueTreeChildOrderChanged (ValueTree&, int, int) override; | |||||
void valueTreeParentChanged (ValueTree&) override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueTreeSynchroniser) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,123 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_events # | |||||
# ------------------------ # | |||||
# Created by falkTX | |||||
# | |||||
CWD=../.. | |||||
MODULENAME=juce_events | |||||
include ../Makefile.mk | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_EVENTS_FLAGS) -I.. | |||||
ifeq ($(WIN32),true) | |||||
BUILD_CXX_FLAGS += -Wno-missing-field-initializers | |||||
endif | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
ifeq ($(MACOS),true) | |||||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||||
else | |||||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||||
OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
all: $(MODULEDIR)/$(MODULENAME).a | |||||
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||||
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||||
win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||||
win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
clean: | |||||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
-include $(OBJS:%.o=%.d) | |||||
-include $(OBJS_posix32:%.o=%.d) | |||||
-include $(OBJS_posix64:%.o=%.d) | |||||
-include $(OBJS_win32:%.o=%.d) | |||||
-include $(OBJS_win64:%.o=%.d) | |||||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class ActionBroadcaster::ActionMessage : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
ActionMessage (const ActionBroadcaster* ab, | |||||
const String& messageText, ActionListener* l) noexcept | |||||
: broadcaster (const_cast<ActionBroadcaster*> (ab)), | |||||
message (messageText), | |||||
listener (l) | |||||
{} | |||||
void messageCallback() override | |||||
{ | |||||
if (const ActionBroadcaster* const b = broadcaster) | |||||
if (b->actionListeners.contains (listener)) | |||||
listener->actionListenerCallback (message); | |||||
} | |||||
private: | |||||
WeakReference<ActionBroadcaster> broadcaster; | |||||
const String message; | |||||
ActionListener* const listener; | |||||
JUCE_DECLARE_NON_COPYABLE (ActionMessage) | |||||
}; | |||||
//============================================================================== | |||||
ActionBroadcaster::ActionBroadcaster() | |||||
{ | |||||
// are you trying to create this object before or after juce has been intialised?? | |||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||||
} | |||||
ActionBroadcaster::~ActionBroadcaster() | |||||
{ | |||||
// all event-based objects must be deleted BEFORE juce is shut down! | |||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||||
} | |||||
void ActionBroadcaster::addActionListener (ActionListener* const listener) | |||||
{ | |||||
const ScopedLock sl (actionListenerLock); | |||||
if (listener != nullptr) | |||||
actionListeners.add (listener); | |||||
} | |||||
void ActionBroadcaster::removeActionListener (ActionListener* const listener) | |||||
{ | |||||
const ScopedLock sl (actionListenerLock); | |||||
actionListeners.removeValue (listener); | |||||
} | |||||
void ActionBroadcaster::removeAllActionListeners() | |||||
{ | |||||
const ScopedLock sl (actionListenerLock); | |||||
actionListeners.clear(); | |||||
} | |||||
void ActionBroadcaster::sendActionMessage (const String& message) const | |||||
{ | |||||
const ScopedLock sl (actionListenerLock); | |||||
for (int i = actionListeners.size(); --i >= 0;) | |||||
(new ActionMessage (this, message, actionListeners.getUnchecked(i)))->post(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,77 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** Manages a list of ActionListeners, and can send them messages. | |||||
To quickly add methods to your class that can add/remove action | |||||
listeners and broadcast to them, you can derive from this. | |||||
@see ActionListener, ChangeListener | |||||
*/ | |||||
class JUCE_API ActionBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an ActionBroadcaster. */ | |||||
ActionBroadcaster(); | |||||
/** Destructor. */ | |||||
virtual ~ActionBroadcaster(); | |||||
//============================================================================== | |||||
/** Adds a listener to the list. | |||||
Trying to add a listener that's already on the list will have no effect. | |||||
*/ | |||||
void addActionListener (ActionListener* listener); | |||||
/** Removes a listener from the list. | |||||
If the listener isn't on the list, this won't have any effect. | |||||
*/ | |||||
void removeActionListener (ActionListener* listener); | |||||
/** Removes all listeners from the list. */ | |||||
void removeAllActionListeners(); | |||||
//============================================================================== | |||||
/** Broadcasts a message to all the registered listeners. | |||||
@see ActionListener::actionListenerCallback | |||||
*/ | |||||
void sendActionMessage (const String& message) const; | |||||
private: | |||||
//============================================================================== | |||||
class ActionMessage; | |||||
friend class ActionMessage; | |||||
SortedSet<ActionListener*> actionListeners; | |||||
CriticalSection actionListenerLock; | |||||
JUCE_DECLARE_WEAK_REFERENCEABLE (ActionBroadcaster) | |||||
JUCE_DECLARE_NON_COPYABLE (ActionBroadcaster) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,46 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Interface class for delivery of events that are sent by an ActionBroadcaster. | |||||
@see ActionBroadcaster, ChangeListener | |||||
*/ | |||||
class JUCE_API ActionListener | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~ActionListener() {} | |||||
/** Overridden by your subclass to receive the callback. | |||||
@param message the string that was specified when the event was triggered | |||||
by a call to ActionBroadcaster::sendActionMessage() | |||||
*/ | |||||
virtual void actionListenerCallback (const String& message) = 0; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,93 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class AsyncUpdater::AsyncUpdaterMessage : public CallbackMessage | |||||
{ | |||||
public: | |||||
AsyncUpdaterMessage (AsyncUpdater& au) : owner (au) {} | |||||
void messageCallback() override | |||||
{ | |||||
if (shouldDeliver.compareAndSetBool (0, 1)) | |||||
owner.handleAsyncUpdate(); | |||||
} | |||||
AsyncUpdater& owner; | |||||
Atomic<int> shouldDeliver; | |||||
JUCE_DECLARE_NON_COPYABLE (AsyncUpdaterMessage) | |||||
}; | |||||
//============================================================================== | |||||
AsyncUpdater::AsyncUpdater() | |||||
{ | |||||
activeMessage = new AsyncUpdaterMessage (*this); | |||||
} | |||||
AsyncUpdater::~AsyncUpdater() | |||||
{ | |||||
// You're deleting this object with a background thread while there's an update | |||||
// pending on the main event thread - that's pretty dodgy threading, as the callback could | |||||
// happen after this destructor has finished. You should either use a MessageManagerLock while | |||||
// deleting this object, or find some other way to avoid such a race condition. | |||||
jassert ((! isUpdatePending()) | |||||
|| MessageManager::getInstanceWithoutCreating() == nullptr | |||||
|| MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||||
activeMessage->shouldDeliver.set (0); | |||||
} | |||||
void AsyncUpdater::triggerAsyncUpdate() | |||||
{ | |||||
// If you're calling this before (or after) the MessageManager is | |||||
// running, then you're not going to get any callbacks! | |||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||||
if (activeMessage->shouldDeliver.compareAndSetBool (1, 0)) | |||||
if (! activeMessage->post()) | |||||
cancelPendingUpdate(); // if the message queue fails, this avoids getting | |||||
// trapped waiting for the message to arrive | |||||
} | |||||
void AsyncUpdater::cancelPendingUpdate() noexcept | |||||
{ | |||||
activeMessage->shouldDeliver.set (0); | |||||
} | |||||
void AsyncUpdater::handleUpdateNowIfNeeded() | |||||
{ | |||||
// This can only be called by the event thread. | |||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||||
if (activeMessage->shouldDeliver.exchange (0) != 0) | |||||
handleAsyncUpdate(); | |||||
} | |||||
bool AsyncUpdater::isUpdatePending() const noexcept | |||||
{ | |||||
return activeMessage->shouldDeliver.value != 0; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,108 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Has a callback method that is triggered asynchronously. | |||||
This object allows an asynchronous callback function to be triggered, for | |||||
tasks such as coalescing multiple updates into a single callback later on. | |||||
Basically, one or more calls to the triggerAsyncUpdate() will result in the | |||||
message thread calling handleAsyncUpdate() as soon as it can. | |||||
*/ | |||||
class JUCE_API AsyncUpdater | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an AsyncUpdater object. */ | |||||
AsyncUpdater(); | |||||
/** Destructor. | |||||
If there are any pending callbacks when the object is deleted, these are lost. | |||||
*/ | |||||
virtual ~AsyncUpdater(); | |||||
//============================================================================== | |||||
/** Causes the callback to be triggered at a later time. | |||||
This method returns immediately, after which a callback to the | |||||
handleAsyncUpdate() method will be made by the message thread as | |||||
soon as possible. | |||||
If an update callback is already pending but hasn't happened yet, calling | |||||
this method will have no effect. | |||||
It's thread-safe to call this method from any thread, BUT beware of calling | |||||
it from a real-time (e.g. audio) thread, because it involves posting a message | |||||
to the system queue, which means it may block (and in general will do on | |||||
most OSes). | |||||
*/ | |||||
void triggerAsyncUpdate(); | |||||
/** This will stop any pending updates from happening. | |||||
If called after triggerAsyncUpdate() and before the handleAsyncUpdate() | |||||
callback happens, this will cancel the handleAsyncUpdate() callback. | |||||
Note that this method simply cancels the next callback - if a callback is already | |||||
in progress on a different thread, this won't block until the callback finishes, so | |||||
there's no guarantee that the callback isn't still running when the method returns. | |||||
*/ | |||||
void cancelPendingUpdate() noexcept; | |||||
/** If an update has been triggered and is pending, this will invoke it | |||||
synchronously. | |||||
Use this as a kind of "flush" operation - if an update is pending, the | |||||
handleAsyncUpdate() method will be called immediately; if no update is | |||||
pending, then nothing will be done. | |||||
Because this may invoke the callback, this method must only be called on | |||||
the main event thread. | |||||
*/ | |||||
void handleUpdateNowIfNeeded(); | |||||
/** Returns true if there's an update callback in the pipeline. */ | |||||
bool isUpdatePending() const noexcept; | |||||
//============================================================================== | |||||
/** Called back to do whatever your class needs to do. | |||||
This method is called by the message thread at the next convenient time | |||||
after the triggerAsyncUpdate() method has been called. | |||||
*/ | |||||
virtual void handleAsyncUpdate() = 0; | |||||
private: | |||||
//============================================================================== | |||||
class AsyncUpdaterMessage; | |||||
friend class ReferenceCountedObjectPtr<AsyncUpdaterMessage>; | |||||
ReferenceCountedObjectPtr<AsyncUpdaterMessage> activeMessage; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncUpdater) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,99 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ChangeBroadcaster::ChangeBroadcaster() noexcept | |||||
{ | |||||
broadcastCallback.owner = this; | |||||
} | |||||
ChangeBroadcaster::~ChangeBroadcaster() | |||||
{ | |||||
} | |||||
void ChangeBroadcaster::addChangeListener (ChangeListener* const listener) | |||||
{ | |||||
// Listeners can only be safely added when the event thread is locked | |||||
// You can use a MessageManagerLock if you need to call this from another thread. | |||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||||
changeListeners.add (listener); | |||||
} | |||||
void ChangeBroadcaster::removeChangeListener (ChangeListener* const listener) | |||||
{ | |||||
// Listeners can only be safely removed when the event thread is locked | |||||
// You can use a MessageManagerLock if you need to call this from another thread. | |||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||||
changeListeners.remove (listener); | |||||
} | |||||
void ChangeBroadcaster::removeAllChangeListeners() | |||||
{ | |||||
// Listeners can only be safely removed when the event thread is locked | |||||
// You can use a MessageManagerLock if you need to call this from another thread. | |||||
jassert (MessageManager::getInstance()->currentThreadHasLockedMessageManager()); | |||||
changeListeners.clear(); | |||||
} | |||||
void ChangeBroadcaster::sendChangeMessage() | |||||
{ | |||||
if (changeListeners.size() > 0) | |||||
broadcastCallback.triggerAsyncUpdate(); | |||||
} | |||||
void ChangeBroadcaster::sendSynchronousChangeMessage() | |||||
{ | |||||
// This can only be called by the event thread. | |||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
broadcastCallback.cancelPendingUpdate(); | |||||
callListeners(); | |||||
} | |||||
void ChangeBroadcaster::dispatchPendingMessages() | |||||
{ | |||||
broadcastCallback.handleUpdateNowIfNeeded(); | |||||
} | |||||
void ChangeBroadcaster::callListeners() | |||||
{ | |||||
changeListeners.call (&ChangeListener::changeListenerCallback, this); | |||||
} | |||||
//============================================================================== | |||||
ChangeBroadcaster::ChangeBroadcasterCallback::ChangeBroadcasterCallback() | |||||
: owner (nullptr) | |||||
{ | |||||
} | |||||
void ChangeBroadcaster::ChangeBroadcasterCallback::handleAsyncUpdate() | |||||
{ | |||||
jassert (owner != nullptr); | |||||
owner->callListeners(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,101 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Holds a list of ChangeListeners, and sends messages to them when instructed. | |||||
@see ChangeListener | |||||
*/ | |||||
class JUCE_API ChangeBroadcaster | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an ChangeBroadcaster. */ | |||||
ChangeBroadcaster() noexcept; | |||||
/** Destructor. */ | |||||
virtual ~ChangeBroadcaster(); | |||||
//============================================================================== | |||||
/** Registers a listener to receive change callbacks from this broadcaster. | |||||
Trying to add a listener that's already on the list will have no effect. | |||||
*/ | |||||
void addChangeListener (ChangeListener* listener); | |||||
/** Unregisters a listener from the list. | |||||
If the listener isn't on the list, this won't have any effect. | |||||
*/ | |||||
void removeChangeListener (ChangeListener* listener); | |||||
/** Removes all listeners from the list. */ | |||||
void removeAllChangeListeners(); | |||||
//============================================================================== | |||||
/** Causes an asynchronous change message to be sent to all the registered listeners. | |||||
The message will be delivered asynchronously by the main message thread, so this | |||||
method will return immediately. To call the listeners synchronously use | |||||
sendSynchronousChangeMessage(). | |||||
*/ | |||||
void sendChangeMessage(); | |||||
/** Sends a synchronous change message to all the registered listeners. | |||||
This will immediately call all the listeners that are registered. For thread-safety | |||||
reasons, you must only call this method on the main message thread. | |||||
@see dispatchPendingMessages | |||||
*/ | |||||
void sendSynchronousChangeMessage(); | |||||
/** If a change message has been sent but not yet dispatched, this will call | |||||
sendSynchronousChangeMessage() to make the callback immediately. | |||||
For thread-safety reasons, you must only call this method on the main message thread. | |||||
*/ | |||||
void dispatchPendingMessages(); | |||||
private: | |||||
//============================================================================== | |||||
class ChangeBroadcasterCallback : public AsyncUpdater | |||||
{ | |||||
public: | |||||
ChangeBroadcasterCallback(); | |||||
void handleAsyncUpdate() override; | |||||
ChangeBroadcaster* owner; | |||||
}; | |||||
friend class ChangeBroadcasterCallback; | |||||
ChangeBroadcasterCallback broadcastCallback; | |||||
ListenerList <ChangeListener> changeListeners; | |||||
void callListeners(); | |||||
JUCE_DECLARE_NON_COPYABLE (ChangeBroadcaster) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,61 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class ChangeBroadcaster; | |||||
//============================================================================== | |||||
/** | |||||
Receives change event callbacks that are sent out by a ChangeBroadcaster. | |||||
A ChangeBroadcaster keeps a set of listeners to which it broadcasts a message when | |||||
the ChangeBroadcaster::sendChangeMessage() method is called. A subclass of | |||||
ChangeListener is used to receive these callbacks. | |||||
Note that the major difference between an ActionListener and a ChangeListener | |||||
is that for a ChangeListener, multiple changes will be coalesced into fewer | |||||
callbacks, but ActionListeners perform one callback for every event posted. | |||||
@see ChangeBroadcaster, ActionListener | |||||
*/ | |||||
class JUCE_API ChangeListener | |||||
{ | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~ChangeListener() {} | |||||
/** Your subclass should implement this method to receive the callback. | |||||
@param source the ChangeBroadcaster that triggered the callback. | |||||
*/ | |||||
virtual void changeListenerCallback (ChangeBroadcaster* source) = 0; | |||||
//============================================================================== | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// This method's signature has changed to take a ChangeBroadcaster parameter - please update your code! | |||||
private: virtual int changeListenerCallback (void*) { return 0; } | |||||
#endif | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,267 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
enum { magicMastSlaveConnectionHeader = 0x712baf04 }; | |||||
static const char* startMessage = "__ipc_st"; | |||||
static const char* killMessage = "__ipc_k_"; | |||||
static const char* pingMessage = "__ipc_p_"; | |||||
enum { specialMessageSize = 8, defaultTimeoutMs = 8000 }; | |||||
static String getCommandLinePrefix (const String& commandLineUniqueID) | |||||
{ | |||||
return "--" + commandLineUniqueID + ":"; | |||||
} | |||||
//============================================================================== | |||||
// This thread sends and receives ping messages every second, so that it | |||||
// can find out if the other process has stopped running. | |||||
struct ChildProcessPingThread : public Thread, | |||||
private AsyncUpdater | |||||
{ | |||||
ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout) | |||||
{ | |||||
pingReceived(); | |||||
} | |||||
static bool isPingMessage (const MemoryBlock& m) noexcept | |||||
{ | |||||
return memcmp (m.getData(), pingMessage, specialMessageSize) == 0; | |||||
} | |||||
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; } | |||||
void triggerConnectionLostMessage() { triggerAsyncUpdate(); } | |||||
virtual bool sendPingMessage (const MemoryBlock&) = 0; | |||||
virtual void pingFailed() = 0; | |||||
int timeoutMs; | |||||
private: | |||||
Atomic<int> countdown; | |||||
void handleAsyncUpdate() override { pingFailed(); } | |||||
void run() override | |||||
{ | |||||
while (! threadShouldExit()) | |||||
{ | |||||
if (--countdown <= 0 || ! sendPingMessage (MemoryBlock (pingMessage, specialMessageSize))) | |||||
{ | |||||
triggerConnectionLostMessage(); | |||||
break; | |||||
} | |||||
wait (1000); | |||||
} | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread) | |||||
}; | |||||
//============================================================================== | |||||
struct ChildProcessMaster::Connection : public InterprocessConnection, | |||||
private ChildProcessPingThread | |||||
{ | |||||
Connection (ChildProcessMaster& m, const String& pipeName, int timeout) | |||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader), | |||||
ChildProcessPingThread (timeout), | |||||
owner (m) | |||||
{ | |||||
if (createPipe (pipeName, timeoutMs)) | |||||
startThread (4); | |||||
} | |||||
~Connection() | |||||
{ | |||||
stopThread (10000); | |||||
} | |||||
private: | |||||
void connectionMade() override {} | |||||
void connectionLost() override { owner.handleConnectionLost(); } | |||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); } | |||||
void pingFailed() override { connectionLost(); } | |||||
void messageReceived (const MemoryBlock& m) override | |||||
{ | |||||
pingReceived(); | |||||
if (m.getSize() != specialMessageSize || ! isPingMessage (m)) | |||||
owner.handleMessageFromSlave (m); | |||||
} | |||||
ChildProcessMaster& owner; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||||
}; | |||||
//============================================================================== | |||||
ChildProcessMaster::ChildProcessMaster() {} | |||||
ChildProcessMaster::~ChildProcessMaster() | |||||
{ | |||||
if (connection != nullptr) | |||||
{ | |||||
sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize)); | |||||
connection->disconnect(); | |||||
connection = nullptr; | |||||
} | |||||
} | |||||
void ChildProcessMaster::handleConnectionLost() {} | |||||
bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb) | |||||
{ | |||||
if (connection != nullptr) | |||||
return connection->sendMessage (mb); | |||||
jassertfalse; // this can only be used when the connection is active! | |||||
return false; | |||||
} | |||||
bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID, int timeoutMs, int streamFlags) | |||||
{ | |||||
connection = nullptr; | |||||
jassert (childProcess.kill()); | |||||
const String pipeName ("p" + String::toHexString (Random().nextInt64())); | |||||
StringArray args; | |||||
args.add (executable.getFullPathName()); | |||||
args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName); | |||||
if (childProcess.start (args, streamFlags)) | |||||
{ | |||||
connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); | |||||
if (connection->isConnected()) | |||||
{ | |||||
sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize)); | |||||
return true; | |||||
} | |||||
connection = nullptr; | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
struct ChildProcessSlave::Connection : public InterprocessConnection, | |||||
private ChildProcessPingThread | |||||
{ | |||||
Connection (ChildProcessSlave& p, const String& pipeName, int timeout) | |||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader), | |||||
ChildProcessPingThread (timeout), | |||||
owner (p) | |||||
{ | |||||
connectToPipe (pipeName, timeoutMs); | |||||
startThread (4); | |||||
} | |||||
~Connection() | |||||
{ | |||||
stopThread (10000); | |||||
} | |||||
private: | |||||
ChildProcessSlave& owner; | |||||
void connectionMade() override {} | |||||
void connectionLost() override { owner.handleConnectionLost(); } | |||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); } | |||||
void pingFailed() override { connectionLost(); } | |||||
void messageReceived (const MemoryBlock& m) override | |||||
{ | |||||
pingReceived(); | |||||
if (m.getSize() == specialMessageSize) | |||||
{ | |||||
if (isPingMessage (m)) | |||||
return; | |||||
if (memcmp (m.getData(), killMessage, specialMessageSize) == 0) | |||||
{ | |||||
triggerConnectionLostMessage(); | |||||
return; | |||||
} | |||||
if (memcmp (m.getData(), startMessage, specialMessageSize) == 0) | |||||
{ | |||||
owner.handleConnectionMade(); | |||||
return; | |||||
} | |||||
} | |||||
owner.handleMessageFromMaster (m); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection) | |||||
}; | |||||
//============================================================================== | |||||
ChildProcessSlave::ChildProcessSlave() {} | |||||
ChildProcessSlave::~ChildProcessSlave() {} | |||||
void ChildProcessSlave::handleConnectionMade() {} | |||||
void ChildProcessSlave::handleConnectionLost() {} | |||||
bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb) | |||||
{ | |||||
if (connection != nullptr) | |||||
return connection->sendMessage (mb); | |||||
jassertfalse; // this can only be used when the connection is active! | |||||
return false; | |||||
} | |||||
bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine, | |||||
const String& commandLineUniqueID, | |||||
int timeoutMs) | |||||
{ | |||||
String prefix (getCommandLinePrefix (commandLineUniqueID)); | |||||
if (commandLine.trim().startsWith (prefix)) | |||||
{ | |||||
String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false) | |||||
.upToFirstOccurrenceOf (" ", false, false).trim()); | |||||
if (pipeName.isNotEmpty()) | |||||
{ | |||||
connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs); | |||||
if (! connection->isConnected()) | |||||
connection = nullptr; | |||||
} | |||||
} | |||||
return connection != nullptr; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,189 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Acts as the slave end of a master/slave pair of connected processes. | |||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||||
to spawn a child process, and to manage a 2-way messaging connection to control it. | |||||
To use the system, you need to create subclasses of both ChildProcessSlave and | |||||
ChildProcessMaster. To instantiate the ChildProcessSlave object, you must | |||||
add some code to your main() or JUCEApplication::initialise() function that | |||||
calls the initialiseFromCommandLine() method to check the app's command-line | |||||
parameters to see whether it's being launched as a child process. If this returns | |||||
true then the slave process can be allowed to run, and its handleMessageFromMaster() | |||||
method will be called whenever a message arrives. | |||||
The juce demo app has a good example of this class in action. | |||||
@see ChildProcessMaster, InterprocessConnection, ChildProcess | |||||
*/ | |||||
class JUCE_API ChildProcessSlave | |||||
{ | |||||
public: | |||||
/** Creates a non-connected slave process. | |||||
Use initialiseFromCommandLine to connect to a master process. | |||||
*/ | |||||
ChildProcessSlave(); | |||||
/** Destructor. */ | |||||
virtual ~ChildProcessSlave(); | |||||
/** This checks some command-line parameters to see whether they were generated by | |||||
ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process. | |||||
In an exe that can be used as a child process, you should add some code to your | |||||
main() or JUCEApplication::initialise() that calls this method. | |||||
The commandLineUniqueID should be a short alphanumeric identifier (no spaces!) | |||||
that matches the string passed to ChildProcessMaster::launchSlaveProcess(). | |||||
The timeoutMs parameter lets you specify how long the child process is allowed | |||||
to run without receiving a ping from the master before the master is considered to | |||||
have died, and handleConnectionLost() will be called. Passing <= 0 for this timeout | |||||
makes it use a default value. | |||||
Returns true if the command-line matches and the connection is made successfully. | |||||
*/ | |||||
bool initialiseFromCommandLine (const String& commandLine, | |||||
const String& commandLineUniqueID, | |||||
int timeoutMs = 0); | |||||
//============================================================================== | |||||
/** This will be called to deliver messages from the master process. | |||||
The call will probably be made on a background thread, so be careful with your | |||||
thread-safety! You may want to respond by sending back a message with | |||||
sendMessageToMaster() | |||||
*/ | |||||
virtual void handleMessageFromMaster (const MemoryBlock&) = 0; | |||||
/** This will be called when the master process finishes connecting to this slave. | |||||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||||
*/ | |||||
virtual void handleConnectionMade(); | |||||
/** This will be called when the connection to the master process is lost. | |||||
The call may be made from any thread (including the message thread). | |||||
Typically, if your process only exists to act as a slave, you should probably exit | |||||
when this happens. | |||||
*/ | |||||
virtual void handleConnectionLost(); | |||||
/** Tries to send a message to the master process. | |||||
This returns true if the message was sent, but doesn't check that it actually gets | |||||
delivered at the other end. If successful, the data will emerge in a call to your | |||||
ChildProcessMaster::handleMessageFromSlave(). | |||||
*/ | |||||
bool sendMessageToMaster (const MemoryBlock&); | |||||
private: | |||||
struct Connection; | |||||
friend struct Connection; | |||||
friend struct ContainerDeletePolicy<Connection>; | |||||
ScopedPointer<Connection> connection; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
Acts as the master in a master/slave pair of connected processes. | |||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app | |||||
to spawn a child process, and to manage a 2-way messaging connection to control it. | |||||
To use the system, you need to create subclasses of both ChildProcessSlave and | |||||
ChildProcessMaster. When you want your master process to launch the slave, you | |||||
just call launchSlaveProcess(), and it'll attempt to launch the executable that | |||||
you specify (which may be the same exe), and assuming it has been set-up to | |||||
correctly parse the command-line parameters (see ChildProcessSlave) then a | |||||
two-way connection will be created. | |||||
The juce demo app has a good example of this class in action. | |||||
@see ChildProcessSlave, InterprocessConnection, ChildProcess | |||||
*/ | |||||
class JUCE_API ChildProcessMaster | |||||
{ | |||||
public: | |||||
/** Creates an uninitialised master process object. | |||||
Use launchSlaveProcess to launch and connect to a child process. | |||||
*/ | |||||
ChildProcessMaster(); | |||||
/** Destructor. */ | |||||
virtual ~ChildProcessMaster(); | |||||
/** Attempts to launch and connect to a slave process. | |||||
This will start the given executable, passing it a special command-line | |||||
parameter based around the commandLineUniqueID string, which must be a | |||||
short alphanumeric string (no spaces!) that identifies your app. The exe | |||||
that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine() | |||||
in its startup code, and must use a matching ID to commandLineUniqueID. | |||||
The timeoutMs parameter lets you specify how long the child process is allowed | |||||
to go without sending a ping before it is considered to have died and | |||||
handleConnectionLost() will be called. Passing <= 0 for this timeout makes | |||||
it use a default value. | |||||
If this all works, the method returns true, and you can begin sending and | |||||
receiving messages with the slave process. | |||||
*/ | |||||
bool launchSlaveProcess (const File& executableToLaunch, | |||||
const String& commandLineUniqueID, | |||||
int timeoutMs = 0, | |||||
int streamFlags = ChildProcess::wantStdOut | ChildProcess::wantStdErr); | |||||
/** This will be called to deliver a message from the slave process. | |||||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||||
*/ | |||||
virtual void handleMessageFromSlave (const MemoryBlock&) = 0; | |||||
/** This will be called when the slave process dies or is somehow disconnected. | |||||
The call will probably be made on a background thread, so be careful with your thread-safety! | |||||
*/ | |||||
virtual void handleConnectionLost(); | |||||
/** Attempts to send a message to the slave process. | |||||
This returns true if the message was dispatched, but doesn't check that it actually | |||||
gets delivered at the other end. If successful, the data will emerge in a call to | |||||
your ChildProcessSlave::handleMessageFromMaster(). | |||||
*/ | |||||
bool sendMessageToSlave (const MemoryBlock&); | |||||
private: | |||||
ChildProcess childProcess; | |||||
struct Connection; | |||||
friend struct Connection; | |||||
friend struct ContainerDeletePolicy<Connection>; | |||||
ScopedPointer<Connection> connection; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,362 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct InterprocessConnection::ConnectionThread : public Thread | |||||
{ | |||||
ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {} | |||||
void run() override { owner.runThread(); } | |||||
InterprocessConnection& owner; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread) | |||||
}; | |||||
//============================================================================== | |||||
InterprocessConnection::InterprocessConnection (bool callbacksOnMessageThread, uint32 magicMessageHeaderNumber) | |||||
: useMessageThread (callbacksOnMessageThread), | |||||
magicMessageHeader (magicMessageHeaderNumber) | |||||
{ | |||||
thread = new ConnectionThread (*this); | |||||
} | |||||
InterprocessConnection::~InterprocessConnection() | |||||
{ | |||||
callbackConnectionState = false; | |||||
disconnect(); | |||||
masterReference.clear(); | |||||
thread = nullptr; | |||||
} | |||||
//============================================================================== | |||||
bool InterprocessConnection::connectToSocket (const String& hostName, | |||||
const int portNumber, | |||||
const int timeOutMillisecs) | |||||
{ | |||||
disconnect(); | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
socket = new StreamingSocket(); | |||||
if (socket->connect (hostName, portNumber, timeOutMillisecs)) | |||||
{ | |||||
connectionMadeInt(); | |||||
thread->startThread(); | |||||
return true; | |||||
} | |||||
socket = nullptr; | |||||
return false; | |||||
} | |||||
bool InterprocessConnection::connectToPipe (const String& pipeName, const int timeoutMs) | |||||
{ | |||||
disconnect(); | |||||
ScopedPointer<NamedPipe> newPipe (new NamedPipe()); | |||||
if (newPipe->openExisting (pipeName)) | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
pipeReceiveMessageTimeout = timeoutMs; | |||||
initialiseWithPipe (newPipe.release()); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool InterprocessConnection::createPipe (const String& pipeName, const int timeoutMs, bool mustNotExist) | |||||
{ | |||||
disconnect(); | |||||
ScopedPointer<NamedPipe> newPipe (new NamedPipe()); | |||||
if (newPipe->createNewPipe (pipeName, mustNotExist)) | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
pipeReceiveMessageTimeout = timeoutMs; | |||||
initialiseWithPipe (newPipe.release()); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
void InterprocessConnection::disconnect() | |||||
{ | |||||
thread->signalThreadShouldExit(); | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
if (socket != nullptr) socket->close(); | |||||
if (pipe != nullptr) pipe->close(); | |||||
} | |||||
thread->stopThread (4000); | |||||
deletePipeAndSocket(); | |||||
connectionLostInt(); | |||||
} | |||||
void InterprocessConnection::deletePipeAndSocket() | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
socket = nullptr; | |||||
pipe = nullptr; | |||||
} | |||||
bool InterprocessConnection::isConnected() const | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
return ((socket != nullptr && socket->isConnected()) | |||||
|| (pipe != nullptr && pipe->isOpen())) | |||||
&& thread->isThreadRunning(); | |||||
} | |||||
String InterprocessConnection::getConnectedHostName() const | |||||
{ | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
if (pipe == nullptr && socket == nullptr) | |||||
return {}; | |||||
if (socket != nullptr && ! socket->isLocal()) | |||||
return socket->getHostName(); | |||||
} | |||||
return IPAddress::local().toString(); | |||||
} | |||||
//============================================================================== | |||||
bool InterprocessConnection::sendMessage (const MemoryBlock& message) | |||||
{ | |||||
uint32 messageHeader[2] = { ByteOrder::swapIfBigEndian (magicMessageHeader), | |||||
ByteOrder::swapIfBigEndian ((uint32) message.getSize()) }; | |||||
MemoryBlock messageData (sizeof (messageHeader) + message.getSize()); | |||||
messageData.copyFrom (messageHeader, 0, sizeof (messageHeader)); | |||||
messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize()); | |||||
return writeData (messageData.getData(), (int) messageData.getSize()) == (int) messageData.getSize(); | |||||
} | |||||
int InterprocessConnection::writeData (void* data, int dataSize) | |||||
{ | |||||
const ScopedLock sl (pipeAndSocketLock); | |||||
if (socket != nullptr) | |||||
return socket->write (data, dataSize); | |||||
if (pipe != nullptr) | |||||
return pipe->write (data, dataSize, pipeReceiveMessageTimeout); | |||||
return 0; | |||||
} | |||||
//============================================================================== | |||||
void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket) | |||||
{ | |||||
jassert (socket == nullptr && pipe == nullptr); | |||||
socket = newSocket; | |||||
connectionMadeInt(); | |||||
thread->startThread(); | |||||
} | |||||
void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe) | |||||
{ | |||||
jassert (socket == nullptr && pipe == nullptr); | |||||
pipe = newPipe; | |||||
connectionMadeInt(); | |||||
thread->startThread(); | |||||
} | |||||
//============================================================================== | |||||
struct ConnectionStateMessage : public MessageManager::MessageBase | |||||
{ | |||||
ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept | |||||
: owner (ipc), connectionMade (connected) | |||||
{} | |||||
void messageCallback() override | |||||
{ | |||||
if (auto* ipc = owner.get()) | |||||
{ | |||||
if (connectionMade) | |||||
ipc->connectionMade(); | |||||
else | |||||
ipc->connectionLost(); | |||||
} | |||||
} | |||||
WeakReference<InterprocessConnection> owner; | |||||
bool connectionMade; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage) | |||||
}; | |||||
void InterprocessConnection::connectionMadeInt() | |||||
{ | |||||
if (! callbackConnectionState) | |||||
{ | |||||
callbackConnectionState = true; | |||||
if (useMessageThread) | |||||
(new ConnectionStateMessage (this, true))->post(); | |||||
else | |||||
connectionMade(); | |||||
} | |||||
} | |||||
void InterprocessConnection::connectionLostInt() | |||||
{ | |||||
if (callbackConnectionState) | |||||
{ | |||||
callbackConnectionState = false; | |||||
if (useMessageThread) | |||||
(new ConnectionStateMessage (this, false))->post(); | |||||
else | |||||
connectionLost(); | |||||
} | |||||
} | |||||
struct DataDeliveryMessage : public Message | |||||
{ | |||||
DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d) | |||||
: owner (ipc), data (d) | |||||
{} | |||||
void messageCallback() override | |||||
{ | |||||
if (auto* ipc = owner.get()) | |||||
ipc->messageReceived (data); | |||||
} | |||||
WeakReference<InterprocessConnection> owner; | |||||
MemoryBlock data; | |||||
}; | |||||
void InterprocessConnection::deliverDataInt (const MemoryBlock& data) | |||||
{ | |||||
jassert (callbackConnectionState); | |||||
if (useMessageThread) | |||||
(new DataDeliveryMessage (this, data))->post(); | |||||
else | |||||
messageReceived (data); | |||||
} | |||||
//============================================================================== | |||||
bool InterprocessConnection::readNextMessageInt() | |||||
{ | |||||
uint32 messageHeader[2]; | |||||
const int bytes = socket != nullptr ? socket->read (messageHeader, sizeof (messageHeader), true) | |||||
: pipe ->read (messageHeader, sizeof (messageHeader), -1); | |||||
if (bytes == sizeof (messageHeader) | |||||
&& ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader) | |||||
{ | |||||
int bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]); | |||||
if (bytesInMessage > 0) | |||||
{ | |||||
MemoryBlock messageData ((size_t) bytesInMessage, true); | |||||
int bytesRead = 0; | |||||
while (bytesInMessage > 0) | |||||
{ | |||||
if (thread->threadShouldExit()) | |||||
return false; | |||||
const int numThisTime = jmin (bytesInMessage, 65536); | |||||
void* const data = addBytesToPointer (messageData.getData(), bytesRead); | |||||
const int bytesIn = socket != nullptr ? socket->read (data, numThisTime, true) | |||||
: pipe ->read (data, numThisTime, -1); | |||||
if (bytesIn <= 0) | |||||
break; | |||||
bytesRead += bytesIn; | |||||
bytesInMessage -= bytesIn; | |||||
} | |||||
if (bytesRead >= 0) | |||||
deliverDataInt (messageData); | |||||
} | |||||
} | |||||
else if (bytes < 0) | |||||
{ | |||||
if (socket != nullptr) | |||||
deletePipeAndSocket(); | |||||
connectionLostInt(); | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
void InterprocessConnection::runThread() | |||||
{ | |||||
while (! thread->threadShouldExit()) | |||||
{ | |||||
if (socket != nullptr) | |||||
{ | |||||
auto ready = socket->waitUntilReady (true, 0); | |||||
if (ready < 0) | |||||
{ | |||||
deletePipeAndSocket(); | |||||
connectionLostInt(); | |||||
break; | |||||
} | |||||
if (ready == 0) | |||||
{ | |||||
thread->wait (1); | |||||
continue; | |||||
} | |||||
} | |||||
else if (pipe != nullptr) | |||||
{ | |||||
if (! pipe->isOpen()) | |||||
{ | |||||
deletePipeAndSocket(); | |||||
connectionLostInt(); | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
break; | |||||
} | |||||
if (thread->threadShouldExit() || ! readNextMessageInt()) | |||||
break; | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,207 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class InterprocessConnectionServer; | |||||
class MemoryBlock; | |||||
//============================================================================== | |||||
/** | |||||
Manages a simple two-way messaging connection to another process, using either | |||||
a socket or a named pipe as the transport medium. | |||||
To connect to a waiting socket or an open pipe, use the connectToSocket() or | |||||
connectToPipe() methods. If this succeeds, messages can be sent to the other end, | |||||
and incoming messages will result in a callback via the messageReceived() | |||||
method. | |||||
To open a pipe and wait for another client to connect to it, use the createPipe() | |||||
method. | |||||
To act as a socket server and create connections for one or more client, see the | |||||
InterprocessConnectionServer class. | |||||
@see InterprocessConnectionServer, Socket, NamedPipe | |||||
*/ | |||||
class JUCE_API InterprocessConnection | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a connection. | |||||
Connections are created manually, connecting them with the connectToSocket() | |||||
or connectToPipe() methods, or they are created automatically by a InterprocessConnectionServer | |||||
when a client wants to connect. | |||||
@param callbacksOnMessageThread if true, callbacks to the connectionMade(), | |||||
connectionLost() and messageReceived() methods will | |||||
always be made using the message thread; if false, | |||||
these will be called immediately on the connection's | |||||
own thread. | |||||
@param magicMessageHeaderNumber a magic number to use in the header to check the | |||||
validity of the data blocks being sent and received. This | |||||
can be any number, but the sender and receiver must obviously | |||||
use matching values or they won't recognise each other. | |||||
*/ | |||||
InterprocessConnection (bool callbacksOnMessageThread = true, | |||||
uint32 magicMessageHeaderNumber = 0xf2b49e2c); | |||||
/** Destructor. */ | |||||
virtual ~InterprocessConnection(); | |||||
//============================================================================== | |||||
/** Tries to connect this object to a socket. | |||||
For this to work, the machine on the other end needs to have a InterprocessConnectionServer | |||||
object waiting to receive client connections on this port number. | |||||
@param hostName the host computer, either a network address or name | |||||
@param portNumber the socket port number to try to connect to | |||||
@param timeOutMillisecs how long to keep trying before giving up | |||||
@returns true if the connection is established successfully | |||||
@see Socket | |||||
*/ | |||||
bool connectToSocket (const String& hostName, | |||||
int portNumber, | |||||
int timeOutMillisecs); | |||||
/** Tries to connect the object to an existing named pipe. | |||||
For this to work, another process on the same computer must already have opened | |||||
an InterprocessConnection object and used createPipe() to create a pipe for this | |||||
to connect to. | |||||
@param pipeName the name to use for the pipe - this should be unique to your app | |||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing | |||||
to the pipe, or -1 for an infinite timeout. | |||||
@returns true if it connects successfully. | |||||
@see createPipe, NamedPipe | |||||
*/ | |||||
bool connectToPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs); | |||||
/** Tries to create a new pipe for other processes to connect to. | |||||
This creates a pipe with the given name, so that other processes can use | |||||
connectToPipe() to connect to the other end. | |||||
@param pipeName the name to use for the pipe - this should be unique to your app | |||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing | |||||
to the pipe, or -1 for an infinite timeout | |||||
@param mustNotExist if set to true, the method will fail if the pipe already exists | |||||
@returns true if the pipe was created, or false if it fails (e.g. if another process is | |||||
already using using the pipe) | |||||
*/ | |||||
bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs, bool mustNotExist = false); | |||||
/** Disconnects and closes any currently-open sockets or pipes. */ | |||||
void disconnect(); | |||||
/** True if a socket or pipe is currently active. */ | |||||
bool isConnected() const; | |||||
/** Returns the socket that this connection is using (or nullptr if it uses a pipe). */ | |||||
StreamingSocket* getSocket() const noexcept { return socket; } | |||||
/** Returns the pipe that this connection is using (or nullptr if it uses a socket). */ | |||||
NamedPipe* getPipe() const noexcept { return pipe; } | |||||
/** Returns the name of the machine at the other end of this connection. | |||||
This may return an empty string if the name is unknown. | |||||
*/ | |||||
String getConnectedHostName() const; | |||||
//============================================================================== | |||||
/** Tries to send a message to the other end of this connection. | |||||
This will fail if it's not connected, or if there's some kind of write error. If | |||||
it succeeds, the connection object at the other end will receive the message by | |||||
a callback to its messageReceived() method. | |||||
@see messageReceived | |||||
*/ | |||||
bool sendMessage (const MemoryBlock& message); | |||||
//============================================================================== | |||||
/** Called when the connection is first connected. | |||||
If the connection was created with the callbacksOnMessageThread flag set, then | |||||
this will be called on the message thread; otherwise it will be called on a server | |||||
thread. | |||||
*/ | |||||
virtual void connectionMade() = 0; | |||||
/** Called when the connection is broken. | |||||
If the connection was created with the callbacksOnMessageThread flag set, then | |||||
this will be called on the message thread; otherwise it will be called on a server | |||||
thread. | |||||
*/ | |||||
virtual void connectionLost() = 0; | |||||
/** Called when a message arrives. | |||||
When the object at the other end of this connection sends us a message with sendMessage(), | |||||
this callback is used to deliver it to us. | |||||
If the connection was created with the callbacksOnMessageThread flag set, then | |||||
this will be called on the message thread; otherwise it will be called on a server | |||||
thread. | |||||
@see sendMessage | |||||
*/ | |||||
virtual void messageReceived (const MemoryBlock& message) = 0; | |||||
private: | |||||
//============================================================================== | |||||
CriticalSection pipeAndSocketLock; | |||||
ScopedPointer<StreamingSocket> socket; | |||||
ScopedPointer<NamedPipe> pipe; | |||||
bool callbackConnectionState = false; | |||||
const bool useMessageThread; | |||||
const uint32 magicMessageHeader; | |||||
int pipeReceiveMessageTimeout = -1; | |||||
friend class InterprocessConnectionServer; | |||||
void initialiseWithSocket (StreamingSocket*); | |||||
void initialiseWithPipe (NamedPipe*); | |||||
void deletePipeAndSocket(); | |||||
void connectionMadeInt(); | |||||
void connectionLostInt(); | |||||
void deliverDataInt (const MemoryBlock&); | |||||
bool readNextMessageInt(); | |||||
struct ConnectionThread; | |||||
friend struct ConnectionThread; | |||||
friend struct ContainerDeletePolicy<ConnectionThread>; | |||||
ScopedPointer<ConnectionThread> thread; | |||||
void runThread(); | |||||
int writeData (void*, int); | |||||
JUCE_DECLARE_WEAK_REFERENCEABLE (InterprocessConnection) | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,81 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
InterprocessConnectionServer::InterprocessConnectionServer() | |||||
: Thread ("Juce IPC server") | |||||
{ | |||||
} | |||||
InterprocessConnectionServer::~InterprocessConnectionServer() | |||||
{ | |||||
stop(); | |||||
} | |||||
//============================================================================== | |||||
bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber, const String& bindAddress) | |||||
{ | |||||
stop(); | |||||
socket = new StreamingSocket(); | |||||
if (socket->createListener (portNumber, bindAddress)) | |||||
{ | |||||
startThread(); | |||||
return true; | |||||
} | |||||
socket = nullptr; | |||||
return false; | |||||
} | |||||
void InterprocessConnectionServer::stop() | |||||
{ | |||||
signalThreadShouldExit(); | |||||
if (socket != nullptr) | |||||
socket->close(); | |||||
stopThread (4000); | |||||
socket = nullptr; | |||||
} | |||||
int InterprocessConnectionServer::getBoundPort() const noexcept | |||||
{ | |||||
return (socket == nullptr) ? -1 : socket->getBoundPort(); | |||||
} | |||||
void InterprocessConnectionServer::run() | |||||
{ | |||||
while ((! threadShouldExit()) && socket != nullptr) | |||||
{ | |||||
ScopedPointer<StreamingSocket> clientSocket (socket->waitForNextConnection()); | |||||
if (clientSocket != nullptr) | |||||
if (InterprocessConnection* newConnection = createConnectionObject()) | |||||
newConnection->initialiseWithSocket (clientSocket.release()); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,104 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An object that waits for client sockets to connect to a port on this host, and | |||||
creates InterprocessConnection objects for each one. | |||||
To use this, create a class derived from it which implements the createConnectionObject() | |||||
method, so that it creates suitable connection objects for each client that tries | |||||
to connect. | |||||
@see InterprocessConnection | |||||
*/ | |||||
class JUCE_API InterprocessConnectionServer : private Thread | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an uninitialised server object. | |||||
*/ | |||||
InterprocessConnectionServer(); | |||||
/** Destructor. */ | |||||
~InterprocessConnectionServer(); | |||||
//============================================================================== | |||||
/** Starts an internal thread which listens on the given port number. | |||||
While this is running, in another process tries to connect with the | |||||
InterprocessConnection::connectToSocket() method, this object will call | |||||
createConnectionObject() to create a connection to that client. | |||||
Use stop() to stop the thread running. | |||||
@param portNumber The port on which the server will receive | |||||
connections | |||||
@param bindAddress The address on which the server will listen | |||||
for connections. An empty string indicates | |||||
that it should listen on all addresses | |||||
assigned to this machine. | |||||
@see createConnectionObject, stop | |||||
*/ | |||||
bool beginWaitingForSocket (int portNumber, const String& bindAddress = String()); | |||||
/** Terminates the listener thread, if it's active. | |||||
@see beginWaitingForSocket | |||||
*/ | |||||
void stop(); | |||||
/** Returns the local port number to which this server is currently bound. | |||||
This is useful if you need to know to which port the OS has actually bound your | |||||
socket when calling beginWaitingForSocket with a port number of zero. | |||||
Returns -1 if the function fails. | |||||
*/ | |||||
int getBoundPort() const noexcept; | |||||
protected: | |||||
/** Creates a suitable connection object for a client process that wants to | |||||
connect to this one. | |||||
This will be called by the listener thread when a client process tries | |||||
to connect, and must return a new InterprocessConnection object that will | |||||
then run as this end of the connection. | |||||
@see InterprocessConnection | |||||
*/ | |||||
virtual InterprocessConnection* createConnectionObject() = 0; | |||||
private: | |||||
//============================================================================== | |||||
ScopedPointer<StreamingSocket> socket; | |||||
void run() override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,102 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
#ifdef JUCE_EVENTS_H_INCLUDED | |||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||||
already included any other headers - just put it inside a file on its own, possibly with your config | |||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||||
header files that the compiler may be using. | |||||
*/ | |||||
#error "Incorrect use of JUCE cpp file" | |||||
#endif | |||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||||
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 | |||||
#if JUCE_USE_WINRT_MIDI | |||||
#define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 | |||||
#endif | |||||
#include "juce_events.h" | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
#import <IOKit/IOKitLib.h> | |||||
#import <IOKit/IOCFPlugIn.h> | |||||
#import <IOKit/hid/IOHIDLib.h> | |||||
#import <IOKit/hid/IOHIDKeys.h> | |||||
#import <IOKit/pwr_mgt/IOPMLib.h> | |||||
#elif JUCE_LINUX | |||||
#include <unistd.h> | |||||
#endif | |||||
//============================================================================== | |||||
#include "messages/juce_ApplicationBase.cpp" | |||||
#include "messages/juce_DeletedAtShutdown.cpp" | |||||
#include "messages/juce_MessageListener.cpp" | |||||
#include "messages/juce_MessageManager.cpp" | |||||
#include "broadcasters/juce_ActionBroadcaster.cpp" | |||||
#include "broadcasters/juce_AsyncUpdater.cpp" | |||||
#include "broadcasters/juce_ChangeBroadcaster.cpp" | |||||
#include "timers/juce_MultiTimer.cpp" | |||||
#include "timers/juce_Timer.cpp" | |||||
#include "interprocess/juce_InterprocessConnection.cpp" | |||||
#include "interprocess/juce_InterprocessConnectionServer.cpp" | |||||
#include "interprocess/juce_ConnectedChildProcess.cpp" | |||||
//============================================================================== | |||||
#if JUCE_MAC || JUCE_IOS | |||||
#include "native/juce_osx_MessageQueue.h" | |||||
#if JUCE_CLANG | |||||
#pragma clang diagnostic push | |||||
#pragma clang diagnostic ignored "-Wundeclared-selector" | |||||
#endif | |||||
#if JUCE_MAC | |||||
#include "native/juce_mac_MessageManager.mm" | |||||
#else | |||||
#include "native/juce_ios_MessageManager.mm" | |||||
#endif | |||||
#if JUCE_CLANG | |||||
#pragma clang diagnostic pop | |||||
#endif | |||||
#elif JUCE_WINDOWS | |||||
#include "native/juce_win32_Messaging.cpp" | |||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER | |||||
#include "native/juce_win32_WinRTWrapper.cpp" | |||||
#endif | |||||
#elif JUCE_LINUX | |||||
#include "native/juce_linux_Messaging.cpp" | |||||
#elif JUCE_ANDROID | |||||
#include "native/juce_android_Messaging.cpp" | |||||
#endif |
@@ -0,0 +1,96 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_events | |||||
vendor: juce | |||||
version: 5.1.2 | |||||
name: JUCE message and event handling classes | |||||
description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | |||||
website: http://www.juce.com/juce | |||||
license: ISC | |||||
dependencies: juce_core | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#pragma once | |||||
#define JUCE_EVENTS_H_INCLUDED | |||||
#include <juce_core/juce_core.h> | |||||
//============================================================================== | |||||
/** Config: JUCE_EXECUTE_APP_SUSPEND_ON_IOS_BACKGROUND_TASK | |||||
Will execute your application's suspend method on an iOS background task, giving | |||||
you extra time to save your applications state. | |||||
*/ | |||||
#ifndef JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK | |||||
#define JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 0 | |||||
#endif | |||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER && JUCE_WINDOWS | |||||
#include <hstring.h> | |||||
#endif | |||||
#include "messages/juce_MessageManager.h" | |||||
#include "messages/juce_Message.h" | |||||
#include "messages/juce_MessageListener.h" | |||||
#include "messages/juce_CallbackMessage.h" | |||||
#include "messages/juce_DeletedAtShutdown.h" | |||||
#include "messages/juce_NotificationType.h" | |||||
#include "messages/juce_ApplicationBase.h" | |||||
#include "messages/juce_Initialisation.h" | |||||
#include "messages/juce_MountedVolumeListChangeDetector.h" | |||||
#include "broadcasters/juce_ActionBroadcaster.h" | |||||
#include "broadcasters/juce_ActionListener.h" | |||||
#include "broadcasters/juce_AsyncUpdater.h" | |||||
#include "broadcasters/juce_ChangeListener.h" | |||||
#include "broadcasters/juce_ChangeBroadcaster.h" | |||||
#include "timers/juce_Timer.h" | |||||
#include "timers/juce_MultiTimer.h" | |||||
#include "interprocess/juce_InterprocessConnection.h" | |||||
#include "interprocess/juce_InterprocessConnectionServer.h" | |||||
#include "interprocess/juce_ConnectedChildProcess.h" | |||||
#if JUCE_LINUX | |||||
#include "native/juce_linux_EventLoop.h" | |||||
#endif | |||||
#if JUCE_WINDOWS | |||||
#if JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW | |||||
#include "native/juce_win32_HiddenMessageWindow.h" | |||||
#endif | |||||
#if JUCE_EVENTS_INCLUDE_WINRT_WRAPPER | |||||
#include "native/juce_win32_WinRTWrapper.h" | |||||
#endif | |||||
#endif |
@@ -0,0 +1,333 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
JUCEApplicationBase::CreateInstanceFunction JUCEApplicationBase::createInstance = 0; | |||||
JUCEApplicationBase* JUCEApplicationBase::appInstance = nullptr; | |||||
#if JUCE_IOS | |||||
void* JUCEApplicationBase::iOSCustomDelegate = nullptr; | |||||
#endif | |||||
JUCEApplicationBase::JUCEApplicationBase() | |||||
: appReturnValue (0), | |||||
stillInitialising (true) | |||||
{ | |||||
jassert (isStandaloneApp() && appInstance == nullptr); | |||||
appInstance = this; | |||||
} | |||||
JUCEApplicationBase::~JUCEApplicationBase() | |||||
{ | |||||
jassert (appInstance == this); | |||||
appInstance = nullptr; | |||||
} | |||||
void JUCEApplicationBase::setApplicationReturnValue (const int newReturnValue) noexcept | |||||
{ | |||||
appReturnValue = newReturnValue; | |||||
} | |||||
// This is called on the Mac and iOS where the OS doesn't allow the stack to unwind on shutdown.. | |||||
void JUCEApplicationBase::appWillTerminateByForce() | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
{ | |||||
const ScopedPointer<JUCEApplicationBase> app (appInstance); | |||||
if (app != nullptr) | |||||
app->shutdownApp(); | |||||
} | |||||
DeletedAtShutdown::deleteAll(); | |||||
MessageManager::deleteInstance(); | |||||
} | |||||
} | |||||
void JUCEApplicationBase::quit() | |||||
{ | |||||
MessageManager::getInstance()->stopDispatchLoop(); | |||||
} | |||||
void JUCEApplicationBase::sendUnhandledException (const std::exception* const e, | |||||
const char* const sourceFile, | |||||
const int lineNumber) | |||||
{ | |||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||||
{ | |||||
// If you hit this assertion then the __FILE__ macro is providing a | |||||
// relative path instead of an absolute path. On Windows this will be | |||||
// a path relative to the build directory rather than the currently | |||||
// running application. To fix this you must compile with the /FC flag. | |||||
jassert (File::isAbsolutePath (sourceFile)); | |||||
app->unhandledException (e, sourceFile, lineNumber); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
#if ! (JUCE_IOS || JUCE_ANDROID) | |||||
#define JUCE_HANDLE_MULTIPLE_INSTANCES 1 | |||||
#endif | |||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES | |||||
struct JUCEApplicationBase::MultipleInstanceHandler : public ActionListener | |||||
{ | |||||
public: | |||||
MultipleInstanceHandler (const String& appName) | |||||
: appLock ("juceAppLock_" + appName) | |||||
{ | |||||
} | |||||
bool sendCommandLineToPreexistingInstance() | |||||
{ | |||||
if (appLock.enter (0)) | |||||
return false; | |||||
JUCEApplicationBase* const app = JUCEApplicationBase::getInstance(); | |||||
jassert (app != nullptr); | |||||
MessageManager::broadcastMessage (app->getApplicationName() | |||||
+ "/" + app->getCommandLineParameters()); | |||||
return true; | |||||
} | |||||
void actionListenerCallback (const String& message) override | |||||
{ | |||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||||
{ | |||||
const String appName (app->getApplicationName()); | |||||
if (message.startsWith (appName + "/")) | |||||
app->anotherInstanceStarted (message.substring (appName.length() + 1)); | |||||
} | |||||
} | |||||
private: | |||||
InterProcessLock appLock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultipleInstanceHandler) | |||||
}; | |||||
bool JUCEApplicationBase::sendCommandLineToPreexistingInstance() | |||||
{ | |||||
jassert (multipleInstanceHandler == nullptr); // this must only be called once! | |||||
multipleInstanceHandler = new MultipleInstanceHandler (getApplicationName()); | |||||
return multipleInstanceHandler->sendCommandLineToPreexistingInstance(); | |||||
} | |||||
#else | |||||
struct JUCEApplicationBase::MultipleInstanceHandler {}; | |||||
#endif | |||||
//============================================================================== | |||||
#if JUCE_ANDROID | |||||
StringArray JUCEApplicationBase::getCommandLineParameterArray() { return {}; } | |||||
String JUCEApplicationBase::getCommandLineParameters() { return {}; } | |||||
#else | |||||
#if JUCE_WINDOWS && ! defined (_CONSOLE) | |||||
String JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameters() | |||||
{ | |||||
return CharacterFunctions::findEndOfToken (CharPointer_UTF16 (GetCommandLineW()), | |||||
CharPointer_UTF16 (L" "), | |||||
CharPointer_UTF16 (L"\"")).findEndOfWhitespace(); | |||||
} | |||||
StringArray JUCE_CALLTYPE JUCEApplicationBase::getCommandLineParameterArray() | |||||
{ | |||||
StringArray s; | |||||
int argc = 0; | |||||
if (LPWSTR* const argv = CommandLineToArgvW (GetCommandLineW(), &argc)) | |||||
{ | |||||
s = StringArray (argv + 1, argc - 1); | |||||
LocalFree (argv); | |||||
} | |||||
return s; | |||||
} | |||||
#else | |||||
#if JUCE_IOS | |||||
extern int juce_iOSMain (int argc, const char* argv[], void* classPtr); | |||||
#endif | |||||
#if JUCE_MAC | |||||
extern void initialiseNSApplication(); | |||||
#endif | |||||
#if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) | |||||
extern int juce_gtkWebkitMain (int argc, const char* argv[]); | |||||
#endif | |||||
#if JUCE_WINDOWS | |||||
const char* const* juce_argv = nullptr; | |||||
int juce_argc = 0; | |||||
#else | |||||
extern const char* const* juce_argv; // declared in juce_core | |||||
extern int juce_argc; | |||||
#endif | |||||
String JUCEApplicationBase::getCommandLineParameters() | |||||
{ | |||||
String argString; | |||||
for (int i = 1; i < juce_argc; ++i) | |||||
{ | |||||
String arg (juce_argv[i]); | |||||
if (arg.containsChar (' ') && ! arg.isQuotedString()) | |||||
arg = arg.quoted ('"'); | |||||
argString << arg << ' '; | |||||
} | |||||
return argString.trim(); | |||||
} | |||||
StringArray JUCEApplicationBase::getCommandLineParameterArray() | |||||
{ | |||||
return StringArray (juce_argv + 1, juce_argc - 1); | |||||
} | |||||
int JUCEApplicationBase::main (int argc, const char* argv[]) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
juce_argc = argc; | |||||
juce_argv = argv; | |||||
#if JUCE_MAC | |||||
initialiseNSApplication(); | |||||
#endif | |||||
#if JUCE_LINUX && JUCE_MODULE_AVAILABLE_juce_gui_extra && (! defined(JUCE_WEB_BROWSER) || JUCE_WEB_BROWSER) | |||||
if (argc >= 2 && String (argv[1]) == "--juce-gtkwebkitfork-child") | |||||
return juce_gtkWebkitMain (argc, argv); | |||||
#endif | |||||
#if JUCE_IOS | |||||
return juce_iOSMain (argc, argv, iOSCustomDelegate); | |||||
#else | |||||
return JUCEApplicationBase::main(); | |||||
#endif | |||||
} | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
int JUCEApplicationBase::main() | |||||
{ | |||||
ScopedJuceInitialiser_GUI libraryInitialiser; | |||||
jassert (createInstance != nullptr); | |||||
const ScopedPointer<JUCEApplicationBase> app (createInstance()); | |||||
jassert (app != nullptr); | |||||
if (! app->initialiseApp()) | |||||
return app->shutdownApp(); | |||||
JUCE_TRY | |||||
{ | |||||
// loop until a quit message is received.. | |||||
MessageManager::getInstance()->runDispatchLoop(); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
return app->shutdownApp(); | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
bool JUCEApplicationBase::initialiseApp() | |||||
{ | |||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES | |||||
if ((! moreThanOneInstanceAllowed()) && sendCommandLineToPreexistingInstance()) | |||||
{ | |||||
DBG ("Another instance is running - quitting..."); | |||||
return false; | |||||
} | |||||
#endif | |||||
#if JUCE_WINDOWS && JUCE_STANDALONE_APPLICATION && (! defined (_CONSOLE)) && (! JUCE_MINGW) | |||||
if (AttachConsole (ATTACH_PARENT_PROCESS) != 0) | |||||
{ | |||||
// if we've launched a GUI app from cmd.exe or PowerShell, we need this to enable printf etc. | |||||
// However, only reassign stdout, stderr, stdin if they have not been already opened by | |||||
// a redirect or similar. | |||||
FILE* ignore; | |||||
if (_fileno(stdout) < 0) freopen_s (&ignore, "CONOUT$", "w", stdout); | |||||
if (_fileno(stderr) < 0) freopen_s (&ignore, "CONOUT$", "w", stderr); | |||||
if (_fileno(stdin) < 0) freopen_s (&ignore, "CONIN$", "r", stdin); | |||||
} | |||||
#endif | |||||
// let the app do its setting-up.. | |||||
initialise (getCommandLineParameters()); | |||||
stillInitialising = false; | |||||
if (MessageManager::getInstance()->hasStopMessageBeenSent()) | |||||
return false; | |||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES | |||||
if (multipleInstanceHandler != nullptr) | |||||
MessageManager::getInstance()->registerBroadcastListener (multipleInstanceHandler); | |||||
#endif | |||||
return true; | |||||
} | |||||
int JUCEApplicationBase::shutdownApp() | |||||
{ | |||||
jassert (JUCEApplicationBase::getInstance() == this); | |||||
#if JUCE_HANDLE_MULTIPLE_INSTANCES | |||||
if (multipleInstanceHandler != nullptr) | |||||
MessageManager::getInstance()->deregisterBroadcastListener (multipleInstanceHandler); | |||||
#endif | |||||
JUCE_TRY | |||||
{ | |||||
// give the app a chance to clean up.. | |||||
shutdown(); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
multipleInstanceHandler = nullptr; | |||||
return getApplicationReturnValue(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,312 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Abstract base class for application classes. | |||||
Note that in the juce_gui_basics module, there's a utility class JUCEApplication | |||||
which derives from JUCEApplicationBase, and takes care of a few chores. Most | |||||
of the time you'll want to derive your class from JUCEApplication rather than | |||||
using JUCEApplicationBase directly, but if you're not using the juce_gui_basics | |||||
module then you might need to go straight to this base class. | |||||
Any application that wants to run an event loop must declare a subclass of | |||||
JUCEApplicationBase, and implement its various pure virtual methods. | |||||
It then needs to use the START_JUCE_APPLICATION macro somewhere in a CPP file | |||||
to declare an instance of this class and generate suitable platform-specific | |||||
boilerplate code to launch the app. | |||||
e.g. @code | |||||
class MyJUCEApp : public JUCEApplication | |||||
{ | |||||
public: | |||||
MyJUCEApp() {} | |||||
~MyJUCEApp() {} | |||||
void initialise (const String& commandLine) override | |||||
{ | |||||
myMainWindow = new MyApplicationWindow(); | |||||
myMainWindow->setBounds (100, 100, 400, 500); | |||||
myMainWindow->setVisible (true); | |||||
} | |||||
void shutdown() override | |||||
{ | |||||
myMainWindow = nullptr; | |||||
} | |||||
const String getApplicationName() override | |||||
{ | |||||
return "Super JUCE-o-matic"; | |||||
} | |||||
const String getApplicationVersion() override | |||||
{ | |||||
return "1.0"; | |||||
} | |||||
private: | |||||
ScopedPointer<MyApplicationWindow> myMainWindow; | |||||
}; | |||||
// this generates boilerplate code to launch our app class: | |||||
START_JUCE_APPLICATION (MyJUCEApp) | |||||
@endcode | |||||
@see JUCEApplication, START_JUCE_APPLICATION | |||||
*/ | |||||
class JUCE_API JUCEApplicationBase | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
JUCEApplicationBase(); | |||||
public: | |||||
/** Destructor. */ | |||||
virtual ~JUCEApplicationBase(); | |||||
//============================================================================== | |||||
/** Returns the global instance of the application object that's running. */ | |||||
static JUCEApplicationBase* getInstance() noexcept { return appInstance; } | |||||
//============================================================================== | |||||
/** Returns the application's name. */ | |||||
virtual const String getApplicationName() = 0; | |||||
/** Returns the application's version number. */ | |||||
virtual const String getApplicationVersion() = 0; | |||||
/** Checks whether multiple instances of the app are allowed. | |||||
If you application class returns true for this, more than one instance is | |||||
permitted to run (except on the Mac where this isn't possible). | |||||
If it's false, the second instance won't start, but it you will still get a | |||||
callback to anotherInstanceStarted() to tell you about this - which | |||||
gives you a chance to react to what the user was trying to do. | |||||
*/ | |||||
virtual bool moreThanOneInstanceAllowed() = 0; | |||||
/** Called when the application starts. | |||||
This will be called once to let the application do whatever initialisation | |||||
it needs, create its windows, etc. | |||||
After the method returns, the normal event-dispatch loop will be run, | |||||
until the quit() method is called, at which point the shutdown() | |||||
method will be called to let the application clear up anything it needs | |||||
to delete. | |||||
If during the initialise() method, the application decides not to start-up | |||||
after all, it can just call the quit() method and the event loop won't be run. | |||||
@param commandLineParameters the line passed in does not include the name of | |||||
the executable, just the parameter list. To get the | |||||
parameters as an array, you can call | |||||
JUCEApplication::getCommandLineParameters() | |||||
@see shutdown, quit | |||||
*/ | |||||
virtual void initialise (const String& commandLineParameters) = 0; | |||||
/* Called to allow the application to clear up before exiting. | |||||
After JUCEApplication::quit() has been called, the event-dispatch loop will | |||||
terminate, and this method will get called to allow the app to sort itself | |||||
out. | |||||
Be careful that nothing happens in this method that might rely on messages | |||||
being sent, or any kind of window activity, because the message loop is no | |||||
longer running at this point. | |||||
@see DeletedAtShutdown | |||||
*/ | |||||
virtual void shutdown() = 0; | |||||
/** Indicates that the user has tried to start up another instance of the app. | |||||
This will get called even if moreThanOneInstanceAllowed() is false. | |||||
*/ | |||||
virtual void anotherInstanceStarted (const String& commandLine) = 0; | |||||
/** Called when the operating system is trying to close the application. | |||||
The default implementation of this method is to call quit(), but it may | |||||
be overloaded to ignore the request or do some other special behaviour | |||||
instead. For example, you might want to offer the user the chance to save | |||||
their changes before quitting, and give them the chance to cancel. | |||||
If you want to send a quit signal to your app, this is the correct method | |||||
to call, because it means that requests that come from the system get handled | |||||
in the same way as those from your own application code. So e.g. you'd | |||||
call this method from a "quit" item on a menu bar. | |||||
*/ | |||||
virtual void systemRequestedQuit() = 0; | |||||
/** This method is called when the application is being put into background mode | |||||
by the operating system. | |||||
*/ | |||||
virtual void suspended() = 0; | |||||
/** This method is called when the application is being woken from background mode | |||||
by the operating system. | |||||
*/ | |||||
virtual void resumed() = 0; | |||||
/** If any unhandled exceptions make it through to the message dispatch loop, this | |||||
callback will be triggered, in case you want to log them or do some other | |||||
type of error-handling. | |||||
If the type of exception is derived from the std::exception class, the pointer | |||||
passed-in will be valid. If the exception is of unknown type, this pointer | |||||
will be null. | |||||
*/ | |||||
virtual void unhandledException (const std::exception*, | |||||
const String& sourceFilename, | |||||
int lineNumber) = 0; | |||||
//============================================================================== | |||||
/** Override this method to be informed when the back button is pressed on a device. | |||||
This is currently only implemented on Android devices. | |||||
*/ | |||||
virtual void backButtonPressed() { } | |||||
//============================================================================== | |||||
/** Signals that the main message loop should stop and the application should terminate. | |||||
This isn't synchronous, it just posts a quit message to the main queue, and | |||||
when this message arrives, the message loop will stop, the shutdown() method | |||||
will be called, and the app will exit. | |||||
Note that this will cause an unconditional quit to happen, so if you need an | |||||
extra level before this, e.g. to give the user the chance to save their work | |||||
and maybe cancel the quit, you'll need to handle this in the systemRequestedQuit() | |||||
method - see that method's help for more info. | |||||
@see MessageManager | |||||
*/ | |||||
static void quit(); | |||||
//============================================================================== | |||||
/** Returns the application's command line parameters as a set of strings. | |||||
@see getCommandLineParameters | |||||
*/ | |||||
static StringArray JUCE_CALLTYPE getCommandLineParameterArray(); | |||||
/** Returns the application's command line parameters as a single string. | |||||
@see getCommandLineParameterArray | |||||
*/ | |||||
static String JUCE_CALLTYPE getCommandLineParameters(); | |||||
//============================================================================== | |||||
/** Sets the value that should be returned as the application's exit code when the | |||||
app quits. | |||||
This is the value that's returned by the main() function. Normally you'd leave this | |||||
as 0 unless you want to indicate an error code. | |||||
@see getApplicationReturnValue | |||||
*/ | |||||
void setApplicationReturnValue (int newReturnValue) noexcept; | |||||
/** Returns the value that has been set as the application's exit code. | |||||
@see setApplicationReturnValue | |||||
*/ | |||||
int getApplicationReturnValue() const noexcept { return appReturnValue; } | |||||
//============================================================================== | |||||
/** Returns true if this executable is running as an app (as opposed to being a plugin | |||||
or other kind of shared library. */ | |||||
static bool isStandaloneApp() noexcept { return createInstance != nullptr; } | |||||
/** Returns true if the application hasn't yet completed its initialise() method | |||||
and entered the main event loop. | |||||
This is handy for things like splash screens to know when the app's up-and-running | |||||
properly. | |||||
*/ | |||||
bool isInitialising() const noexcept { return stillInitialising; } | |||||
//============================================================================== | |||||
#ifndef DOXYGEN | |||||
// The following methods are for internal use only... | |||||
static int main(); | |||||
static int main (int argc, const char* argv[]); | |||||
static void appWillTerminateByForce(); | |||||
typedef JUCEApplicationBase* (*CreateInstanceFunction)(); | |||||
static CreateInstanceFunction createInstance; | |||||
#if JUCE_IOS | |||||
static void* iOSCustomDelegate; | |||||
#endif | |||||
virtual bool initialiseApp(); | |||||
int shutdownApp(); | |||||
static void JUCE_CALLTYPE sendUnhandledException (const std::exception*, const char* sourceFile, int lineNumber); | |||||
bool sendCommandLineToPreexistingInstance(); | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
static JUCEApplicationBase* appInstance; | |||||
int appReturnValue; | |||||
bool stillInitialising; | |||||
struct MultipleInstanceHandler; | |||||
friend struct MultipleInstanceHandler; | |||||
friend struct ContainerDeletePolicy<MultipleInstanceHandler>; | |||||
ScopedPointer<MultipleInstanceHandler> multipleInstanceHandler; | |||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplicationBase) | |||||
}; | |||||
//============================================================================== | |||||
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS || defined (DOXYGEN) | |||||
/** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to | |||||
the JUCEApplicationBase::sendUnhandledException() method. | |||||
This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro. | |||||
*/ | |||||
#define JUCE_TRY try | |||||
/** The JUCE_TRY/JUCE_CATCH_EXCEPTION wrappers can be used to pass any uncaught exceptions to | |||||
the JUCEApplicationBase::sendUnhandledException() method. | |||||
This functionality can be enabled with the JUCE_CATCH_UNHANDLED_EXCEPTIONS macro. | |||||
*/ | |||||
#define JUCE_CATCH_EXCEPTION \ | |||||
catch (const std::exception& e) { juce::JUCEApplicationBase::sendUnhandledException (&e, __FILE__, __LINE__); } \ | |||||
catch (...) { juce::JUCEApplicationBase::sendUnhandledException (nullptr, __FILE__, __LINE__); } | |||||
#else | |||||
#define JUCE_TRY | |||||
#define JUCE_CATCH_EXCEPTION | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,72 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A message that invokes a callback method when it gets delivered. | |||||
You can use this class to fire off actions that you want to be performed later | |||||
on the message thread. | |||||
To use it, create a subclass of CallbackMessage which implements the messageCallback() | |||||
method, then call post() to dispatch it. The event thread will then invoke your | |||||
messageCallback() method later on, and will automatically delete the message object | |||||
afterwards. | |||||
Always create a new instance of a CallbackMessage on the heap, as it will be | |||||
deleted automatically after the message has been delivered. | |||||
Note that this class was essential back in the days before C++11, but in modern | |||||
times you may prefer to use MessageManager::callAsync() with a lambda. | |||||
@see MessageManager::callAsync, MessageListener, ActionListener, ChangeListener | |||||
*/ | |||||
class JUCE_API CallbackMessage : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
CallbackMessage() noexcept {} | |||||
/** Destructor. */ | |||||
~CallbackMessage() {} | |||||
//============================================================================== | |||||
/** Called when the message is delivered. | |||||
You should implement this method and make it do whatever action you want | |||||
to perform. | |||||
Note that like all other messages, this object will be deleted immediately | |||||
after this method has been invoked. | |||||
*/ | |||||
virtual void messageCallback() = 0; | |||||
private: | |||||
// Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered | |||||
// messages still in the system event queue. These aren't harmful, but can cause annoying assertions. | |||||
JUCE_DECLARE_NON_COPYABLE (CallbackMessage) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,94 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
static SpinLock deletedAtShutdownLock; // use a spin lock because it can be statically initialised | |||||
static Array<DeletedAtShutdown*>& getDeletedAtShutdownObjects() | |||||
{ | |||||
static Array<DeletedAtShutdown*> objects; | |||||
return objects; | |||||
} | |||||
DeletedAtShutdown::DeletedAtShutdown() | |||||
{ | |||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||||
getDeletedAtShutdownObjects().add (this); | |||||
} | |||||
DeletedAtShutdown::~DeletedAtShutdown() | |||||
{ | |||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||||
getDeletedAtShutdownObjects().removeFirstMatchingValue (this); | |||||
} | |||||
#if JUCE_MSVC | |||||
// Disable unreachable code warning, in case the compiler manages to figure out that | |||||
// you have no classes of DeletedAtShutdown that could throw an exception in their destructor. | |||||
#pragma warning (push) | |||||
#pragma warning (disable: 4702) | |||||
#endif | |||||
void DeletedAtShutdown::deleteAll() | |||||
{ | |||||
// make a local copy of the array, so it can't get into a loop if something | |||||
// creates another DeletedAtShutdown object during its destructor. | |||||
Array<DeletedAtShutdown*> localCopy; | |||||
{ | |||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||||
localCopy = getDeletedAtShutdownObjects(); | |||||
} | |||||
for (int i = localCopy.size(); --i >= 0;) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
auto* deletee = localCopy.getUnchecked(i); | |||||
// double-check that it's not already been deleted during another object's destructor. | |||||
{ | |||||
const SpinLock::ScopedLockType sl (deletedAtShutdownLock); | |||||
if (! getDeletedAtShutdownObjects().contains (deletee)) | |||||
deletee = nullptr; | |||||
} | |||||
delete deletee; | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
// if this fails, then it's likely that some new DeletedAtShutdown objects were | |||||
// created while executing the destructors of the other ones. | |||||
jassert (getDeletedAtShutdownObjects().isEmpty()); | |||||
getDeletedAtShutdownObjects().clear(); // just to make sure the array doesn't have any memory still allocated | |||||
} | |||||
#if JUCE_MSVC | |||||
#pragma warning (pop) | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,63 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Classes derived from this will be automatically deleted when the application exits. | |||||
After JUCEApplicationBase::shutdown() has been called, any objects derived from | |||||
DeletedAtShutdown which are still in existence will be deleted in the reverse | |||||
order to that in which they were created. | |||||
So if you've got a singleton and don't want to have to explicitly delete it, just | |||||
inherit from this and it'll be taken care of. | |||||
*/ | |||||
class JUCE_API DeletedAtShutdown | |||||
{ | |||||
protected: | |||||
/** Creates a DeletedAtShutdown object. */ | |||||
DeletedAtShutdown(); | |||||
/** Destructor. | |||||
It's ok to delete these objects explicitly - it's only the ones left | |||||
dangling at the end that will be deleted automatically. | |||||
*/ | |||||
virtual ~DeletedAtShutdown(); | |||||
public: | |||||
/** Deletes all extant objects. | |||||
This shouldn't be used by applications, as it's called automatically | |||||
in the shutdown code of the JUCEApplicationBase class. | |||||
*/ | |||||
static void deleteAll(); | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (DeletedAtShutdown) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,200 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** Initialises Juce's GUI classes. | |||||
If you're embedding Juce into an application that uses its own event-loop rather | |||||
than using the START_JUCE_APPLICATION macro, call this function before making any | |||||
Juce calls, to make sure things are initialised correctly. | |||||
Note that if you're creating a Juce DLL for Windows, you may also need to call the | |||||
Process::setCurrentModuleInstanceHandle() method. | |||||
@see shutdownJuce_GUI() | |||||
*/ | |||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); | |||||
/** Clears up any static data being used by Juce's GUI classes. | |||||
If you're embedding Juce into an application that uses its own event-loop rather | |||||
than using the START_JUCE_APPLICATION macro, call this function in your shutdown | |||||
code to clean up any juce objects that might be lying around. | |||||
@see initialiseJuce_GUI() | |||||
*/ | |||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); | |||||
//============================================================================== | |||||
/** A utility object that helps you initialise and shutdown Juce correctly | |||||
using an RAII pattern. | |||||
When the first instance of this class is created, it calls initialiseJuce_GUI(), | |||||
and when the last instance is deleted, it calls shutdownJuce_GUI(), so that you | |||||
can easily be sure that as long as at least one instance of the class exists, the | |||||
library will be initialised. | |||||
This class is particularly handy to use at the beginning of a console app's | |||||
main() function, because it'll take care of shutting down whenever you return | |||||
from the main() call. | |||||
Be careful with your threading though - to be safe, you should always make sure | |||||
that these objects are created and deleted on the message thread. | |||||
*/ | |||||
class JUCE_API ScopedJuceInitialiser_GUI | |||||
{ | |||||
public: | |||||
/** The constructor simply calls initialiseJuce_GUI(). */ | |||||
ScopedJuceInitialiser_GUI(); | |||||
/** The destructor simply calls shutdownJuce_GUI(). */ | |||||
~ScopedJuceInitialiser_GUI(); | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
To start a JUCE app, use this macro: START_JUCE_APPLICATION (AppSubClass) where | |||||
AppSubClass is the name of a class derived from JUCEApplication or JUCEApplicationBase. | |||||
See the JUCEApplication and JUCEApplicationBase class documentation for more details. | |||||
*/ | |||||
#ifdef DOXYGEN | |||||
#define START_JUCE_APPLICATION(AppClass) | |||||
#else | |||||
#if JUCE_WINDOWS && ! defined (_CONSOLE) | |||||
#define JUCE_MAIN_FUNCTION int __stdcall WinMain (struct HINSTANCE__*, struct HINSTANCE__*, char*, int) | |||||
#define JUCE_MAIN_FUNCTION_ARGS | |||||
#else | |||||
#define JUCE_MAIN_FUNCTION int main (int argc, char* argv[]) | |||||
#define JUCE_MAIN_FUNCTION_ARGS argc, (const char**) argv | |||||
#endif | |||||
#if JUCE_IOS | |||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \ | |||||
void* juce_GetIOSCustomDelegateClass() { return nullptr; } | |||||
#define JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } \ | |||||
void* juce_GetIOSCustomDelegateClass() { return [DelegateClass class]; } | |||||
#define JUCE_MAIN_FUNCTION_DEFINITION \ | |||||
extern "C" JUCE_MAIN_FUNCTION \ | |||||
{ \ | |||||
juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \ | |||||
juce::JUCEApplicationBase::iOSCustomDelegate = juce_GetIOSCustomDelegateClass(); \ | |||||
return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \ | |||||
} | |||||
#elif JUCE_ANDROID | |||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } | |||||
#define JUCE_MAIN_FUNCTION_DEFINITION | |||||
#else | |||||
#define JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||||
juce::JUCEApplicationBase* juce_CreateApplication() { return new AppClass(); } | |||||
#define JUCE_MAIN_FUNCTION_DEFINITION \ | |||||
extern "C" JUCE_MAIN_FUNCTION \ | |||||
{ \ | |||||
juce::JUCEApplicationBase::createInstance = &juce_CreateApplication; \ | |||||
return juce::JUCEApplicationBase::main (JUCE_MAIN_FUNCTION_ARGS); \ | |||||
} | |||||
#endif | |||||
#if JucePlugin_Build_Standalone | |||||
#if JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP | |||||
#define START_JUCE_APPLICATION(AppClass) JUCE_CREATE_APPLICATION_DEFINE(AppClass) | |||||
#if JUCE_IOS | |||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) | |||||
#endif | |||||
#else | |||||
#define START_JUCE_APPLICATION(AppClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app."); | |||||
#if JUCE_IOS | |||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) static_assert(false, "You are trying to use START_JUCE_APPLICATION in an audio plug-in. Define JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1 if you want to use a custom standalone target app."); | |||||
#endif | |||||
#endif | |||||
#else | |||||
#define START_JUCE_APPLICATION(AppClass) \ | |||||
JUCE_CREATE_APPLICATION_DEFINE(AppClass) \ | |||||
JUCE_MAIN_FUNCTION_DEFINITION | |||||
#if JUCE_IOS | |||||
/** | |||||
You can instruct JUCE to use a custom iOS app delegate class instaed of JUCE's default | |||||
app delegate. For JUCE to work you must pass all messages to JUCE's internal app delegate. | |||||
Below is an example of minimal forwarding custom delegate. Note that you are at your own | |||||
risk if you decide to use your own delegate an subtle, hard to debug bugs may occur. | |||||
@interface MyCustomDelegate : NSObject <UIApplicationDelegate> { NSObject<UIApplicationDelegate>* juceDelegate; } @end | |||||
@implementation MyCustomDelegate | |||||
-(id) init | |||||
{ | |||||
self = [super init]; | |||||
juceDelegate = reinterpret_cast<NSObject<UIApplicationDelegate>*> ([[NSClassFromString (@"JuceAppStartupDelegate") alloc] init]); | |||||
return self; | |||||
} | |||||
-(void)dealloc | |||||
{ | |||||
[juceDelegate release]; | |||||
[super dealloc]; | |||||
} | |||||
- (void)forwardInvocation:(NSInvocation *)anInvocation | |||||
{ | |||||
if (juceDelegate != nullptr && [juceDelegate respondsToSelector:[anInvocation selector]]) | |||||
[anInvocation invokeWithTarget:juceDelegate]; | |||||
else | |||||
[super forwardInvocation:anInvocation]; | |||||
} | |||||
-(BOOL)respondsToSelector:(SEL)aSelector | |||||
{ | |||||
if (juceDelegate != nullptr && [juceDelegate respondsToSelector:aSelector]) | |||||
return YES; | |||||
return [super respondsToSelector:aSelector]; | |||||
} | |||||
@end | |||||
*/ | |||||
#define START_JUCE_APPLICATION_WITH_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||||
JUCE_CREATE_APPLICATION_DEFINE_CUSTOM_DELEGATE(AppClass, DelegateClass) \ | |||||
JUCE_MAIN_FUNCTION_DEFINITION | |||||
#endif | |||||
#endif | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,62 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class MessageListener; | |||||
//============================================================================== | |||||
/** The base class for objects that can be sent to a MessageListener. | |||||
If you want to send a message that carries some kind of custom data, just | |||||
create a subclass of Message with some appropriate member variables to hold | |||||
your data. | |||||
Always create a new instance of a Message object on the heap, as it will be | |||||
deleted automatically after the message has been delivered. | |||||
@see MessageListener, MessageManager, ActionListener, ChangeListener | |||||
*/ | |||||
class JUCE_API Message : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an uninitialised message. */ | |||||
Message() noexcept; | |||||
~Message(); | |||||
typedef ReferenceCountedObjectPtr<Message> Ptr; | |||||
//============================================================================== | |||||
private: | |||||
friend class MessageListener; | |||||
WeakReference<MessageListener> recipient; | |||||
void messageCallback() override; | |||||
// Avoid the leak-detector because for plugins, the host can unload our DLL with undelivered | |||||
// messages still in the system event queue. These aren't harmful, but can cause annoying assertions. | |||||
JUCE_DECLARE_NON_COPYABLE (Message) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,52 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
Message::Message() noexcept {} | |||||
Message::~Message() {} | |||||
void Message::messageCallback() | |||||
{ | |||||
if (MessageListener* const r = recipient) | |||||
r->handleMessage (*this); | |||||
} | |||||
MessageListener::MessageListener() noexcept | |||||
{ | |||||
// Are you trying to create a messagelistener before or after juce has been intialised?? | |||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||||
} | |||||
MessageListener::~MessageListener() | |||||
{ | |||||
masterReference.clear(); | |||||
} | |||||
void MessageListener::postMessage (Message* const message) const | |||||
{ | |||||
message->recipient = const_cast<MessageListener*> (this); | |||||
message->post(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,68 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
MessageListener subclasses can post and receive Message objects. | |||||
@see Message, MessageManager, ActionListener, ChangeListener | |||||
*/ | |||||
class JUCE_API MessageListener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
MessageListener() noexcept; | |||||
/** Destructor. */ | |||||
virtual ~MessageListener(); | |||||
//============================================================================== | |||||
/** This is the callback method that receives incoming messages. | |||||
This is called by the MessageManager from its dispatch loop. | |||||
@see postMessage | |||||
*/ | |||||
virtual void handleMessage (const Message& message) = 0; | |||||
//============================================================================== | |||||
/** Sends a message to the message queue, for asynchronous delivery to this listener | |||||
later on. | |||||
This method can be called safely by any thread. | |||||
@param message the message object to send - this will be deleted | |||||
automatically by the message queue, so make sure it's | |||||
allocated on the heap, not the stack! | |||||
@see handleMessage | |||||
*/ | |||||
void postMessage (Message* message) const; | |||||
private: | |||||
WeakReference<MessageListener>::Master masterReference; | |||||
friend class WeakReference<MessageListener>; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,387 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
MessageManager::MessageManager() noexcept | |||||
: messageThreadId (Thread::getCurrentThreadId()) | |||||
{ | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
Thread::setCurrentThreadName ("Juce Message Thread"); | |||||
} | |||||
MessageManager::~MessageManager() noexcept | |||||
{ | |||||
broadcaster = nullptr; | |||||
doPlatformSpecificShutdown(); | |||||
jassert (instance == this); | |||||
instance = nullptr; // do this last in case this instance is still needed by doPlatformSpecificShutdown() | |||||
} | |||||
MessageManager* MessageManager::instance = nullptr; | |||||
MessageManager* MessageManager::getInstance() | |||||
{ | |||||
if (instance == nullptr) | |||||
{ | |||||
instance = new MessageManager(); | |||||
doPlatformSpecificInitialisation(); | |||||
} | |||||
return instance; | |||||
} | |||||
MessageManager* MessageManager::getInstanceWithoutCreating() noexcept | |||||
{ | |||||
return instance; | |||||
} | |||||
void MessageManager::deleteInstance() | |||||
{ | |||||
deleteAndZero (instance); | |||||
} | |||||
//============================================================================== | |||||
bool MessageManager::MessageBase::post() | |||||
{ | |||||
auto* mm = MessageManager::instance; | |||||
if (mm == nullptr || mm->quitMessagePosted || ! postMessageToSystemQueue (this)) | |||||
{ | |||||
Ptr deleter (this); // (this will delete messages that were just created with a 0 ref count) | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_MODAL_LOOPS_PERMITTED && ! (JUCE_MAC || JUCE_IOS) | |||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||||
{ | |||||
jassert (isThisTheMessageThread()); // must only be called by the message thread | |||||
const int64 endTime = Time::currentTimeMillis() + millisecondsToRunFor; | |||||
while (! quitMessageReceived) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
if (! dispatchNextMessageOnSystemQueue (millisecondsToRunFor >= 0)) | |||||
Thread::sleep (1); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
if (millisecondsToRunFor >= 0 && Time::currentTimeMillis() >= endTime) | |||||
break; | |||||
} | |||||
return ! quitMessageReceived; | |||||
} | |||||
#endif | |||||
#if ! (JUCE_MAC || JUCE_IOS || JUCE_ANDROID) | |||||
class MessageManager::QuitMessage : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
QuitMessage() {} | |||||
void messageCallback() override | |||||
{ | |||||
if (auto* mm = MessageManager::instance) | |||||
mm->quitMessageReceived = true; | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (QuitMessage) | |||||
}; | |||||
void MessageManager::runDispatchLoop() | |||||
{ | |||||
jassert (isThisTheMessageThread()); // must only be called by the message thread | |||||
while (! quitMessageReceived) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
if (! dispatchNextMessageOnSystemQueue (false)) | |||||
Thread::sleep (1); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
} | |||||
void MessageManager::stopDispatchLoop() | |||||
{ | |||||
(new QuitMessage())->post(); | |||||
quitMessagePosted = true; | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
class AsyncFunctionCallback : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
AsyncFunctionCallback (MessageCallbackFunction* const f, void* const param) | |||||
: func (f), parameter (param) | |||||
{} | |||||
void messageCallback() override | |||||
{ | |||||
result = (*func) (parameter); | |||||
finished.signal(); | |||||
} | |||||
WaitableEvent finished; | |||||
void* volatile result = nullptr; | |||||
private: | |||||
MessageCallbackFunction* const func; | |||||
void* const parameter; | |||||
JUCE_DECLARE_NON_COPYABLE (AsyncFunctionCallback) | |||||
}; | |||||
void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* const func, void* const parameter) | |||||
{ | |||||
if (isThisTheMessageThread()) | |||||
return func (parameter); | |||||
// If this thread has the message manager locked, then this will deadlock! | |||||
jassert (! currentThreadHasLockedMessageManager()); | |||||
const ReferenceCountedObjectPtr<AsyncFunctionCallback> message (new AsyncFunctionCallback (func, parameter)); | |||||
if (message->post()) | |||||
{ | |||||
message->finished.wait(); | |||||
return message->result; | |||||
} | |||||
jassertfalse; // the OS message queue failed to send the message! | |||||
return nullptr; | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::deliverBroadcastMessage (const String& value) | |||||
{ | |||||
if (broadcaster != nullptr) | |||||
broadcaster->sendActionMessage (value); | |||||
} | |||||
void MessageManager::registerBroadcastListener (ActionListener* const listener) | |||||
{ | |||||
if (broadcaster == nullptr) | |||||
broadcaster = new ActionBroadcaster(); | |||||
broadcaster->addActionListener (listener); | |||||
} | |||||
void MessageManager::deregisterBroadcastListener (ActionListener* const listener) | |||||
{ | |||||
if (broadcaster != nullptr) | |||||
broadcaster->removeActionListener (listener); | |||||
} | |||||
//============================================================================== | |||||
bool MessageManager::isThisTheMessageThread() const noexcept | |||||
{ | |||||
return Thread::getCurrentThreadId() == messageThreadId; | |||||
} | |||||
void MessageManager::setCurrentThreadAsMessageThread() | |||||
{ | |||||
const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); | |||||
if (messageThreadId != thisThread) | |||||
{ | |||||
messageThreadId = thisThread; | |||||
// This is needed on windows to make sure the message window is created by this thread | |||||
doPlatformSpecificShutdown(); | |||||
doPlatformSpecificInitialisation(); | |||||
} | |||||
} | |||||
bool MessageManager::currentThreadHasLockedMessageManager() const noexcept | |||||
{ | |||||
const Thread::ThreadID thisThread = Thread::getCurrentThreadId(); | |||||
return thisThread == messageThreadId || thisThread == threadWithLock; | |||||
} | |||||
//============================================================================== | |||||
//============================================================================== | |||||
/* The only safe way to lock the message thread while another thread does | |||||
some work is by posting a special message, whose purpose is to tie up the event | |||||
loop until the other thread has finished its business. | |||||
Any other approach can get horribly deadlocked if the OS uses its own hidden locks which | |||||
get locked before making an event callback, because if the same OS lock gets indirectly | |||||
accessed from another thread inside a MM lock, you're screwed. (this is exactly what happens | |||||
in Cocoa). | |||||
*/ | |||||
class MessageManagerLock::BlockingMessage : public MessageManager::MessageBase | |||||
{ | |||||
public: | |||||
BlockingMessage() noexcept {} | |||||
void messageCallback() override | |||||
{ | |||||
lockedEvent.signal(); | |||||
releaseEvent.wait(); | |||||
} | |||||
WaitableEvent lockedEvent, releaseEvent; | |||||
JUCE_DECLARE_NON_COPYABLE (BlockingMessage) | |||||
}; | |||||
//============================================================================== | |||||
MessageManagerLock::MessageManagerLock (Thread* const threadToCheck) | |||||
: blockingMessage(), checker (threadToCheck, nullptr), | |||||
locked (attemptLock (threadToCheck != nullptr ? &checker : nullptr)) | |||||
{ | |||||
} | |||||
MessageManagerLock::MessageManagerLock (ThreadPoolJob* const jobToCheckForExitSignal) | |||||
: blockingMessage(), checker (nullptr, jobToCheckForExitSignal), | |||||
locked (attemptLock (jobToCheckForExitSignal != nullptr ? &checker : nullptr)) | |||||
{ | |||||
} | |||||
MessageManagerLock::MessageManagerLock (BailOutChecker& bailOutChecker) | |||||
: blockingMessage(), checker (nullptr, nullptr), | |||||
locked (attemptLock (&bailOutChecker)) | |||||
{ | |||||
} | |||||
bool MessageManagerLock::attemptLock (BailOutChecker* bailOutChecker) | |||||
{ | |||||
auto* mm = MessageManager::instance; | |||||
if (mm == nullptr) | |||||
return false; | |||||
if (mm->currentThreadHasLockedMessageManager()) | |||||
return true; | |||||
if (bailOutChecker == nullptr) | |||||
{ | |||||
mm->lockingLock.enter(); | |||||
} | |||||
else | |||||
{ | |||||
while (! mm->lockingLock.tryEnter()) | |||||
{ | |||||
if (bailOutChecker->shouldAbortAcquiringLock()) | |||||
return false; | |||||
Thread::yield(); | |||||
} | |||||
} | |||||
blockingMessage = new BlockingMessage(); | |||||
if (! blockingMessage->post()) | |||||
{ | |||||
blockingMessage = nullptr; | |||||
return false; | |||||
} | |||||
while (! blockingMessage->lockedEvent.wait (20)) | |||||
{ | |||||
if (bailOutChecker != nullptr && bailOutChecker->shouldAbortAcquiringLock()) | |||||
{ | |||||
blockingMessage->releaseEvent.signal(); | |||||
blockingMessage = nullptr; | |||||
mm->lockingLock.exit(); | |||||
return false; | |||||
} | |||||
} | |||||
jassert (mm->threadWithLock == 0); | |||||
mm->threadWithLock = Thread::getCurrentThreadId(); | |||||
return true; | |||||
} | |||||
MessageManagerLock::~MessageManagerLock() noexcept | |||||
{ | |||||
if (blockingMessage != nullptr) | |||||
{ | |||||
auto* mm = MessageManager::instance; | |||||
jassert (mm == nullptr || mm->currentThreadHasLockedMessageManager()); | |||||
blockingMessage->releaseEvent.signal(); | |||||
blockingMessage = nullptr; | |||||
if (mm != nullptr) | |||||
{ | |||||
mm->threadWithLock = 0; | |||||
mm->lockingLock.exit(); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
MessageManagerLock::ThreadChecker::ThreadChecker (Thread* const threadToUse, | |||||
ThreadPoolJob* const threadJobToUse) | |||||
: threadToCheck (threadToUse), job (threadJobToUse) | |||||
{ | |||||
} | |||||
bool MessageManagerLock::ThreadChecker::shouldAbortAcquiringLock() | |||||
{ | |||||
return (threadToCheck != nullptr && threadToCheck->threadShouldExit()) | |||||
|| (job != nullptr && job->shouldExit()); | |||||
} | |||||
//============================================================================== | |||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI(); | |||||
JUCE_API void JUCE_CALLTYPE initialiseJuce_GUI() | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
MessageManager::getInstance(); | |||||
} | |||||
} | |||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI(); | |||||
JUCE_API void JUCE_CALLTYPE shutdownJuce_GUI() | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
DeletedAtShutdown::deleteAll(); | |||||
MessageManager::deleteInstance(); | |||||
} | |||||
} | |||||
static int numScopedInitInstances = 0; | |||||
ScopedJuceInitialiser_GUI::ScopedJuceInitialiser_GUI() { if (numScopedInitInstances++ == 0) initialiseJuce_GUI(); } | |||||
ScopedJuceInitialiser_GUI::~ScopedJuceInitialiser_GUI() { if (--numScopedInitInstances == 0) shutdownJuce_GUI(); } | |||||
} // namespace juce |
@@ -0,0 +1,381 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class MessageManagerLock; | |||||
class ThreadPoolJob; | |||||
class ActionListener; | |||||
class ActionBroadcaster; | |||||
//============================================================================== | |||||
#if JUCE_MODULE_AVAILABLE_juce_opengl | |||||
class OpenGLContext; | |||||
#endif | |||||
//============================================================================== | |||||
/** See MessageManager::callFunctionOnMessageThread() for use of this function type. */ | |||||
typedef void* (MessageCallbackFunction) (void* userData); | |||||
//============================================================================== | |||||
/** | |||||
This class is in charge of the application's event-dispatch loop. | |||||
@see Message, CallbackMessage, MessageManagerLock, JUCEApplication, JUCEApplicationBase | |||||
*/ | |||||
class JUCE_API MessageManager | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Returns the global instance of the MessageManager. */ | |||||
static MessageManager* getInstance(); | |||||
/** Returns the global instance of the MessageManager, or nullptr if it doesn't exist. */ | |||||
static MessageManager* getInstanceWithoutCreating() noexcept; | |||||
/** Deletes the global MessageManager instance. | |||||
Does nothing if no instance had been created. | |||||
*/ | |||||
static void deleteInstance(); | |||||
//============================================================================== | |||||
/** Runs the event dispatch loop until a stop message is posted. | |||||
This method is only intended to be run by the application's startup routine, | |||||
as it blocks, and will only return after the stopDispatchLoop() method has been used. | |||||
@see stopDispatchLoop | |||||
*/ | |||||
void runDispatchLoop(); | |||||
/** Sends a signal that the dispatch loop should terminate. | |||||
After this is called, the runDispatchLoop() or runDispatchLoopUntil() methods | |||||
will be interrupted and will return. | |||||
@see runDispatchLoop | |||||
*/ | |||||
void stopDispatchLoop(); | |||||
/** Returns true if the stopDispatchLoop() method has been called. | |||||
*/ | |||||
bool hasStopMessageBeenSent() const noexcept { return quitMessagePosted; } | |||||
#if JUCE_MODAL_LOOPS_PERMITTED || DOXYGEN | |||||
/** Synchronously dispatches messages until a given time has elapsed. | |||||
Returns false if a quit message has been posted by a call to stopDispatchLoop(), | |||||
otherwise returns true. | |||||
*/ | |||||
bool runDispatchLoopUntil (int millisecondsToRunFor); | |||||
#endif | |||||
//============================================================================== | |||||
/** Asynchronously invokes a function or C++11 lambda on the message thread. */ | |||||
template <typename FunctionType> | |||||
static void callAsync (FunctionType functionToCall) | |||||
{ | |||||
new AsyncCallInvoker<FunctionType> (functionToCall); | |||||
} | |||||
/** Calls a function using the message-thread. | |||||
This can be used by any thread to cause this function to be called-back | |||||
by the message thread. If it's the message-thread that's calling this method, | |||||
then the function will just be called; if another thread is calling, a message | |||||
will be posted to the queue, and this method will block until that message | |||||
is delivered, the function is called, and the result is returned. | |||||
Be careful not to cause any deadlocks with this! It's easy to do - e.g. if the caller | |||||
thread has a critical section locked, which an unrelated message callback then tries to lock | |||||
before the message thread gets round to processing this callback. | |||||
@param callback the function to call - its signature must be @code | |||||
void* myCallbackFunction (void*) @endcode | |||||
@param userData a user-defined pointer that will be passed to the function that gets called | |||||
@returns the value that the callback function returns. | |||||
@see MessageManagerLock | |||||
*/ | |||||
void* callFunctionOnMessageThread (MessageCallbackFunction* callback, void* userData); | |||||
/** Returns true if the caller-thread is the message thread. */ | |||||
bool isThisTheMessageThread() const noexcept; | |||||
/** Called to tell the manager that the current thread is the one that's running the dispatch loop. | |||||
(Best to ignore this method unless you really know what you're doing..) | |||||
@see getCurrentMessageThread | |||||
*/ | |||||
void setCurrentThreadAsMessageThread(); | |||||
/** Returns the ID of the current message thread, as set by setCurrentThreadAsMessageThread(). | |||||
(Best to ignore this method unless you really know what you're doing..) | |||||
@see setCurrentThreadAsMessageThread | |||||
*/ | |||||
Thread::ThreadID getCurrentMessageThread() const noexcept { return messageThreadId; } | |||||
/** Returns true if the caller thread has currently got the message manager locked. | |||||
see the MessageManagerLock class for more info about this. | |||||
This will be true if the caller is the message thread, because that automatically | |||||
gains a lock while a message is being dispatched. | |||||
*/ | |||||
bool currentThreadHasLockedMessageManager() const noexcept; | |||||
//============================================================================== | |||||
/** Sends a message to all other JUCE applications that are running. | |||||
@param messageText the string that will be passed to the actionListenerCallback() | |||||
method of the broadcast listeners in the other app. | |||||
@see registerBroadcastListener, ActionListener | |||||
*/ | |||||
static void broadcastMessage (const String& messageText); | |||||
/** Registers a listener to get told about broadcast messages. | |||||
The actionListenerCallback() callback's string parameter | |||||
is the message passed into broadcastMessage(). | |||||
@see broadcastMessage | |||||
*/ | |||||
void registerBroadcastListener (ActionListener* listener); | |||||
/** Deregisters a broadcast listener. */ | |||||
void deregisterBroadcastListener (ActionListener* listener); | |||||
//============================================================================== | |||||
/** Internal class used as the base class for all message objects. | |||||
You shouldn't need to use this directly - see the CallbackMessage or Message | |||||
classes instead. | |||||
*/ | |||||
class JUCE_API MessageBase : public ReferenceCountedObject | |||||
{ | |||||
public: | |||||
MessageBase() noexcept {} | |||||
virtual ~MessageBase() {} | |||||
virtual void messageCallback() = 0; | |||||
bool post(); | |||||
typedef ReferenceCountedObjectPtr<MessageBase> Ptr; | |||||
JUCE_DECLARE_NON_COPYABLE (MessageBase) | |||||
}; | |||||
//============================================================================== | |||||
#ifndef DOXYGEN | |||||
// Internal methods - do not use! | |||||
void deliverBroadcastMessage (const String&); | |||||
~MessageManager() noexcept; | |||||
static bool dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages); | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
MessageManager() noexcept; | |||||
static MessageManager* instance; | |||||
friend class MessageBase; | |||||
class QuitMessage; | |||||
friend class QuitMessage; | |||||
friend class MessageManagerLock; | |||||
ScopedPointer<ActionBroadcaster> broadcaster; | |||||
bool quitMessagePosted = false, quitMessageReceived = false; | |||||
Thread::ThreadID messageThreadId; | |||||
Thread::ThreadID volatile threadWithLock = {}; | |||||
CriticalSection lockingLock; | |||||
static bool postMessageToSystemQueue (MessageBase*); | |||||
static void* exitModalLoopCallback (void*); | |||||
static void doPlatformSpecificInitialisation(); | |||||
static void doPlatformSpecificShutdown(); | |||||
template <typename FunctionType> | |||||
struct AsyncCallInvoker : public MessageBase | |||||
{ | |||||
AsyncCallInvoker (FunctionType f) : callback (f) { post(); } | |||||
void messageCallback() override { callback(); } | |||||
FunctionType callback; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncCallInvoker) | |||||
}; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageManager) | |||||
}; | |||||
//============================================================================== | |||||
/** Used to make sure that the calling thread has exclusive access to the message loop. | |||||
Because it's not thread-safe to call any of the Component or other UI classes | |||||
from threads other than the message thread, one of these objects can be used to | |||||
lock the message loop and allow this to be done. The message thread will be | |||||
suspended for the lifetime of the MessageManagerLock object, so create one on | |||||
the stack like this: @code | |||||
void MyThread::run() | |||||
{ | |||||
someData = 1234; | |||||
const MessageManagerLock mmLock; | |||||
// the event loop will now be locked so it's safe to make a few calls.. | |||||
myComponent->setBounds (newBounds); | |||||
myComponent->repaint(); | |||||
// ..the event loop will now be unlocked as the MessageManagerLock goes out of scope | |||||
} | |||||
@endcode | |||||
Obviously be careful not to create one of these and leave it lying around, or | |||||
your app will grind to a halt! | |||||
MessageManagerLocks are re-entrant, so can be safely nested if the current thread | |||||
already has the lock. | |||||
Another caveat is that using this in conjunction with other CriticalSections | |||||
can create lots of interesting ways of producing a deadlock! In particular, if | |||||
your message thread calls stopThread() for a thread that uses these locks, | |||||
you'll get an (occasional) deadlock.. | |||||
@see MessageManager, MessageManager::currentThreadHasLockedMessageManager | |||||
*/ | |||||
class JUCE_API MessageManagerLock | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Tries to acquire a lock on the message manager. | |||||
The constructor attempts to gain a lock on the message loop, and the lock will be | |||||
kept for the lifetime of this object. | |||||
Optionally, you can pass a thread object here, and while waiting to obtain the lock, | |||||
this method will keep checking whether the thread has been given the | |||||
Thread::signalThreadShouldExit() signal. If this happens, then it will return | |||||
without gaining the lock. If you pass a thread, you must check whether the lock was | |||||
successful by calling lockWasGained(). If this is false, your thread is being told to | |||||
die, so you should take evasive action. | |||||
If you pass nullptr for the thread object, it will wait indefinitely for the lock - be | |||||
careful when doing this, because it's very easy to deadlock if your message thread | |||||
attempts to call stopThread() on a thread just as that thread attempts to get the | |||||
message lock. | |||||
If the calling thread already has the lock, nothing will be done, so it's safe and | |||||
quick to use these locks recursively. | |||||
E.g. | |||||
@code | |||||
void run() | |||||
{ | |||||
... | |||||
while (! threadShouldExit()) | |||||
{ | |||||
MessageManagerLock mml (Thread::getCurrentThread()); | |||||
if (! mml.lockWasGained()) | |||||
return; // another thread is trying to kill us! | |||||
..do some locked stuff here.. | |||||
} | |||||
..and now the MM is now unlocked.. | |||||
} | |||||
@endcode | |||||
*/ | |||||
MessageManagerLock (Thread* threadToCheckForExitSignal = nullptr); | |||||
//============================================================================== | |||||
/** This has the same behaviour as the other constructor, but takes a ThreadPoolJob | |||||
instead of a thread. | |||||
See the MessageManagerLock (Thread*) constructor for details on how this works. | |||||
*/ | |||||
MessageManagerLock (ThreadPoolJob* jobToCheckForExitSignal); | |||||
//============================================================================== | |||||
struct BailOutChecker | |||||
{ | |||||
virtual ~BailOutChecker() {} | |||||
/** Return true if acquiring the lock should be aborted. */ | |||||
virtual bool shouldAbortAcquiringLock() = 0; | |||||
}; | |||||
/** This is an abstraction of the other constructors. You can pass this constructor | |||||
a functor which is periodically checked if attempting the lock should be aborted. | |||||
See the MessageManagerLock (Thread*) constructor for details on how this works. | |||||
*/ | |||||
MessageManagerLock (BailOutChecker&); | |||||
//============================================================================== | |||||
/** Releases the current thread's lock on the message manager. | |||||
Make sure this object is created and deleted by the same thread, | |||||
otherwise there are no guarantees what will happen! | |||||
*/ | |||||
~MessageManagerLock() noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the lock was successfully acquired. | |||||
(See the constructor that takes a Thread for more info). | |||||
*/ | |||||
bool lockWasGained() const noexcept { return locked; } | |||||
private: | |||||
class BlockingMessage; | |||||
friend class ReferenceCountedObjectPtr<BlockingMessage>; | |||||
ReferenceCountedObjectPtr<BlockingMessage> blockingMessage; | |||||
struct ThreadChecker : BailOutChecker | |||||
{ | |||||
ThreadChecker (Thread* const, ThreadPoolJob* const); | |||||
// Required to supress VS2013 compiler warnings | |||||
ThreadChecker& operator= (const ThreadChecker&) = delete; | |||||
bool shouldAbortAcquiringLock() override; | |||||
Thread* const threadToCheck; | |||||
ThreadPoolJob* const job; | |||||
}; | |||||
//============================================================================== | |||||
ThreadChecker checker; | |||||
bool locked; | |||||
//============================================================================== | |||||
bool attemptLock (BailOutChecker*); | |||||
JUCE_DECLARE_NON_COPYABLE (MessageManagerLock) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#if JUCE_MAC || JUCE_WINDOWS || defined (DOXYGEN) | |||||
//============================================================================== | |||||
/** | |||||
An instance of this class will provide callbacks when drives are | |||||
mounted or unmounted on the system. | |||||
Just inherit from this class and implement the pure virtual method | |||||
to get the callbacks, there's no need to do anything else. | |||||
@see File::findFileSystemRoots() | |||||
*/ | |||||
class JUCE_API MountedVolumeListChangeDetector | |||||
{ | |||||
public: | |||||
MountedVolumeListChangeDetector(); | |||||
virtual ~MountedVolumeListChangeDetector(); | |||||
/** This method is called when a volume is mounted or unmounted. */ | |||||
virtual void mountedVolumeListChanged() = 0; | |||||
private: | |||||
JUCE_PUBLIC_IN_DLL_BUILD (struct Pimpl) | |||||
friend struct ContainerDeletePolicy<Pimpl>; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MountedVolumeListChangeDetector) | |||||
}; | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,39 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
These enums are used in various classes to indicate whether a notification | |||||
event should be sent out. | |||||
*/ | |||||
enum NotificationType | |||||
{ | |||||
dontSendNotification = 0, /**< No notification message should be sent. */ | |||||
sendNotification = 1, /**< Requests a notification message, either synchronous or not. */ | |||||
sendNotificationSync, /**< Requests a synchronous notification. */ | |||||
sendNotificationAsync, /**< Requests an asynchronous notification. */ | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,147 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (constructor, "<init>", "()V") \ | |||||
METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \ | |||||
DECLARE_JNI_CLASS (JNIHandler, "android/os/Handler"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
namespace Android | |||||
{ | |||||
class Runnable : public juce::AndroidInterfaceImplementer | |||||
{ | |||||
public: | |||||
virtual void run() = 0; | |||||
private: | |||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||||
{ | |||||
auto* env = getEnv(); | |||||
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, Method.getName)); | |||||
if (methodName == "run") | |||||
{ | |||||
run(); | |||||
return nullptr; | |||||
} | |||||
// invoke base class | |||||
return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||||
} | |||||
}; | |||||
struct Handler | |||||
{ | |||||
juce_DeclareSingleton (Handler, false) | |||||
Handler() : nativeHandler (getEnv()->NewObject (JNIHandler, JNIHandler.constructor)) {} | |||||
bool post (Runnable* runnable) | |||||
{ | |||||
return (getEnv()->CallBooleanMethod (nativeHandler.get(), JNIHandler.post, | |||||
CreateJavaInterface (runnable, "java/lang/Runnable").get()) != 0); | |||||
} | |||||
GlobalRef nativeHandler; | |||||
}; | |||||
juce_ImplementSingleton (Handler); | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::doPlatformSpecificInitialisation() { Android::Handler::getInstance(); } | |||||
void MessageManager::doPlatformSpecificShutdown() {} | |||||
//============================================================================== | |||||
bool MessageManager::dispatchNextMessageOnSystemQueue (const bool) | |||||
{ | |||||
Logger::outputDebugString ("*** Modal loops are not possible in Android!! Exiting..."); | |||||
exit (1); | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
struct AndroidMessageCallback : public Android::Runnable | |||||
{ | |||||
AndroidMessageCallback (const MessageManager::MessageBase::Ptr& messageToDeliver) | |||||
: message (messageToDeliver) | |||||
{} | |||||
AndroidMessageCallback (MessageManager::MessageBase::Ptr && messageToDeliver) | |||||
: message (static_cast<MessageManager::MessageBase::Ptr&&> (messageToDeliver)) | |||||
{} | |||||
void run() override | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
message->messageCallback(); | |||||
// delete the message already here as Java will only run the | |||||
// destructor of this runnable the next time the garbage | |||||
// collector kicks in. | |||||
message = nullptr; | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
MessageManager::MessageBase::Ptr message; | |||||
}; | |||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
{ | |||||
return Android::Handler::getInstance()->post (new AndroidMessageCallback (message)); | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::broadcastMessage (const String&) | |||||
{ | |||||
} | |||||
void MessageManager::runDispatchLoop() | |||||
{ | |||||
} | |||||
void MessageManager::stopDispatchLoop() | |||||
{ | |||||
struct QuitCallback : public CallbackMessage | |||||
{ | |||||
QuitCallback() {} | |||||
void messageCallback() override | |||||
{ | |||||
android.activity.callVoidMethod (JuceAppActivity.finish); | |||||
} | |||||
}; | |||||
(new QuitCallback())->post(); | |||||
quitMessagePosted = true; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,103 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
void MessageManager::runDispatchLoop() | |||||
{ | |||||
jassert (isThisTheMessageThread()); // must only be called by the message thread | |||||
while (! quitMessagePosted) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||||
beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]]; | |||||
} | |||||
} | |||||
} | |||||
void MessageManager::stopDispatchLoop() | |||||
{ | |||||
if (! SystemStats::isRunningInAppExtensionSandbox()) | |||||
[[[UIApplication sharedApplication] delegate] applicationWillTerminate: [UIApplication sharedApplication]]; | |||||
exit (0); // iOS apps get no mercy.. | |||||
} | |||||
#if JUCE_MODAL_LOOPS_PERMITTED | |||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
jassert (isThisTheMessageThread()); // must only be called by the message thread | |||||
uint32 startTime = Time::getMillisecondCounter(); | |||||
NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; | |||||
while (! quitMessagePosted) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode | |||||
beforeDate: endDate]; | |||||
if (millisecondsToRunFor >= 0 | |||||
&& Time::getMillisecondCounter() >= startTime + (uint32) millisecondsToRunFor) | |||||
break; | |||||
} | |||||
} | |||||
return ! quitMessagePosted; | |||||
} | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
static ScopedPointer<MessageQueue> messageQueue; | |||||
void MessageManager::doPlatformSpecificInitialisation() | |||||
{ | |||||
if (messageQueue == nullptr) | |||||
messageQueue = new MessageQueue(); | |||||
} | |||||
void MessageManager::doPlatformSpecificShutdown() | |||||
{ | |||||
messageQueue = nullptr; | |||||
} | |||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
{ | |||||
if (messageQueue != nullptr) | |||||
messageQueue->post (message); | |||||
return true; | |||||
} | |||||
void MessageManager::broadcastMessage (const String&) | |||||
{ | |||||
// N/A on current iOS | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,55 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace LinuxEventLoop | |||||
{ | |||||
struct CallbackFunctionBase | |||||
{ | |||||
virtual ~CallbackFunctionBase() {} | |||||
virtual bool operator()(int fd) = 0; | |||||
bool active = true; | |||||
}; | |||||
template <typename FdCallbackFunction> | |||||
struct CallbackFunction : public CallbackFunctionBase | |||||
{ | |||||
FdCallbackFunction callback; | |||||
CallbackFunction (FdCallbackFunction c) : callback (c) {} | |||||
bool operator() (int fd) override { return callback (fd); } | |||||
}; | |||||
template <typename FdCallbackFunction> | |||||
void setWindowSystemFd (int fd, FdCallbackFunction readCallback) | |||||
{ | |||||
setWindowSystemFdInternal (fd, new CallbackFunction<FdCallbackFunction> (readCallback)); | |||||
} | |||||
void removeWindowSystemFd() noexcept; | |||||
void setWindowSystemFdInternal (int fd, CallbackFunctionBase* readCallback) noexcept; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,265 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
#include <poll.h> | |||||
enum FdType | |||||
{ | |||||
INTERNAL_QUEUE_FD, | |||||
WINDOW_SYSTEM_FD, | |||||
FD_COUNT, | |||||
}; | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
class InternalMessageQueue | |||||
{ | |||||
public: | |||||
InternalMessageQueue() | |||||
{ | |||||
auto ret = ::socketpair (AF_LOCAL, SOCK_STREAM, 0, fd); | |||||
ignoreUnused (ret); jassert (ret == 0); | |||||
auto internalQueueCb = [this] (int _fd) | |||||
{ | |||||
if (const MessageManager::MessageBase::Ptr msg = this->popNextMessage (_fd)) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
msg->messageCallback(); | |||||
return true; | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
return false; | |||||
}; | |||||
pfds[INTERNAL_QUEUE_FD].fd = getReadHandle(); | |||||
pfds[INTERNAL_QUEUE_FD].events = POLLIN; | |||||
readCallback[INTERNAL_QUEUE_FD] = new LinuxEventLoop::CallbackFunction<decltype(internalQueueCb)> (internalQueueCb); | |||||
} | |||||
~InternalMessageQueue() | |||||
{ | |||||
close (getReadHandle()); | |||||
close (getWriteHandle()); | |||||
clearSingletonInstance(); | |||||
} | |||||
//============================================================================== | |||||
void postMessage (MessageManager::MessageBase* const msg) noexcept | |||||
{ | |||||
ScopedLock sl (lock); | |||||
queue.add (msg); | |||||
const int maxBytesInSocketQueue = 128; | |||||
if (bytesInSocket < maxBytesInSocketQueue) | |||||
{ | |||||
bytesInSocket++; | |||||
ScopedUnlock ul (lock); | |||||
const unsigned char x = 0xff; | |||||
ssize_t bytesWritten = write (getWriteHandle(), &x, 1); | |||||
ignoreUnused (bytesWritten); | |||||
} | |||||
} | |||||
void setWindowSystemFd (int _fd, LinuxEventLoop::CallbackFunctionBase* _readCallback) | |||||
{ | |||||
jassert (fdCount == 1); | |||||
ScopedLock sl (lock); | |||||
fdCount = 2; | |||||
pfds[WINDOW_SYSTEM_FD].fd = _fd; | |||||
pfds[WINDOW_SYSTEM_FD].events = POLLIN; | |||||
readCallback[WINDOW_SYSTEM_FD] = _readCallback; | |||||
readCallback[WINDOW_SYSTEM_FD]->active = true; | |||||
} | |||||
void removeWindowSystemFd() | |||||
{ | |||||
jassert (fdCount == FD_COUNT); | |||||
ScopedLock sl (lock); | |||||
fdCount = 1; | |||||
readCallback[WINDOW_SYSTEM_FD]->active = false; | |||||
} | |||||
bool dispatchNextEvent() noexcept | |||||
{ | |||||
for (int counter = 0; counter < fdCount; counter++) | |||||
{ | |||||
const int i = loopCount++; | |||||
loopCount %= fdCount; | |||||
if (readCallback[i] != nullptr && readCallback[i]->active) | |||||
if ((*readCallback[i]) (pfds[i].fd)) | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
bool sleepUntilEvent (const int timeoutMs) | |||||
{ | |||||
const int pnum = poll (pfds, static_cast<nfds_t> (fdCount), timeoutMs); | |||||
return (pnum > 0); | |||||
} | |||||
//============================================================================== | |||||
juce_DeclareSingleton_SingleThreaded_Minimal (InternalMessageQueue) | |||||
private: | |||||
CriticalSection lock; | |||||
ReferenceCountedArray <MessageManager::MessageBase> queue; | |||||
int fd[2]; | |||||
pollfd pfds[FD_COUNT]; | |||||
ScopedPointer<LinuxEventLoop::CallbackFunctionBase> readCallback[FD_COUNT]; | |||||
int fdCount = 1; | |||||
int loopCount = 0; | |||||
int bytesInSocket = 0; | |||||
int getWriteHandle() const noexcept { return fd[0]; } | |||||
int getReadHandle() const noexcept { return fd[1]; } | |||||
MessageManager::MessageBase::Ptr popNextMessage (int _fd) noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (bytesInSocket > 0) | |||||
{ | |||||
--bytesInSocket; | |||||
const ScopedUnlock ul (lock); | |||||
unsigned char x; | |||||
ssize_t numBytes = read (_fd, &x, 1); | |||||
ignoreUnused (numBytes); | |||||
} | |||||
return queue.removeAndReturn (0); | |||||
} | |||||
}; | |||||
juce_ImplementSingleton_SingleThreaded (InternalMessageQueue) | |||||
//============================================================================== | |||||
namespace LinuxErrorHandling | |||||
{ | |||||
static bool keyboardBreakOccurred = false; | |||||
//============================================================================== | |||||
void keyboardBreakSignalHandler (int sig) | |||||
{ | |||||
if (sig == SIGINT) | |||||
keyboardBreakOccurred = true; | |||||
} | |||||
void installKeyboardBreakHandler() | |||||
{ | |||||
struct sigaction saction; | |||||
sigset_t maskSet; | |||||
sigemptyset (&maskSet); | |||||
saction.sa_handler = keyboardBreakSignalHandler; | |||||
saction.sa_mask = maskSet; | |||||
saction.sa_flags = 0; | |||||
sigaction (SIGINT, &saction, 0); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::doPlatformSpecificInitialisation() | |||||
{ | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
LinuxErrorHandling::installKeyboardBreakHandler(); | |||||
// Create the internal message queue | |||||
auto* queue = InternalMessageQueue::getInstance(); | |||||
ignoreUnused (queue); | |||||
} | |||||
void MessageManager::doPlatformSpecificShutdown() | |||||
{ | |||||
InternalMessageQueue::deleteInstance(); | |||||
} | |||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
{ | |||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
{ | |||||
queue->postMessage (message); | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
void MessageManager::broadcastMessage (const String&) | |||||
{ | |||||
// TODO | |||||
} | |||||
// this function expects that it will NEVER be called simultaneously for two concurrent threads | |||||
bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) | |||||
{ | |||||
for (;;) | |||||
{ | |||||
if (LinuxErrorHandling::keyboardBreakOccurred) | |||||
JUCEApplicationBase::getInstance()->quit(); | |||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
{ | |||||
if (queue->dispatchNextEvent()) | |||||
break; | |||||
if (returnIfNoPendingMessages) | |||||
return false; | |||||
// wait for 2000ms for next events if necessary | |||||
queue->sleepUntilEvent (2000); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
void LinuxEventLoop::setWindowSystemFdInternal (int fd, LinuxEventLoop::CallbackFunctionBase* readCallback) noexcept | |||||
{ | |||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
queue->setWindowSystemFd (fd, readCallback); | |||||
} | |||||
void LinuxEventLoop::removeWindowSystemFd() noexcept | |||||
{ | |||||
if (auto* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||||
queue->removeWindowSystemFd(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,430 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
typedef void (*AppFocusChangeCallback)(); | |||||
AppFocusChangeCallback appFocusChangeCallback = nullptr; | |||||
typedef bool (*CheckEventBlockedByModalComps) (NSEvent*); | |||||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||||
typedef void (*MenuTrackingChangedCallback)(bool); | |||||
MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; | |||||
//============================================================================== | |||||
struct AppDelegate | |||||
{ | |||||
public: | |||||
AppDelegate() | |||||
{ | |||||
static AppDelegateClass cls; | |||||
delegate = [cls.createInstance() init]; | |||||
NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; | |||||
[center addObserver: delegate selector: @selector (mainMenuTrackingBegan:) | |||||
name: NSMenuDidBeginTrackingNotification object: nil]; | |||||
[center addObserver: delegate selector: @selector (mainMenuTrackingEnded:) | |||||
name: NSMenuDidEndTrackingNotification object: nil]; | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
{ | |||||
[NSApp setDelegate: delegate]; | |||||
[[NSDistributedNotificationCenter defaultCenter] addObserver: delegate | |||||
selector: @selector (broadcastMessageCallback:) | |||||
name: getBroadcastEventName() | |||||
object: nil | |||||
suspensionBehavior: NSNotificationSuspensionBehaviorDeliverImmediately]; | |||||
} | |||||
else | |||||
{ | |||||
[center addObserver: delegate selector: @selector (applicationDidResignActive:) | |||||
name: NSApplicationDidResignActiveNotification object: NSApp]; | |||||
[center addObserver: delegate selector: @selector (applicationDidBecomeActive:) | |||||
name: NSApplicationDidBecomeActiveNotification object: NSApp]; | |||||
[center addObserver: delegate selector: @selector (applicationWillUnhide:) | |||||
name: NSApplicationWillUnhideNotification object: NSApp]; | |||||
} | |||||
} | |||||
~AppDelegate() | |||||
{ | |||||
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate]; | |||||
[[NSNotificationCenter defaultCenter] removeObserver: delegate]; | |||||
if (JUCEApplicationBase::isStandaloneApp()) | |||||
{ | |||||
[NSApp setDelegate: nil]; | |||||
[[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate | |||||
name: getBroadcastEventName() | |||||
object: nil]; | |||||
} | |||||
[delegate release]; | |||||
} | |||||
static NSString* getBroadcastEventName() | |||||
{ | |||||
return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64())); | |||||
} | |||||
MessageQueue messageQueue; | |||||
id delegate; | |||||
private: | |||||
//============================================================================== | |||||
struct AppDelegateClass : public ObjCClass<NSObject> | |||||
{ | |||||
AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||||
{ | |||||
addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@@"); | |||||
addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); | |||||
addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); | |||||
addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); | |||||
addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); | |||||
addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); | |||||
addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); | |||||
addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); | |||||
addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); | |||||
addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); | |||||
addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); | |||||
addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); | |||||
addMethod (@selector (dummyMethod), dummyMethod, "v@:"); | |||||
registerClass(); | |||||
} | |||||
private: | |||||
static void applicationWillFinishLaunching (id self, SEL, NSApplication*, NSNotification*) | |||||
{ | |||||
[[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||||
andSelector: @selector (getUrl:withReplyEvent:) | |||||
forEventClass: kInternetEventClass | |||||
andEventID: kAEGetURL]; | |||||
} | |||||
static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | |||||
{ | |||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
{ | |||||
app->systemRequestedQuit(); | |||||
if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||||
return NSTerminateCancel; | |||||
} | |||||
return NSTerminateNow; | |||||
} | |||||
static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) | |||||
{ | |||||
JUCEApplicationBase::appWillTerminateByForce(); | |||||
} | |||||
static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) | |||||
{ | |||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
{ | |||||
app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||||
return YES; | |||||
} | |||||
return NO; | |||||
} | |||||
static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||||
{ | |||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
{ | |||||
StringArray files; | |||||
for (NSString* f in filenames) | |||||
files.add (quotedIfContainsSpaces (f)); | |||||
if (files.size() > 0) | |||||
app->anotherInstanceStarted (files.joinIntoString (" ")); | |||||
} | |||||
} | |||||
static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||||
static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||||
static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||||
static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) | |||||
{ | |||||
NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||||
const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")])); | |||||
MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||||
} | |||||
static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) | |||||
{ | |||||
if (menuTrackingChangedCallback != nullptr) | |||||
(*menuTrackingChangedCallback) (true); | |||||
} | |||||
static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) | |||||
{ | |||||
if (menuTrackingChangedCallback != nullptr) | |||||
(*menuTrackingChangedCallback) (false); | |||||
} | |||||
static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) | |||||
static void focusChanged() | |||||
{ | |||||
if (appFocusChangeCallback != nullptr) | |||||
(*appFocusChangeCallback)(); | |||||
} | |||||
static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||||
{ | |||||
if (auto* app = JUCEApplicationBase::getInstance()) | |||||
app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||||
} | |||||
static String quotedIfContainsSpaces (NSString* file) | |||||
{ | |||||
String s (nsStringToJuce (file)); | |||||
if (s.containsChar (' ')) | |||||
s = s.quoted ('"'); | |||||
return s; | |||||
} | |||||
}; | |||||
}; | |||||
//============================================================================== | |||||
void MessageManager::runDispatchLoop() | |||||
{ | |||||
if (! quitMessagePosted) // check that the quit message wasn't already posted.. | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
// must only be called by the message thread! | |||||
jassert (isThisTheMessageThread()); | |||||
#if JUCE_PROJUCER_LIVE_BUILD | |||||
runDispatchLoopUntil (std::numeric_limits<int>::max()); | |||||
#else | |||||
#if JUCE_CATCH_UNHANDLED_EXCEPTIONS | |||||
@try | |||||
{ | |||||
[NSApp run]; | |||||
} | |||||
@catch (NSException* e) | |||||
{ | |||||
// An AppKit exception will kill the app, but at least this provides a chance to log it., | |||||
std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]); | |||||
JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__); | |||||
} | |||||
@finally | |||||
{ | |||||
} | |||||
#else | |||||
[NSApp run]; | |||||
#endif | |||||
#endif | |||||
} | |||||
} | |||||
} | |||||
static void shutdownNSApp() | |||||
{ | |||||
[NSApp stop: nil]; | |||||
[NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; | |||||
} | |||||
void MessageManager::stopDispatchLoop() | |||||
{ | |||||
#if JUCE_PROJUCER_LIVE_BUILD | |||||
quitMessagePosted = true; | |||||
#else | |||||
if (isThisTheMessageThread()) | |||||
{ | |||||
quitMessagePosted = true; | |||||
shutdownNSApp(); | |||||
} | |||||
else | |||||
{ | |||||
struct QuitCallback : public CallbackMessage | |||||
{ | |||||
QuitCallback() {} | |||||
void messageCallback() override { MessageManager::getInstance()->stopDispatchLoop(); } | |||||
}; | |||||
(new QuitCallback())->post(); | |||||
} | |||||
#endif | |||||
} | |||||
#if JUCE_MODAL_LOOPS_PERMITTED | |||||
bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) | |||||
{ | |||||
jassert (millisecondsToRunFor >= 0); | |||||
jassert (isThisTheMessageThread()); // must only be called by the message thread | |||||
uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor; | |||||
while (! quitMessagePosted) | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true); | |||||
NSEvent* e = [NSApp nextEventMatchingMask: NSEventMaskAny | |||||
untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001] | |||||
inMode: NSDefaultRunLoopMode | |||||
dequeue: YES]; | |||||
if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e))) | |||||
[NSApp sendEvent: e]; | |||||
if (Time::getMillisecondCounter() >= endTime) | |||||
break; | |||||
} | |||||
} | |||||
return ! quitMessagePosted; | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
void initialiseNSApplication(); | |||||
void initialiseNSApplication() | |||||
{ | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
[NSApplication sharedApplication]; | |||||
} | |||||
} | |||||
static AppDelegate* appDelegate = nullptr; | |||||
void MessageManager::doPlatformSpecificInitialisation() | |||||
{ | |||||
if (appDelegate == nil) | |||||
appDelegate = new AppDelegate(); | |||||
} | |||||
void MessageManager::doPlatformSpecificShutdown() | |||||
{ | |||||
delete appDelegate; | |||||
appDelegate = nullptr; | |||||
} | |||||
bool MessageManager::postMessageToSystemQueue (MessageBase* message) | |||||
{ | |||||
jassert (appDelegate != nil); | |||||
appDelegate->messageQueue.post (message); | |||||
return true; | |||||
} | |||||
void MessageManager::broadcastMessage (const String& message) | |||||
{ | |||||
NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message) | |||||
forKey: nsStringLiteral ("message")]; | |||||
[[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroadcastEventName() | |||||
object: nil | |||||
userInfo: info]; | |||||
} | |||||
// Special function used by some plugin classes to re-post carbon events | |||||
void __attribute__ ((visibility("default"))) repostCurrentNSEvent(); | |||||
void __attribute__ ((visibility("default"))) repostCurrentNSEvent() | |||||
{ | |||||
struct EventReposter : public CallbackMessage | |||||
{ | |||||
EventReposter() : e ([[NSApp currentEvent] retain]) {} | |||||
~EventReposter() { [e release]; } | |||||
void messageCallback() override | |||||
{ | |||||
[NSApp postEvent: e atStart: YES]; | |||||
} | |||||
NSEvent* e; | |||||
}; | |||||
(new EventReposter())->post(); | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
struct MountedVolumeListChangeDetector::Pimpl | |||||
{ | |||||
Pimpl (MountedVolumeListChangeDetector& d) : owner (d) | |||||
{ | |||||
static ObserverClass cls; | |||||
delegate = [cls.createInstance() init]; | |||||
ObserverClass::setOwner (delegate, this); | |||||
NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter]; | |||||
[nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil]; | |||||
[nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil]; | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate]; | |||||
[delegate release]; | |||||
} | |||||
private: | |||||
MountedVolumeListChangeDetector& owner; | |||||
id delegate; | |||||
struct ObserverClass : public ObjCClass<NSObject> | |||||
{ | |||||
ObserverClass() : ObjCClass<NSObject> ("JUCEDriveObserver_") | |||||
{ | |||||
addIvar<Pimpl*> ("owner"); | |||||
addMethod (@selector (changed:), changed, "v@:@"); | |||||
addProtocol (@protocol (NSTextInput)); | |||||
registerClass(); | |||||
} | |||||
static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); } | |||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } | |||||
static void changed (id self, SEL, NSNotification*) | |||||
{ | |||||
getOwner (self)->owner.mountedVolumeListChanged(); | |||||
} | |||||
}; | |||||
}; | |||||
MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } | |||||
MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,105 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/* An internal message pump class used in OSX and iOS. */ | |||||
class MessageQueue | |||||
{ | |||||
public: | |||||
MessageQueue() | |||||
{ | |||||
#if JUCE_IOS | |||||
runLoop = CFRunLoopGetCurrent(); | |||||
#else | |||||
runLoop = CFRunLoopGetMain(); | |||||
#endif | |||||
CFRunLoopSourceContext sourceContext; | |||||
zerostruct (sourceContext); // (can't use "= { 0 }" on this object because it's typedef'ed as a C struct) | |||||
sourceContext.info = this; | |||||
sourceContext.perform = runLoopSourceCallback; | |||||
runLoopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 1, &sourceContext); | |||||
CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes); | |||||
} | |||||
~MessageQueue() noexcept | |||||
{ | |||||
CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes); | |||||
CFRunLoopSourceInvalidate (runLoopSource); | |||||
CFRelease (runLoopSource); | |||||
} | |||||
void post (MessageManager::MessageBase* const message) | |||||
{ | |||||
messages.add (message); | |||||
wakeUp(); | |||||
} | |||||
private: | |||||
ReferenceCountedArray<MessageManager::MessageBase, CriticalSection> messages; | |||||
CFRunLoopRef runLoop; | |||||
CFRunLoopSourceRef runLoopSource; | |||||
void wakeUp() noexcept | |||||
{ | |||||
CFRunLoopSourceSignal (runLoopSource); | |||||
CFRunLoopWakeUp (runLoop); | |||||
} | |||||
bool deliverNextMessage() | |||||
{ | |||||
const MessageManager::MessageBase::Ptr nextMessage (messages.removeAndReturn (0)); | |||||
if (nextMessage == nullptr) | |||||
return false; | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
nextMessage->messageCallback(); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
return true; | |||||
} | |||||
void runLoopCallback() noexcept | |||||
{ | |||||
for (int i = 4; --i >= 0;) | |||||
if (! deliverNextMessage()) | |||||
return; | |||||
wakeUp(); | |||||
} | |||||
static void runLoopSourceCallback (void* info) noexcept | |||||
{ | |||||
static_cast<MessageQueue*> (info)->runLoopCallback(); | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,135 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
class HiddenMessageWindow | |||||
{ | |||||
public: | |||||
HiddenMessageWindow (const TCHAR* const messageWindowName, WNDPROC wndProc) | |||||
{ | |||||
String className ("JUCE_"); | |||||
className << String::toHexString (Time::getHighResolutionTicks()); | |||||
HMODULE moduleHandle = (HMODULE) Process::getCurrentModuleInstanceHandle(); | |||||
WNDCLASSEX wc = { 0 }; | |||||
wc.cbSize = sizeof (wc); | |||||
wc.lpfnWndProc = wndProc; | |||||
wc.cbWndExtra = 4; | |||||
wc.hInstance = moduleHandle; | |||||
wc.lpszClassName = className.toWideCharPointer(); | |||||
atom = RegisterClassEx (&wc); | |||||
jassert (atom != 0); | |||||
hwnd = CreateWindow (getClassNameFromAtom(), messageWindowName, | |||||
0, 0, 0, 0, 0, 0, 0, moduleHandle, 0); | |||||
jassert (hwnd != 0); | |||||
} | |||||
~HiddenMessageWindow() | |||||
{ | |||||
DestroyWindow (hwnd); | |||||
UnregisterClass (getClassNameFromAtom(), 0); | |||||
} | |||||
inline HWND getHWND() const noexcept { return hwnd; } | |||||
private: | |||||
ATOM atom; | |||||
HWND hwnd; | |||||
LPCTSTR getClassNameFromAtom() noexcept { return (LPCTSTR) (pointer_sized_uint) atom; } | |||||
}; | |||||
//============================================================================== | |||||
class JuceWindowIdentifier | |||||
{ | |||||
public: | |||||
static bool isJUCEWindow (HWND hwnd) noexcept | |||||
{ | |||||
return GetWindowLongPtr (hwnd, GWLP_USERDATA) == getImprobableWindowNumber(); | |||||
} | |||||
static void setAsJUCEWindow (HWND hwnd, bool isJuceWindow) noexcept | |||||
{ | |||||
SetWindowLongPtr (hwnd, GWLP_USERDATA, isJuceWindow ? getImprobableWindowNumber() : 0); | |||||
} | |||||
private: | |||||
static LONG_PTR getImprobableWindowNumber() noexcept | |||||
{ | |||||
static auto number = (LONG_PTR) Random().nextInt64(); | |||||
return number; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class DeviceChangeDetector : private Timer | |||||
{ | |||||
public: | |||||
DeviceChangeDetector (const wchar_t* const name) | |||||
: messageWindow (name, (WNDPROC) deviceChangeEventCallback) | |||||
{ | |||||
SetWindowLongPtr (messageWindow.getHWND(), GWLP_USERDATA, (LONG_PTR) this); | |||||
} | |||||
virtual ~DeviceChangeDetector() {} | |||||
virtual void systemDeviceChanged() = 0; | |||||
void triggerAsyncDeviceChangeCallback() | |||||
{ | |||||
// We'll pause before sending a message, because on device removal, the OS hasn't always updated | |||||
// its device lists correctly at this point. This also helps avoid repeated callbacks. | |||||
startTimer (500); | |||||
} | |||||
private: | |||||
HiddenMessageWindow messageWindow; | |||||
static LRESULT CALLBACK deviceChangeEventCallback (HWND h, const UINT message, | |||||
const WPARAM wParam, const LPARAM lParam) | |||||
{ | |||||
if (message == WM_DEVICECHANGE | |||||
&& (wParam == 0x8000 /*DBT_DEVICEARRIVAL*/ | |||||
|| wParam == 0x8004 /*DBT_DEVICEREMOVECOMPLETE*/ | |||||
|| wParam == 0x0007 /*DBT_DEVNODES_CHANGED*/)) | |||||
{ | |||||
((DeviceChangeDetector*) GetWindowLongPtr (h, GWLP_USERDATA)) | |||||
->triggerAsyncDeviceChangeCallback(); | |||||
} | |||||
return DefWindowProc (h, message, wParam, lParam); | |||||
} | |||||
void timerCallback() override | |||||
{ | |||||
stopTimer(); | |||||
systemDeviceChanged(); | |||||
} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,232 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
extern HWND juce_messageWindowHandle; | |||||
typedef bool (*CheckEventBlockedByModalComps) (const MSG&); | |||||
CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; | |||||
//============================================================================== | |||||
namespace WindowsMessageHelpers | |||||
{ | |||||
const unsigned int customMessageID = WM_USER + 123; | |||||
const unsigned int broadcastMessageMagicNumber = 0xc403; | |||||
const TCHAR messageWindowName[] = _T("JUCEWindow"); | |||||
ScopedPointer<HiddenMessageWindow> messageWindow; | |||||
void dispatchMessageFromLParam (LPARAM lParam) | |||||
{ | |||||
if (MessageManager::MessageBase* message = reinterpret_cast<MessageManager::MessageBase*> (lParam)) | |||||
{ | |||||
JUCE_TRY | |||||
{ | |||||
message->messageCallback(); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
message->decReferenceCount(); | |||||
} | |||||
} | |||||
BOOL CALLBACK broadcastEnumWindowProc (HWND hwnd, LPARAM lParam) | |||||
{ | |||||
if (hwnd != juce_messageWindowHandle) | |||||
{ | |||||
TCHAR windowName[64] = { 0 }; // no need to read longer strings than this | |||||
GetWindowText (hwnd, windowName, 63); | |||||
if (String (windowName) == messageWindowName) | |||||
reinterpret_cast<Array<HWND>*> (lParam)->add (hwnd); | |||||
} | |||||
return TRUE; | |||||
} | |||||
void handleBroadcastMessage (const COPYDATASTRUCT* const data) | |||||
{ | |||||
if (data != nullptr && data->dwData == broadcastMessageMagicNumber) | |||||
{ | |||||
struct BroadcastMessage : public CallbackMessage | |||||
{ | |||||
BroadcastMessage (CharPointer_UTF32 text, size_t length) : message (text, length) {} | |||||
void messageCallback() override { MessageManager::getInstance()->deliverBroadcastMessage (message); } | |||||
String message; | |||||
}; | |||||
(new BroadcastMessage (CharPointer_UTF32 ((const CharPointer_UTF32::CharType*) data->lpData), | |||||
data->cbData / sizeof (CharPointer_UTF32::CharType))) | |||||
->post(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
LRESULT CALLBACK messageWndProc (HWND h, const UINT message, const WPARAM wParam, const LPARAM lParam) noexcept | |||||
{ | |||||
if (h == juce_messageWindowHandle) | |||||
{ | |||||
if (message == customMessageID) | |||||
{ | |||||
// (These are trapped early in our dispatch loop, but must also be checked | |||||
// here in case some 3rd-party code is running the dispatch loop). | |||||
dispatchMessageFromLParam (lParam); | |||||
return 0; | |||||
} | |||||
if (message == WM_COPYDATA) | |||||
{ | |||||
handleBroadcastMessage (reinterpret_cast<const COPYDATASTRUCT*> (lParam)); | |||||
return 0; | |||||
} | |||||
} | |||||
return DefWindowProc (h, message, wParam, lParam); | |||||
} | |||||
} | |||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! JUCE_MINGW | |||||
LRESULT juce_offerEventToActiveXControl (::MSG&); | |||||
#endif | |||||
//============================================================================== | |||||
bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPendingMessages) | |||||
{ | |||||
using namespace WindowsMessageHelpers; | |||||
MSG m; | |||||
if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE)) | |||||
return false; | |||||
if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) | |||||
{ | |||||
#if JUCE_MODULE_AVAILABLE_juce_gui_extra && ! JUCE_MINGW | |||||
if (juce_offerEventToActiveXControl (m) != S_FALSE) | |||||
return true; | |||||
#endif | |||||
if (m.message == customMessageID && m.hwnd == juce_messageWindowHandle) | |||||
{ | |||||
dispatchMessageFromLParam (m.lParam); | |||||
} | |||||
else if (m.message == WM_QUIT) | |||||
{ | |||||
if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||||
app->systemRequestedQuit(); | |||||
} | |||||
else if (isEventBlockedByModalComps == nullptr || ! isEventBlockedByModalComps (m)) | |||||
{ | |||||
if ((m.message == WM_LBUTTONDOWN || m.message == WM_RBUTTONDOWN) | |||||
&& ! JuceWindowIdentifier::isJUCEWindow (m.hwnd)) | |||||
{ | |||||
// if it's someone else's window being clicked on, and the focus is | |||||
// currently on a juce window, pass the kb focus over.. | |||||
HWND currentFocus = GetFocus(); | |||||
if (currentFocus == 0 || JuceWindowIdentifier::isJUCEWindow (currentFocus)) | |||||
SetFocus (m.hwnd); | |||||
} | |||||
TranslateMessage (&m); | |||||
DispatchMessage (&m); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
{ | |||||
message->incReferenceCount(); | |||||
return PostMessage (juce_messageWindowHandle, WindowsMessageHelpers::customMessageID, 0, (LPARAM) message) != 0; | |||||
} | |||||
void MessageManager::broadcastMessage (const String& value) | |||||
{ | |||||
const String localCopy (value); | |||||
Array<HWND> windows; | |||||
EnumWindows (&WindowsMessageHelpers::broadcastEnumWindowProc, (LPARAM) &windows); | |||||
for (int i = windows.size(); --i >= 0;) | |||||
{ | |||||
COPYDATASTRUCT data; | |||||
data.dwData = WindowsMessageHelpers::broadcastMessageMagicNumber; | |||||
data.cbData = (localCopy.length() + 1) * sizeof (CharPointer_UTF32::CharType); | |||||
data.lpData = (void*) localCopy.toUTF32().getAddress(); | |||||
DWORD_PTR result; | |||||
SendMessageTimeout (windows.getUnchecked (i), WM_COPYDATA, | |||||
(WPARAM) juce_messageWindowHandle, | |||||
(LPARAM) &data, | |||||
SMTO_BLOCK | SMTO_ABORTIFHUNG, 8000, &result); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::doPlatformSpecificInitialisation() | |||||
{ | |||||
OleInitialize (0); | |||||
using namespace WindowsMessageHelpers; | |||||
messageWindow = new HiddenMessageWindow (messageWindowName, (WNDPROC) messageWndProc); | |||||
juce_messageWindowHandle = messageWindow->getHWND(); | |||||
} | |||||
void MessageManager::doPlatformSpecificShutdown() | |||||
{ | |||||
WindowsMessageHelpers::messageWindow = nullptr; | |||||
OleUninitialize(); | |||||
} | |||||
//============================================================================== | |||||
struct MountedVolumeListChangeDetector::Pimpl : private DeviceChangeDetector | |||||
{ | |||||
Pimpl (MountedVolumeListChangeDetector& d) : DeviceChangeDetector (L"MountedVolumeList"), owner (d) | |||||
{ | |||||
File::findFileSystemRoots (lastVolumeList); | |||||
} | |||||
void systemDeviceChanged() override | |||||
{ | |||||
Array<File> newList; | |||||
File::findFileSystemRoots (newList); | |||||
if (lastVolumeList != newList) | |||||
{ | |||||
lastVolumeList = newList; | |||||
owner.mountedVolumeListChanged(); | |||||
} | |||||
} | |||||
MountedVolumeListChangeDetector& owner; | |||||
Array<File> lastVolumeList; | |||||
}; | |||||
MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); } | |||||
MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {} | |||||
} // namespace juce |
@@ -0,0 +1,26 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
juce_ImplementSingleton (WinRTWrapper) | |||||
} |
@@ -0,0 +1,131 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class WinRTWrapper : public DeletedAtShutdown | |||||
{ | |||||
public: | |||||
juce_DeclareSingleton (WinRTWrapper, true) | |||||
class ScopedHString | |||||
{ | |||||
public: | |||||
ScopedHString (String str) | |||||
{ | |||||
if (WinRTWrapper::getInstance()->isInitialised()) | |||||
WinRTWrapper::getInstance()->createHString (str.toWideCharPointer(), | |||||
static_cast<uint32_t> (str.length()), | |||||
&hstr); | |||||
} | |||||
~ScopedHString() | |||||
{ | |||||
if (WinRTWrapper::getInstance()->isInitialised() && hstr != nullptr) | |||||
WinRTWrapper::getInstance()->deleteHString (hstr); | |||||
} | |||||
HSTRING get() const noexcept | |||||
{ | |||||
return hstr; | |||||
} | |||||
private: | |||||
HSTRING hstr = nullptr; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedHString) | |||||
}; | |||||
~WinRTWrapper() | |||||
{ | |||||
if (winRTHandle != nullptr) | |||||
::FreeLibrary (winRTHandle); | |||||
} | |||||
String hStringToString (HSTRING hstr) | |||||
{ | |||||
if (isInitialised()) | |||||
if (const wchar_t* str = getHStringRawBuffer (hstr, nullptr)) | |||||
return String (str); | |||||
return {}; | |||||
} | |||||
bool isInitialised() const noexcept | |||||
{ | |||||
return initialised; | |||||
} | |||||
template <class ComClass> | |||||
ComSmartPtr<ComClass> getWRLFactory (const wchar_t* runtimeClassID) | |||||
{ | |||||
ComSmartPtr<ComClass> comPtr; | |||||
if (isInitialised()) | |||||
{ | |||||
ScopedHString classID (runtimeClassID); | |||||
if (classID.get() != nullptr) | |||||
roGetActivationFactory (classID.get(), __uuidof (ComClass), (void**) comPtr.resetAndGetPointerAddress()); | |||||
} | |||||
return comPtr; | |||||
} | |||||
private: | |||||
WinRTWrapper() | |||||
{ | |||||
winRTHandle = ::LoadLibraryA ("api-ms-win-core-winrt-l1-1-0"); | |||||
if (winRTHandle == nullptr) | |||||
return; | |||||
roInitialize = (RoInitializeFuncPtr) ::GetProcAddress (winRTHandle, "RoInitialize"); | |||||
createHString = (WindowsCreateStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsCreateString"); | |||||
deleteHString = (WindowsDeleteStringFuncPtr) ::GetProcAddress (winRTHandle, "WindowsDeleteString"); | |||||
getHStringRawBuffer = (WindowsGetStringRawBufferFuncPtr) ::GetProcAddress (winRTHandle, "WindowsGetStringRawBuffer"); | |||||
roGetActivationFactory = (RoGetActivationFactoryFuncPtr) ::GetProcAddress (winRTHandle, "RoGetActivationFactory"); | |||||
if (roInitialize == nullptr || createHString == nullptr || deleteHString == nullptr | |||||
|| getHStringRawBuffer == nullptr || roGetActivationFactory == nullptr) | |||||
return; | |||||
HRESULT status = roInitialize (1); | |||||
initialised = ! (status != S_OK && status != S_FALSE && status != 0x80010106L); | |||||
} | |||||
HMODULE winRTHandle = nullptr; | |||||
bool initialised = false; | |||||
typedef HRESULT (WINAPI* RoInitializeFuncPtr) (int); | |||||
typedef HRESULT (WINAPI* WindowsCreateStringFuncPtr) (LPCWSTR, UINT32, HSTRING*); | |||||
typedef HRESULT (WINAPI* WindowsDeleteStringFuncPtr) (HSTRING); | |||||
typedef PCWSTR (WINAPI* WindowsGetStringRawBufferFuncPtr) (HSTRING, UINT32*); | |||||
typedef HRESULT (WINAPI* RoGetActivationFactoryFuncPtr) (HSTRING, REFIID, void**); | |||||
RoInitializeFuncPtr roInitialize = nullptr; | |||||
WindowsCreateStringFuncPtr createHString = nullptr; | |||||
WindowsDeleteStringFuncPtr deleteHString = nullptr; | |||||
WindowsGetStringRawBufferFuncPtr getHStringRawBuffer = nullptr; | |||||
RoGetActivationFactoryFuncPtr roGetActivationFactory = nullptr; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,108 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct MultiTimerCallback : public Timer | |||||
{ | |||||
MultiTimerCallback (const int tid, MultiTimer& mt) noexcept | |||||
: owner (mt), timerID (tid) | |||||
{ | |||||
} | |||||
void timerCallback() override | |||||
{ | |||||
owner.timerCallback (timerID); | |||||
} | |||||
MultiTimer& owner; | |||||
const int timerID; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiTimerCallback) | |||||
}; | |||||
//============================================================================== | |||||
MultiTimer::MultiTimer() noexcept {} | |||||
MultiTimer::MultiTimer (const MultiTimer&) noexcept {} | |||||
MultiTimer::~MultiTimer() | |||||
{ | |||||
const SpinLock::ScopedLockType sl (timerListLock); | |||||
timers.clear(); | |||||
} | |||||
//============================================================================== | |||||
Timer* MultiTimer::getCallback (int timerID) const noexcept | |||||
{ | |||||
for (int i = timers.size(); --i >= 0;) | |||||
{ | |||||
MultiTimerCallback* const t = static_cast<MultiTimerCallback*> (timers.getUnchecked(i)); | |||||
if (t->timerID == timerID) | |||||
return t; | |||||
} | |||||
return nullptr; | |||||
} | |||||
void MultiTimer::startTimer (const int timerID, const int intervalInMilliseconds) noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (timerListLock); | |||||
Timer* timer = getCallback (timerID); | |||||
if (timer == nullptr) | |||||
timers.add (timer = new MultiTimerCallback (timerID, *this)); | |||||
timer->startTimer (intervalInMilliseconds); | |||||
} | |||||
void MultiTimer::stopTimer (const int timerID) noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (timerListLock); | |||||
if (Timer* const t = getCallback (timerID)) | |||||
t->stopTimer(); | |||||
} | |||||
bool MultiTimer::isTimerRunning (const int timerID) const noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (timerListLock); | |||||
if (Timer* const t = getCallback (timerID)) | |||||
return t->isTimerRunning(); | |||||
return false; | |||||
} | |||||
int MultiTimer::getTimerInterval (const int timerID) const noexcept | |||||
{ | |||||
const SpinLock::ScopedLockType sl (timerListLock); | |||||
if (Timer* const t = getCallback (timerID)) | |||||
return t->getTimerInterval(); | |||||
return 0; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,123 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A type of timer class that can run multiple timers with different frequencies, | |||||
all of which share a single callback. | |||||
This class is very similar to the Timer class, but allows you run multiple | |||||
separate timers, where each one has a unique ID number. The methods in this | |||||
class are exactly equivalent to those in Timer, but with the addition of | |||||
this ID number. | |||||
To use it, you need to create a subclass of MultiTimer, implementing the | |||||
timerCallback() method. Then you can start timers with startTimer(), and | |||||
each time the callback is triggered, it passes in the ID of the timer that | |||||
caused it. | |||||
@see Timer | |||||
*/ | |||||
class JUCE_API MultiTimer | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates a MultiTimer. | |||||
When created, no timers are running, so use startTimer() to start things off. | |||||
*/ | |||||
MultiTimer() noexcept; | |||||
/** Creates a copy of another timer. | |||||
Note that this timer will not contain any running timers, even if the one you're | |||||
copying from was running. | |||||
*/ | |||||
MultiTimer (const MultiTimer&) noexcept; | |||||
public: | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~MultiTimer(); | |||||
//============================================================================== | |||||
/** The user-defined callback routine that actually gets called by each of the | |||||
timers that are running. | |||||
It's perfectly ok to call startTimer() or stopTimer() from within this | |||||
callback to change the subsequent intervals. | |||||
*/ | |||||
virtual void timerCallback (int timerID) = 0; | |||||
//============================================================================== | |||||
/** Starts a timer and sets the length of interval required. | |||||
If the timer is already started, this will reset it, so the | |||||
time between calling this method and the next timer callback | |||||
will not be less than the interval length passed in. | |||||
@param timerID a unique Id number that identifies the timer to | |||||
start. This is the id that will be passed back | |||||
to the timerCallback() method when this timer is | |||||
triggered | |||||
@param intervalInMilliseconds the interval to use (any values less than 1 will be | |||||
rounded up to 1) | |||||
*/ | |||||
void startTimer (int timerID, int intervalInMilliseconds) noexcept; | |||||
/** Stops a timer. | |||||
If a timer has been started with the given ID number, it will be cancelled. | |||||
No more callbacks will be made for the specified timer after this method returns. | |||||
If this is called from a different thread, any callbacks that may | |||||
be currently executing may be allowed to finish before the method | |||||
returns. | |||||
*/ | |||||
void stopTimer (int timerID) noexcept; | |||||
//============================================================================== | |||||
/** Checks whether a timer has been started for a specified ID. | |||||
@returns true if a timer with the given ID is running. | |||||
*/ | |||||
bool isTimerRunning (int timerID) const noexcept; | |||||
/** Returns the interval for a specified timer ID. | |||||
@returns the timer's interval in milliseconds if it's running, or 0 if no | |||||
timer was running for the ID number specified. | |||||
*/ | |||||
int getTimerInterval (int timerID) const noexcept; | |||||
//============================================================================== | |||||
private: | |||||
SpinLock timerListLock; | |||||
OwnedArray<Timer> timers; | |||||
Timer* getCallback (int) const noexcept; | |||||
MultiTimer& operator= (const MultiTimer&); | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,371 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class Timer::TimerThread : private Thread, | |||||
private DeletedAtShutdown, | |||||
private AsyncUpdater | |||||
{ | |||||
public: | |||||
typedef CriticalSection LockType; // (mysteriously, using a SpinLock here causes problems on some XP machines..) | |||||
TimerThread() | |||||
: Thread ("Juce Timer"), | |||||
firstTimer (nullptr) | |||||
{ | |||||
triggerAsyncUpdate(); | |||||
} | |||||
~TimerThread() noexcept | |||||
{ | |||||
signalThreadShouldExit(); | |||||
callbackArrived.signal(); | |||||
stopThread (4000); | |||||
jassert (instance == this || instance == nullptr); | |||||
if (instance == this) | |||||
instance = nullptr; | |||||
} | |||||
void run() override | |||||
{ | |||||
uint32 lastTime = Time::getMillisecondCounter(); | |||||
MessageManager::MessageBase::Ptr messageToSend (new CallTimersMessage()); | |||||
while (! threadShouldExit()) | |||||
{ | |||||
const uint32 now = Time::getMillisecondCounter(); | |||||
const int elapsed = (int) (now >= lastTime ? (now - lastTime) | |||||
: (std::numeric_limits<uint32>::max() - (lastTime - now))); | |||||
lastTime = now; | |||||
const int timeUntilFirstTimer = getTimeUntilFirstTimer (elapsed); | |||||
if (timeUntilFirstTimer <= 0) | |||||
{ | |||||
if (callbackArrived.wait (0)) | |||||
{ | |||||
// already a message in flight - do nothing.. | |||||
} | |||||
else | |||||
{ | |||||
messageToSend->post(); | |||||
if (! callbackArrived.wait (300)) | |||||
{ | |||||
// Sometimes our message can get discarded by the OS (e.g. when running as an RTAS | |||||
// when the app has a modal loop), so this is how long to wait before assuming the | |||||
// message has been lost and trying again. | |||||
messageToSend->post(); | |||||
} | |||||
continue; | |||||
} | |||||
} | |||||
// don't wait for too long because running this loop also helps keep the | |||||
// Time::getApproximateMillisecondTimer value stay up-to-date | |||||
wait (jlimit (1, 100, timeUntilFirstTimer)); | |||||
} | |||||
} | |||||
void callTimers() | |||||
{ | |||||
// avoid getting stuck in a loop if a timer callback repeatedly takes too long | |||||
const uint32 timeout = Time::getMillisecondCounter() + 100; | |||||
const LockType::ScopedLockType sl (lock); | |||||
while (firstTimer != nullptr && firstTimer->timerCountdownMs <= 0) | |||||
{ | |||||
Timer* const t = firstTimer; | |||||
t->timerCountdownMs = t->timerPeriodMs; | |||||
removeTimer (t); | |||||
addTimer (t); | |||||
const LockType::ScopedUnlockType ul (lock); | |||||
JUCE_TRY | |||||
{ | |||||
t->timerCallback(); | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
if (Time::getMillisecondCounter() > timeout) | |||||
break; | |||||
} | |||||
callbackArrived.signal(); | |||||
} | |||||
void callTimersSynchronously() | |||||
{ | |||||
if (! isThreadRunning()) | |||||
{ | |||||
// (This is relied on by some plugins in cases where the MM has | |||||
// had to restart and the async callback never started) | |||||
cancelPendingUpdate(); | |||||
triggerAsyncUpdate(); | |||||
} | |||||
callTimers(); | |||||
} | |||||
static inline void add (Timer* const tim) noexcept | |||||
{ | |||||
if (instance == nullptr) | |||||
instance = new TimerThread(); | |||||
instance->addTimer (tim); | |||||
} | |||||
static inline void remove (Timer* const tim) noexcept | |||||
{ | |||||
if (instance != nullptr) | |||||
instance->removeTimer (tim); | |||||
} | |||||
static inline void resetCounter (Timer* const tim, const int newCounter) noexcept | |||||
{ | |||||
if (instance != nullptr) | |||||
{ | |||||
tim->timerCountdownMs = newCounter; | |||||
tim->timerPeriodMs = newCounter; | |||||
if ((tim->nextTimer != nullptr && tim->nextTimer->timerCountdownMs < tim->timerCountdownMs) | |||||
|| (tim->previousTimer != nullptr && tim->previousTimer->timerCountdownMs > tim->timerCountdownMs)) | |||||
{ | |||||
instance->removeTimer (tim); | |||||
instance->addTimer (tim); | |||||
} | |||||
} | |||||
} | |||||
static TimerThread* instance; | |||||
static LockType lock; | |||||
private: | |||||
Timer* volatile firstTimer; | |||||
WaitableEvent callbackArrived; | |||||
struct CallTimersMessage : public MessageManager::MessageBase | |||||
{ | |||||
CallTimersMessage() {} | |||||
void messageCallback() override | |||||
{ | |||||
if (instance != nullptr) | |||||
instance->callTimers(); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
void addTimer (Timer* const t) noexcept | |||||
{ | |||||
#if JUCE_DEBUG | |||||
// trying to add a timer that's already here - shouldn't get to this point, | |||||
// so if you get this assertion, let me know! | |||||
jassert (! timerExists (t)); | |||||
#endif | |||||
Timer* i = firstTimer; | |||||
if (i == nullptr || i->timerCountdownMs > t->timerCountdownMs) | |||||
{ | |||||
t->nextTimer = firstTimer; | |||||
firstTimer = t; | |||||
} | |||||
else | |||||
{ | |||||
while (i->nextTimer != nullptr && i->nextTimer->timerCountdownMs <= t->timerCountdownMs) | |||||
i = i->nextTimer; | |||||
jassert (i != nullptr); | |||||
t->nextTimer = i->nextTimer; | |||||
t->previousTimer = i; | |||||
i->nextTimer = t; | |||||
} | |||||
if (t->nextTimer != nullptr) | |||||
t->nextTimer->previousTimer = t; | |||||
jassert ((t->nextTimer == nullptr || t->nextTimer->timerCountdownMs >= t->timerCountdownMs) | |||||
&& (t->previousTimer == nullptr || t->previousTimer->timerCountdownMs <= t->timerCountdownMs)); | |||||
notify(); | |||||
} | |||||
void removeTimer (Timer* const t) noexcept | |||||
{ | |||||
#if JUCE_DEBUG | |||||
// trying to remove a timer that's not here - shouldn't get to this point, | |||||
// so if you get this assertion, let me know! | |||||
jassert (timerExists (t)); | |||||
#endif | |||||
if (t->previousTimer != nullptr) | |||||
{ | |||||
jassert (firstTimer != t); | |||||
t->previousTimer->nextTimer = t->nextTimer; | |||||
} | |||||
else | |||||
{ | |||||
jassert (firstTimer == t); | |||||
firstTimer = t->nextTimer; | |||||
} | |||||
if (t->nextTimer != nullptr) | |||||
t->nextTimer->previousTimer = t->previousTimer; | |||||
t->nextTimer = nullptr; | |||||
t->previousTimer = nullptr; | |||||
} | |||||
int getTimeUntilFirstTimer (const int numMillisecsElapsed) const | |||||
{ | |||||
const LockType::ScopedLockType sl (lock); | |||||
for (Timer* t = firstTimer; t != nullptr; t = t->nextTimer) | |||||
t->timerCountdownMs -= numMillisecsElapsed; | |||||
return firstTimer != nullptr ? firstTimer->timerCountdownMs : 1000; | |||||
} | |||||
void handleAsyncUpdate() override | |||||
{ | |||||
startThread (7); | |||||
} | |||||
#if JUCE_DEBUG | |||||
bool timerExists (Timer* const t) const noexcept | |||||
{ | |||||
for (Timer* tt = firstTimer; tt != nullptr; tt = tt->nextTimer) | |||||
if (tt == t) | |||||
return true; | |||||
return false; | |||||
} | |||||
#endif | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TimerThread) | |||||
}; | |||||
Timer::TimerThread* Timer::TimerThread::instance = nullptr; | |||||
Timer::TimerThread::LockType Timer::TimerThread::lock; | |||||
//============================================================================== | |||||
Timer::Timer() noexcept | |||||
: timerCountdownMs (0), | |||||
timerPeriodMs (0), | |||||
previousTimer (nullptr), | |||||
nextTimer (nullptr) | |||||
{ | |||||
} | |||||
Timer::Timer (const Timer&) noexcept | |||||
: timerCountdownMs (0), | |||||
timerPeriodMs (0), | |||||
previousTimer (nullptr), | |||||
nextTimer (nullptr) | |||||
{ | |||||
} | |||||
Timer::~Timer() | |||||
{ | |||||
stopTimer(); | |||||
} | |||||
void Timer::startTimer (const int interval) noexcept | |||||
{ | |||||
// If you're calling this before (or after) the MessageManager is | |||||
// running, then you're not going to get any timer callbacks! | |||||
jassert (MessageManager::getInstanceWithoutCreating() != nullptr); | |||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||||
if (timerPeriodMs == 0) | |||||
{ | |||||
timerCountdownMs = interval; | |||||
timerPeriodMs = jmax (1, interval); | |||||
TimerThread::add (this); | |||||
} | |||||
else | |||||
{ | |||||
TimerThread::resetCounter (this, interval); | |||||
} | |||||
} | |||||
void Timer::startTimerHz (int timerFrequencyHz) noexcept | |||||
{ | |||||
if (timerFrequencyHz > 0) | |||||
startTimer (1000 / timerFrequencyHz); | |||||
else | |||||
stopTimer(); | |||||
} | |||||
void Timer::stopTimer() noexcept | |||||
{ | |||||
const TimerThread::LockType::ScopedLockType sl (TimerThread::lock); | |||||
if (timerPeriodMs > 0) | |||||
{ | |||||
TimerThread::remove (this); | |||||
timerPeriodMs = 0; | |||||
} | |||||
} | |||||
void JUCE_CALLTYPE Timer::callPendingTimersSynchronously() | |||||
{ | |||||
if (TimerThread::instance != nullptr) | |||||
TimerThread::instance->callTimersSynchronously(); | |||||
} | |||||
struct LambdaInvoker : private Timer | |||||
{ | |||||
LambdaInvoker (int milliseconds, std::function<void()> f) : function (f) | |||||
{ | |||||
startTimer (milliseconds); | |||||
} | |||||
void timerCallback() override | |||||
{ | |||||
auto f = function; | |||||
delete this; | |||||
f(); | |||||
} | |||||
std::function<void()> function; | |||||
JUCE_DECLARE_NON_COPYABLE (LambdaInvoker) | |||||
}; | |||||
void JUCE_CALLTYPE Timer::callAfterDelay (int milliseconds, std::function<void()> f) | |||||
{ | |||||
new LambdaInvoker (milliseconds, f); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,135 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Makes repeated callbacks to a virtual method at a specified time interval. | |||||
A Timer's timerCallback() method will be repeatedly called at a given | |||||
interval. When you create a Timer object, it will do nothing until the | |||||
startTimer() method is called, which will cause the message thread to | |||||
start making callbacks at the specified interval, until stopTimer() is called | |||||
or the object is deleted. | |||||
The time interval isn't guaranteed to be precise to any more than maybe | |||||
10-20ms, and the intervals may end up being much longer than requested if the | |||||
system is busy. Because the callbacks are made by the main message thread, | |||||
anything that blocks the message queue for a period of time will also prevent | |||||
any timers from running until it can carry on. | |||||
If you need to have a single callback that is shared by multiple timers with | |||||
different frequencies, then the MultiTimer class allows you to do that - its | |||||
structure is very similar to the Timer class, but contains multiple timers | |||||
internally, each one identified by an ID number. | |||||
@see HighResolutionTimer, MultiTimer | |||||
*/ | |||||
class JUCE_API Timer | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
/** Creates a Timer. | |||||
When created, the timer is stopped, so use startTimer() to get it going. | |||||
*/ | |||||
Timer() noexcept; | |||||
/** Creates a copy of another timer. | |||||
Note that this timer won't be started, even if the one you're copying | |||||
is running. | |||||
*/ | |||||
Timer (const Timer&) noexcept; | |||||
public: | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~Timer(); | |||||
//============================================================================== | |||||
/** The user-defined callback routine that actually gets called periodically. | |||||
It's perfectly ok to call startTimer() or stopTimer() from within this | |||||
callback to change the subsequent intervals. | |||||
*/ | |||||
virtual void timerCallback() = 0; | |||||
//============================================================================== | |||||
/** Starts the timer and sets the length of interval required. | |||||
If the timer is already started, this will reset it, so the | |||||
time between calling this method and the next timer callback | |||||
will not be less than the interval length passed in. | |||||
@param intervalInMilliseconds the interval to use (any value less | |||||
than 1 will be rounded up to 1) | |||||
*/ | |||||
void startTimer (int intervalInMilliseconds) noexcept; | |||||
/** Starts the timer with an interval specified in Hertz. | |||||
This is effectively the same as calling startTimer (1000 / timerFrequencyHz). | |||||
*/ | |||||
void startTimerHz (int timerFrequencyHz) noexcept; | |||||
/** Stops the timer. | |||||
No more timer callbacks will be triggered after this method returns. | |||||
Note that if you call this from a background thread while the message-thread | |||||
is already in the middle of your callback, then this method will cancel any | |||||
future timer callbacks, but it will return without waiting for the current one | |||||
to finish. The current callback will continue, possibly still running some of | |||||
your timer code after this method has returned. | |||||
*/ | |||||
void stopTimer() noexcept; | |||||
//============================================================================== | |||||
/** Returns true if the timer is currently running. */ | |||||
bool isTimerRunning() const noexcept { return timerPeriodMs > 0; } | |||||
/** Returns the timer's interval. | |||||
@returns the timer's interval in milliseconds if it's running, or 0 if it's not. | |||||
*/ | |||||
int getTimerInterval() const noexcept { return timerPeriodMs; } | |||||
//============================================================================== | |||||
/** Invokes a lambda after a given number of milliseconds. */ | |||||
static void JUCE_CALLTYPE callAfterDelay (int milliseconds, std::function<void()> functionToCall); | |||||
//============================================================================== | |||||
/** For internal use only: invokes any timers that need callbacks. | |||||
Don't call this unless you really know what you're doing! | |||||
*/ | |||||
static void JUCE_CALLTYPE callPendingTimersSynchronously(); | |||||
private: | |||||
class TimerThread; | |||||
friend class TimerThread; | |||||
int timerCountdownMs, timerPeriodMs; // NB: these member variable names are a little verbose | |||||
Timer* previousTimer, *nextTimer; // to reduce risk of name-clashes with user subclasses | |||||
Timer& operator= (const Timer&) JUCE_DELETED_FUNCTION; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,123 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_graphics # | |||||
# -------------------------- # | |||||
# Created by falkTX | |||||
# | |||||
CWD=../.. | |||||
MODULENAME=juce_graphics | |||||
include ../Makefile.mk | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_GRAPHICS_FLAGS) -I.. | |||||
ifeq ($(WIN32),true) | |||||
BUILD_CXX_FLAGS += -Wno-missing-field-initializers -Wno-strict-overflow | |||||
endif | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
ifeq ($(MACOS),true) | |||||
OBJS = $(OBJDIR)/$(MODULENAME).mm.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o | |||||
else | |||||
OBJS = $(OBJDIR)/$(MODULENAME).cpp.o | |||||
OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o | |||||
OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o | |||||
OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
all: $(MODULEDIR)/$(MODULENAME).a | |||||
posix32: $(MODULEDIR)/$(MODULENAME).posix32.a | |||||
posix64: $(MODULEDIR)/$(MODULENAME).posix64.a | |||||
win32: $(MODULEDIR)/$(MODULENAME).win32.a | |||||
win64: $(MODULEDIR)/$(MODULENAME).win64.a | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
clean: | |||||
rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(MODULEDIR)/$(MODULENAME).a: $(OBJS) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).posix64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win32.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) | |||||
-@mkdir -p $(MODULEDIR) | |||||
@echo "Creating $(MODULENAME).win64.a" | |||||
@rm -f $@ | |||||
@$(AR) crs $@ $^ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $<" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (32bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ | |||||
$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp | |||||
-@mkdir -p $(OBJDIR) | |||||
@echo "Compiling $< (64bit)" | |||||
@$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ | |||||
# ---------------------------------------------------------------------------------------------------------------------------- | |||||
-include $(OBJS:%.o=%.d) | |||||
-include $(OBJS_posix32:%.o=%.d) | |||||
-include $(OBJS_posix64:%.o=%.d) | |||||
-include $(OBJS_win32:%.o=%.d) | |||||
-include $(OBJS_win64:%.o=%.d) | |||||
# ---------------------------------------------------------------------------------------------------------------------------- |
@@ -0,0 +1,468 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace ColourHelpers | |||||
{ | |||||
static uint8 floatToUInt8 (const float n) noexcept | |||||
{ | |||||
return n <= 0.0f ? 0 : (n >= 1.0f ? 255 : static_cast<uint8> (n * 255.996f)); | |||||
} | |||||
//============================================================================== | |||||
struct HSB | |||||
{ | |||||
HSB (Colour col) noexcept | |||||
{ | |||||
const int r = col.getRed(); | |||||
const int g = col.getGreen(); | |||||
const int b = col.getBlue(); | |||||
const int hi = jmax (r, g, b); | |||||
const int lo = jmin (r, g, b); | |||||
if (hi != 0) | |||||
{ | |||||
saturation = (hi - lo) / (float) hi; | |||||
if (saturation > 0) | |||||
{ | |||||
const float invDiff = 1.0f / (hi - lo); | |||||
const float red = (hi - r) * invDiff; | |||||
const float green = (hi - g) * invDiff; | |||||
const float blue = (hi - b) * invDiff; | |||||
if (r == hi) | |||||
hue = blue - green; | |||||
else if (g == hi) | |||||
hue = 2.0f + red - blue; | |||||
else | |||||
hue = 4.0f + green - red; | |||||
hue *= 1.0f / 6.0f; | |||||
if (hue < 0) | |||||
++hue; | |||||
} | |||||
else | |||||
{ | |||||
hue = 0; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
saturation = hue = 0; | |||||
} | |||||
brightness = hi / 255.0f; | |||||
} | |||||
Colour toColour (Colour original) const noexcept | |||||
{ | |||||
return Colour (hue, saturation, brightness, original.getAlpha()); | |||||
} | |||||
static PixelARGB toRGB (float h, float s, float v, const uint8 alpha) noexcept | |||||
{ | |||||
v = jlimit (0.0f, 255.0f, v * 255.0f); | |||||
const uint8 intV = (uint8) roundToInt (v); | |||||
if (s <= 0) | |||||
return PixelARGB (alpha, intV, intV, intV); | |||||
s = jmin (1.0f, s); | |||||
h = (h - std::floor (h)) * 6.0f + 0.00001f; // need a small adjustment to compensate for rounding errors | |||||
const float f = h - std::floor (h); | |||||
const uint8 x = (uint8) roundToInt (v * (1.0f - s)); | |||||
if (h < 1.0f) return PixelARGB (alpha, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x); | |||||
if (h < 2.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - s * f)), intV, x); | |||||
if (h < 3.0f) return PixelARGB (alpha, x, intV, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f))))); | |||||
if (h < 4.0f) return PixelARGB (alpha, x, (uint8) roundToInt (v * (1.0f - s * f)), intV); | |||||
if (h < 5.0f) return PixelARGB (alpha, (uint8) roundToInt (v * (1.0f - (s * (1.0f - f)))), x, intV); | |||||
return PixelARGB (alpha, intV, x, (uint8) roundToInt (v * (1.0f - s * f))); | |||||
} | |||||
float hue, saturation, brightness; | |||||
}; | |||||
//============================================================================== | |||||
struct YIQ | |||||
{ | |||||
YIQ (Colour c) noexcept | |||||
{ | |||||
const float r = c.getFloatRed(); | |||||
const float g = c.getFloatGreen(); | |||||
const float b = c.getFloatBlue(); | |||||
y = 0.2999f * r + 0.5870f * g + 0.1140f * b; | |||||
i = 0.5957f * r - 0.2744f * g - 0.3212f * b; | |||||
q = 0.2114f * r - 0.5225f * g - 0.3113f * b; | |||||
alpha = c.getFloatAlpha(); | |||||
} | |||||
Colour toColour() const noexcept | |||||
{ | |||||
return Colour::fromFloatRGBA (y + 0.9563f * i + 0.6210f * q, | |||||
y - 0.2721f * i - 0.6474f * q, | |||||
y - 1.1070f * i + 1.7046f * q, | |||||
alpha); | |||||
} | |||||
float y, i, q, alpha; | |||||
}; | |||||
} | |||||
//============================================================================== | |||||
Colour::Colour() noexcept | |||||
: argb (0, 0, 0, 0) | |||||
{ | |||||
} | |||||
Colour::Colour (const Colour& other) noexcept | |||||
: argb (other.argb) | |||||
{ | |||||
} | |||||
Colour& Colour::operator= (const Colour& other) noexcept | |||||
{ | |||||
argb = other.argb; | |||||
return *this; | |||||
} | |||||
bool Colour::operator== (const Colour& other) const noexcept { return argb.getNativeARGB() == other.argb.getNativeARGB(); } | |||||
bool Colour::operator!= (const Colour& other) const noexcept { return argb.getNativeARGB() != other.argb.getNativeARGB(); } | |||||
//============================================================================== | |||||
Colour::Colour (const uint32 col) noexcept | |||||
: argb ((col >> 24) & 0xff, (col >> 16) & 0xff, (col >> 8) & 0xff, col & 0xff) | |||||
{ | |||||
} | |||||
Colour::Colour (const uint8 red, const uint8 green, const uint8 blue) noexcept | |||||
{ | |||||
argb.setARGB (0xff, red, green, blue); | |||||
} | |||||
Colour Colour::fromRGB (const uint8 red, const uint8 green, const uint8 blue) noexcept | |||||
{ | |||||
return Colour (red, green, blue); | |||||
} | |||||
Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept | |||||
{ | |||||
argb.setARGB (alpha, red, green, blue); | |||||
} | |||||
Colour Colour::fromRGBA (const uint8 red, const uint8 green, const uint8 blue, const uint8 alpha) noexcept | |||||
{ | |||||
return Colour (red, green, blue, alpha); | |||||
} | |||||
Colour::Colour (const uint8 red, const uint8 green, const uint8 blue, const float alpha) noexcept | |||||
{ | |||||
argb.setARGB (ColourHelpers::floatToUInt8 (alpha), red, green, blue); | |||||
} | |||||
Colour Colour::fromFloatRGBA (const float red, const float green, const float blue, const float alpha) noexcept | |||||
{ | |||||
return Colour (ColourHelpers::floatToUInt8 (red), | |||||
ColourHelpers::floatToUInt8 (green), | |||||
ColourHelpers::floatToUInt8 (blue), alpha); | |||||
} | |||||
Colour::Colour (const float hue, const float saturation, const float brightness, const float alpha) noexcept | |||||
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, ColourHelpers::floatToUInt8 (alpha))) | |||||
{ | |||||
} | |||||
Colour Colour::fromHSV (const float hue, const float saturation, const float brightness, const float alpha) noexcept | |||||
{ | |||||
return Colour (hue, saturation, brightness, alpha); | |||||
} | |||||
Colour::Colour (const float hue, const float saturation, const float brightness, const uint8 alpha) noexcept | |||||
: argb (ColourHelpers::HSB::toRGB (hue, saturation, brightness, alpha)) | |||||
{ | |||||
} | |||||
Colour::Colour (PixelARGB argb_) noexcept | |||||
: argb (argb_) | |||||
{ | |||||
} | |||||
Colour::Colour (PixelRGB rgb) noexcept | |||||
: argb (Colour (rgb.getInARGBMaskOrder()).argb) | |||||
{ | |||||
} | |||||
Colour::Colour (PixelAlpha alpha) noexcept | |||||
: argb (Colour (alpha.getInARGBMaskOrder()).argb) | |||||
{ | |||||
} | |||||
Colour::~Colour() noexcept | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
const PixelARGB Colour::getPixelARGB() const noexcept | |||||
{ | |||||
PixelARGB p (argb); | |||||
p.premultiply(); | |||||
return p; | |||||
} | |||||
uint32 Colour::getARGB() const noexcept | |||||
{ | |||||
return argb.getInARGBMaskOrder(); | |||||
} | |||||
//============================================================================== | |||||
bool Colour::isTransparent() const noexcept | |||||
{ | |||||
return getAlpha() == 0; | |||||
} | |||||
bool Colour::isOpaque() const noexcept | |||||
{ | |||||
return getAlpha() == 0xff; | |||||
} | |||||
Colour Colour::withAlpha (const uint8 newAlpha) const noexcept | |||||
{ | |||||
PixelARGB newCol (argb); | |||||
newCol.setAlpha (newAlpha); | |||||
return Colour (newCol); | |||||
} | |||||
Colour Colour::withAlpha (const float newAlpha) const noexcept | |||||
{ | |||||
jassert (newAlpha >= 0 && newAlpha <= 1.0f); | |||||
PixelARGB newCol (argb); | |||||
newCol.setAlpha (ColourHelpers::floatToUInt8 (newAlpha)); | |||||
return Colour (newCol); | |||||
} | |||||
Colour Colour::withMultipliedAlpha (const float alphaMultiplier) const noexcept | |||||
{ | |||||
jassert (alphaMultiplier >= 0); | |||||
PixelARGB newCol (argb); | |||||
newCol.setAlpha ((uint8) jmin (0xff, roundToInt (alphaMultiplier * newCol.getAlpha()))); | |||||
return Colour (newCol); | |||||
} | |||||
//============================================================================== | |||||
Colour Colour::overlaidWith (Colour src) const noexcept | |||||
{ | |||||
const int destAlpha = getAlpha(); | |||||
if (destAlpha <= 0) | |||||
return src; | |||||
const int invA = 0xff - (int) src.getAlpha(); | |||||
const int resA = 0xff - (((0xff - destAlpha) * invA) >> 8); | |||||
if (resA <= 0) | |||||
return *this; | |||||
const int da = (invA * destAlpha) / resA; | |||||
return Colour ((uint8) (src.getRed() + ((((int) getRed() - src.getRed()) * da) >> 8)), | |||||
(uint8) (src.getGreen() + ((((int) getGreen() - src.getGreen()) * da) >> 8)), | |||||
(uint8) (src.getBlue() + ((((int) getBlue() - src.getBlue()) * da) >> 8)), | |||||
(uint8) resA); | |||||
} | |||||
Colour Colour::interpolatedWith (Colour other, float proportionOfOther) const noexcept | |||||
{ | |||||
if (proportionOfOther <= 0) | |||||
return *this; | |||||
if (proportionOfOther >= 1.0f) | |||||
return other; | |||||
PixelARGB c1 (getPixelARGB()); | |||||
const PixelARGB c2 (other.getPixelARGB()); | |||||
c1.tween (c2, (uint32) roundToInt (proportionOfOther * 255.0f)); | |||||
c1.unpremultiply(); | |||||
return Colour (c1); | |||||
} | |||||
//============================================================================== | |||||
float Colour::getFloatRed() const noexcept { return getRed() / 255.0f; } | |||||
float Colour::getFloatGreen() const noexcept { return getGreen() / 255.0f; } | |||||
float Colour::getFloatBlue() const noexcept { return getBlue() / 255.0f; } | |||||
float Colour::getFloatAlpha() const noexcept { return getAlpha() / 255.0f; } | |||||
//============================================================================== | |||||
void Colour::getHSB (float& h, float& s, float& v) const noexcept | |||||
{ | |||||
const ColourHelpers::HSB hsb (*this); | |||||
h = hsb.hue; | |||||
s = hsb.saturation; | |||||
v = hsb.brightness; | |||||
} | |||||
float Colour::getHue() const noexcept { return ColourHelpers::HSB (*this).hue; } | |||||
float Colour::getSaturation() const noexcept { return ColourHelpers::HSB (*this).saturation; } | |||||
float Colour::getBrightness() const noexcept { return ColourHelpers::HSB (*this).brightness; } | |||||
Colour Colour::withHue (float h) const noexcept { ColourHelpers::HSB hsb (*this); hsb.hue = h; return hsb.toColour (*this); } | |||||
Colour Colour::withSaturation (float s) const noexcept { ColourHelpers::HSB hsb (*this); hsb.saturation = s; return hsb.toColour (*this); } | |||||
Colour Colour::withBrightness (float v) const noexcept { ColourHelpers::HSB hsb (*this); hsb.brightness = v; return hsb.toColour (*this); } | |||||
float Colour::getPerceivedBrightness() const noexcept | |||||
{ | |||||
return std::sqrt (0.241f * square (getFloatRed()) | |||||
+ 0.691f * square (getFloatGreen()) | |||||
+ 0.068f * square (getFloatBlue())); | |||||
} | |||||
//============================================================================== | |||||
Colour Colour::withRotatedHue (const float amountToRotate) const noexcept | |||||
{ | |||||
ColourHelpers::HSB hsb (*this); | |||||
hsb.hue += amountToRotate; | |||||
return hsb.toColour (*this); | |||||
} | |||||
Colour Colour::withMultipliedSaturation (const float amount) const noexcept | |||||
{ | |||||
ColourHelpers::HSB hsb (*this); | |||||
hsb.saturation = jmin (1.0f, hsb.saturation * amount); | |||||
return hsb.toColour (*this); | |||||
} | |||||
Colour Colour::withMultipliedBrightness (const float amount) const noexcept | |||||
{ | |||||
ColourHelpers::HSB hsb (*this); | |||||
hsb.brightness = jmin (1.0f, hsb.brightness * amount); | |||||
return hsb.toColour (*this); | |||||
} | |||||
//============================================================================== | |||||
Colour Colour::brighter (float amount) const noexcept | |||||
{ | |||||
amount = 1.0f / (1.0f + amount); | |||||
return Colour ((uint8) (255 - (amount * (255 - getRed()))), | |||||
(uint8) (255 - (amount * (255 - getGreen()))), | |||||
(uint8) (255 - (amount * (255 - getBlue()))), | |||||
getAlpha()); | |||||
} | |||||
Colour Colour::darker (float amount) const noexcept | |||||
{ | |||||
amount = 1.0f / (1.0f + amount); | |||||
return Colour ((uint8) (amount * getRed()), | |||||
(uint8) (amount * getGreen()), | |||||
(uint8) (amount * getBlue()), | |||||
getAlpha()); | |||||
} | |||||
//============================================================================== | |||||
Colour Colour::greyLevel (const float brightness) noexcept | |||||
{ | |||||
const uint8 level = ColourHelpers::floatToUInt8 (brightness); | |||||
return Colour (level, level, level); | |||||
} | |||||
//============================================================================== | |||||
Colour Colour::contrasting (const float amount) const noexcept | |||||
{ | |||||
return overlaidWith ((getPerceivedBrightness() >= 0.5f | |||||
? Colours::black | |||||
: Colours::white).withAlpha (amount)); | |||||
} | |||||
Colour Colour::contrasting (Colour target, float minContrast) const noexcept | |||||
{ | |||||
const ColourHelpers::YIQ bg (*this); | |||||
ColourHelpers::YIQ fg (target); | |||||
if (std::abs (bg.y - fg.y) >= minContrast) | |||||
return target; | |||||
const float y1 = jmax (0.0f, bg.y - minContrast); | |||||
const float y2 = jmin (1.0f, bg.y + minContrast); | |||||
fg.y = (std::abs (y1 - bg.y) > std::abs (y2 - bg.y)) ? y1 : y2; | |||||
return fg.toColour(); | |||||
} | |||||
Colour Colour::contrasting (Colour colour1, | |||||
Colour colour2) noexcept | |||||
{ | |||||
const float b1 = colour1.getPerceivedBrightness(); | |||||
const float b2 = colour2.getPerceivedBrightness(); | |||||
float best = 0.0f; | |||||
float bestDist = 0.0f; | |||||
for (float i = 0.0f; i < 1.0f; i += 0.02f) | |||||
{ | |||||
const float d1 = std::abs (i - b1); | |||||
const float d2 = std::abs (i - b2); | |||||
const float dist = jmin (d1, d2, 1.0f - d1, 1.0f - d2); | |||||
if (dist > bestDist) | |||||
{ | |||||
best = i; | |||||
bestDist = dist; | |||||
} | |||||
} | |||||
return colour1.overlaidWith (colour2.withMultipliedAlpha (0.5f)) | |||||
.withBrightness (best); | |||||
} | |||||
//============================================================================== | |||||
String Colour::toString() const | |||||
{ | |||||
return String::toHexString ((int) argb.getInARGBMaskOrder()); | |||||
} | |||||
Colour Colour::fromString (StringRef encodedColourString) | |||||
{ | |||||
return Colour ((uint32) CharacterFunctions::HexParser<int>::parse (encodedColourString.text)); | |||||
} | |||||
String Colour::toDisplayString (const bool includeAlphaValue) const | |||||
{ | |||||
return String::toHexString ((int) (argb.getInARGBMaskOrder() & (includeAlphaValue ? 0xffffffff : 0xffffff))) | |||||
.paddedLeft ('0', includeAlphaValue ? 8 : 6) | |||||
.toUpperCase(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,367 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a colour, also including a transparency value. | |||||
The colour is stored internally as unsigned 8-bit red, green, blue and alpha values. | |||||
*/ | |||||
class JUCE_API Colour | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a transparent black colour. */ | |||||
Colour() noexcept; | |||||
/** Creates a copy of another Colour object. */ | |||||
Colour (const Colour& other) noexcept; | |||||
/** Creates a colour from a 32-bit ARGB value. | |||||
The format of this number is: | |||||
((alpha << 24) | (red << 16) | (green << 8) | blue). | |||||
All components in the range 0x00 to 0xff. | |||||
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||||
@see getPixelARGB | |||||
*/ | |||||
explicit Colour (uint32 argb) noexcept; | |||||
/** Creates an opaque colour using 8-bit red, green and blue values */ | |||||
Colour (uint8 red, | |||||
uint8 green, | |||||
uint8 blue) noexcept; | |||||
/** Creates an opaque colour using 8-bit red, green and blue values */ | |||||
static Colour fromRGB (uint8 red, | |||||
uint8 green, | |||||
uint8 blue) noexcept; | |||||
/** Creates a colour using 8-bit red, green, blue and alpha values. */ | |||||
Colour (uint8 red, | |||||
uint8 green, | |||||
uint8 blue, | |||||
uint8 alpha) noexcept; | |||||
/** Creates a colour using 8-bit red, green, blue and alpha values. */ | |||||
static Colour fromRGBA (uint8 red, | |||||
uint8 green, | |||||
uint8 blue, | |||||
uint8 alpha) noexcept; | |||||
/** Creates a colour from 8-bit red, green, and blue values, and a floating-point alpha. | |||||
Alpha of 0.0 is transparent, alpha of 1.0f is opaque. | |||||
Values outside the valid range will be clipped. | |||||
*/ | |||||
Colour (uint8 red, | |||||
uint8 green, | |||||
uint8 blue, | |||||
float alpha) noexcept; | |||||
/** Creates a colour using floating point red, green, blue and alpha values. | |||||
Numbers outside the range 0..1 will be clipped. | |||||
*/ | |||||
static Colour fromFloatRGBA (float red, | |||||
float green, | |||||
float blue, | |||||
float alpha) noexcept; | |||||
/** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. | |||||
The floating point values must be between 0.0 and 1.0. | |||||
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||||
Values outside the valid range will be clipped. | |||||
*/ | |||||
Colour (float hue, | |||||
float saturation, | |||||
float brightness, | |||||
uint8 alpha) noexcept; | |||||
/** Creates a colour using floating point hue, saturation, brightness and alpha values. | |||||
All values must be between 0.0 and 1.0. | |||||
Numbers outside the valid range will be clipped. | |||||
*/ | |||||
Colour (float hue, | |||||
float saturation, | |||||
float brightness, | |||||
float alpha) noexcept; | |||||
/** Creates a colour using a PixelARGB object. This function assumes that the argb pixel is | |||||
not premultiplied. | |||||
*/ | |||||
Colour (PixelARGB argb) noexcept; | |||||
/** Creates a colour using a PixelRGB object. | |||||
*/ | |||||
Colour (PixelRGB rgb) noexcept; | |||||
/** Creates a colour using a PixelAlpha object. | |||||
*/ | |||||
Colour (PixelAlpha alpha) noexcept; | |||||
/** Creates a colour using floating point hue, saturation and brightness values, and an 8-bit alpha. | |||||
The floating point values must be between 0.0 and 1.0. | |||||
An alpha of 0x00 is completely transparent, alpha of 0xff is opaque. | |||||
Values outside the valid range will be clipped. | |||||
*/ | |||||
static Colour fromHSV (float hue, | |||||
float saturation, | |||||
float brightness, | |||||
float alpha) noexcept; | |||||
/** Destructor. */ | |||||
~Colour() noexcept; | |||||
/** Copies another Colour object. */ | |||||
Colour& operator= (const Colour& other) noexcept; | |||||
/** Compares two colours. */ | |||||
bool operator== (const Colour& other) const noexcept; | |||||
/** Compares two colours. */ | |||||
bool operator!= (const Colour& other) const noexcept; | |||||
//============================================================================== | |||||
/** Returns the red component of this colour. | |||||
@returns a value between 0x00 and 0xff. | |||||
*/ | |||||
uint8 getRed() const noexcept { return argb.getRed(); } | |||||
/** Returns the green component of this colour. | |||||
@returns a value between 0x00 and 0xff. | |||||
*/ | |||||
uint8 getGreen() const noexcept { return argb.getGreen(); } | |||||
/** Returns the blue component of this colour. | |||||
@returns a value between 0x00 and 0xff. | |||||
*/ | |||||
uint8 getBlue() const noexcept { return argb.getBlue(); } | |||||
/** Returns the red component of this colour as a floating point value. | |||||
@returns a value between 0.0 and 1.0 | |||||
*/ | |||||
float getFloatRed() const noexcept; | |||||
/** Returns the green component of this colour as a floating point value. | |||||
@returns a value between 0.0 and 1.0 | |||||
*/ | |||||
float getFloatGreen() const noexcept; | |||||
/** Returns the blue component of this colour as a floating point value. | |||||
@returns a value between 0.0 and 1.0 | |||||
*/ | |||||
float getFloatBlue() const noexcept; | |||||
/** Returns a premultiplied ARGB pixel object that represents this colour. | |||||
*/ | |||||
const PixelARGB getPixelARGB() const noexcept; | |||||
/** Returns a 32-bit integer that represents this colour. | |||||
The format of this number is: | |||||
((alpha << 24) | (red << 16) | (green << 16) | blue). | |||||
*/ | |||||
uint32 getARGB() const noexcept; | |||||
//============================================================================== | |||||
/** Returns the colour's alpha (opacity). | |||||
Alpha of 0x00 is completely transparent, 0xff is completely opaque. | |||||
*/ | |||||
uint8 getAlpha() const noexcept { return argb.getAlpha(); } | |||||
/** Returns the colour's alpha (opacity) as a floating point value. | |||||
Alpha of 0.0 is completely transparent, 1.0 is completely opaque. | |||||
*/ | |||||
float getFloatAlpha() const noexcept; | |||||
/** Returns true if this colour is completely opaque. | |||||
Equivalent to (getAlpha() == 0xff). | |||||
*/ | |||||
bool isOpaque() const noexcept; | |||||
/** Returns true if this colour is completely transparent. | |||||
Equivalent to (getAlpha() == 0x00). | |||||
*/ | |||||
bool isTransparent() const noexcept; | |||||
/** Returns a colour that's the same colour as this one, but with a new alpha value. */ | |||||
Colour withAlpha (uint8 newAlpha) const noexcept; | |||||
/** Returns a colour that's the same colour as this one, but with a new alpha value. */ | |||||
Colour withAlpha (float newAlpha) const noexcept; | |||||
/** Returns a colour that's the same colour as this one, but with a modified alpha value. | |||||
The new colour's alpha will be this object's alpha multiplied by the value passed-in. | |||||
*/ | |||||
Colour withMultipliedAlpha (float alphaMultiplier) const noexcept; | |||||
//============================================================================== | |||||
/** Returns a colour that is the result of alpha-compositing a new colour over this one. | |||||
If the foreground colour is semi-transparent, it is blended onto this colour accordingly. | |||||
*/ | |||||
Colour overlaidWith (Colour foregroundColour) const noexcept; | |||||
/** Returns a colour that lies somewhere between this one and another. | |||||
If amountOfOther is zero, the result is 100% this colour, if amountOfOther | |||||
is 1.0, the result is 100% of the other colour. | |||||
*/ | |||||
Colour interpolatedWith (Colour other, float proportionOfOther) const noexcept; | |||||
//============================================================================== | |||||
/** Returns the colour's hue component. | |||||
The value returned is in the range 0.0 to 1.0 | |||||
*/ | |||||
float getHue() const noexcept; | |||||
/** Returns the colour's saturation component. | |||||
The value returned is in the range 0.0 to 1.0 | |||||
*/ | |||||
float getSaturation() const noexcept; | |||||
/** Returns the colour's brightness component. | |||||
The value returned is in the range 0.0 to 1.0 | |||||
*/ | |||||
float getBrightness() const noexcept; | |||||
/** Returns a skewed brightness value, adjusted to better reflect the way the human | |||||
eye responds to different colour channels. This makes it better than getBrightness() | |||||
for comparing differences in brightness. | |||||
*/ | |||||
float getPerceivedBrightness() const noexcept; | |||||
/** Returns the colour's hue, saturation and brightness components all at once. | |||||
The values returned are in the range 0.0 to 1.0 | |||||
*/ | |||||
void getHSB (float& hue, | |||||
float& saturation, | |||||
float& brightness) const noexcept; | |||||
//============================================================================== | |||||
/** Returns a copy of this colour with a different hue. */ | |||||
Colour withHue (float newHue) const noexcept; | |||||
/** Returns a copy of this colour with a different saturation. */ | |||||
Colour withSaturation (float newSaturation) const noexcept; | |||||
/** Returns a copy of this colour with a different brightness. | |||||
@see brighter, darker, withMultipliedBrightness | |||||
*/ | |||||
Colour withBrightness (float newBrightness) const noexcept; | |||||
/** Returns a copy of this colour with it hue rotated. | |||||
The new colour's hue is ((this->getHue() + amountToRotate) % 1.0) | |||||
@see brighter, darker, withMultipliedBrightness | |||||
*/ | |||||
Colour withRotatedHue (float amountToRotate) const noexcept; | |||||
/** Returns a copy of this colour with its saturation multiplied by the given value. | |||||
The new colour's saturation is (this->getSaturation() * multiplier) | |||||
(the result is clipped to legal limits). | |||||
*/ | |||||
Colour withMultipliedSaturation (float multiplier) const noexcept; | |||||
/** Returns a copy of this colour with its brightness multiplied by the given value. | |||||
The new colour's brightness is (this->getBrightness() * multiplier) | |||||
(the result is clipped to legal limits). | |||||
*/ | |||||
Colour withMultipliedBrightness (float amount) const noexcept; | |||||
//============================================================================== | |||||
/** Returns a brighter version of this colour. | |||||
@param amountBrighter how much brighter to make it - a value from 0 to 1.0 where 0 is | |||||
unchanged, and higher values make it brighter | |||||
@see withMultipliedBrightness | |||||
*/ | |||||
Colour brighter (float amountBrighter = 0.4f) const noexcept; | |||||
/** Returns a darker version of this colour. | |||||
@param amountDarker how much darker to make it - a value from 0 to 1.0 where 0 is | |||||
unchanged, and higher values make it darker | |||||
@see withMultipliedBrightness | |||||
*/ | |||||
Colour darker (float amountDarker = 0.4f) const noexcept; | |||||
//============================================================================== | |||||
/** Returns a colour that will be clearly visible against this colour. | |||||
The amount parameter indicates how contrasting the new colour should | |||||
be, so e.g. Colours::black.contrasting (0.1f) will return a colour | |||||
that's just a little bit lighter; Colours::black.contrasting (1.0f) will | |||||
return white; Colours::white.contrasting (1.0f) will return black, etc. | |||||
*/ | |||||
Colour contrasting (float amount = 1.0f) const noexcept; | |||||
/** Returns a colour that is as close as possible to a target colour whilst | |||||
still being in contrast to this one. | |||||
The colour that is returned will be the targetColour, but with its luminosity | |||||
nudged up or down so that it differs from the luminosity of this colour | |||||
by at least the amount specified by minLuminosityDiff. | |||||
*/ | |||||
Colour contrasting (Colour targetColour, float minLuminosityDiff) const noexcept; | |||||
/** Returns a colour that contrasts against two colours. | |||||
Looks for a colour that contrasts with both of the colours passed-in. | |||||
Handy for things like choosing a highlight colour in text editors, etc. | |||||
*/ | |||||
static Colour contrasting (Colour colour1, | |||||
Colour colour2) noexcept; | |||||
//============================================================================== | |||||
/** Returns an opaque shade of grey. | |||||
@param brightness the level of grey to return - 0 is black, 1.0 is white | |||||
*/ | |||||
static Colour greyLevel (float brightness) noexcept; | |||||
//============================================================================== | |||||
/** Returns a stringified version of this colour. | |||||
The string can be turned back into a colour using the fromString() method. | |||||
*/ | |||||
String toString() const; | |||||
/** Reads the colour from a string that was created with toString(). */ | |||||
static Colour fromString (StringRef encodedColourString); | |||||
/** Returns the colour as a hex string in the form RRGGBB or AARRGGBB. */ | |||||
String toDisplayString (bool includeAlphaValue) const; | |||||
private: | |||||
//============================================================================== | |||||
PixelARGB argb; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,244 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
ColourGradient::ColourGradient() noexcept | |||||
{ | |||||
#if JUCE_DEBUG | |||||
point1.setX (987654.0f); | |||||
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED jassert (point1.x != 987654.0f); | |||||
#else | |||||
#define JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED | |||||
#endif | |||||
} | |||||
ColourGradient::ColourGradient (Colour colour1, const float x1, const float y1, | |||||
Colour colour2, const float x2, const float y2, | |||||
const bool radial) | |||||
: point1 (x1, y1), | |||||
point2 (x2, y2), | |||||
isRadial (radial) | |||||
{ | |||||
colours.add (ColourPoint (0.0, colour1)); | |||||
colours.add (ColourPoint (1.0, colour2)); | |||||
} | |||||
ColourGradient::ColourGradient (Colour colour1, Point<float> p1, | |||||
Colour colour2, Point<float> p2, | |||||
const bool radial) | |||||
: point1 (p1), | |||||
point2 (p2), | |||||
isRadial (radial) | |||||
{ | |||||
colours.add (ColourPoint (0.0, colour1)); | |||||
colours.add (ColourPoint (1.0, colour2)); | |||||
} | |||||
ColourGradient::~ColourGradient() | |||||
{ | |||||
} | |||||
bool ColourGradient::operator== (const ColourGradient& other) const noexcept | |||||
{ | |||||
return point1 == other.point1 && point2 == other.point2 | |||||
&& isRadial == other.isRadial | |||||
&& colours == other.colours; | |||||
} | |||||
bool ColourGradient::operator!= (const ColourGradient& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
void ColourGradient::clearColours() | |||||
{ | |||||
colours.clear(); | |||||
} | |||||
int ColourGradient::addColour (const double proportionAlongGradient, Colour colour) | |||||
{ | |||||
// must be within the two end-points | |||||
jassert (proportionAlongGradient >= 0 && proportionAlongGradient <= 1.0); | |||||
if (proportionAlongGradient <= 0) | |||||
{ | |||||
colours.set (0, ColourPoint (0.0, colour)); | |||||
return 0; | |||||
} | |||||
const double pos = jmin (1.0, proportionAlongGradient); | |||||
int i; | |||||
for (i = 0; i < colours.size(); ++i) | |||||
if (colours.getReference(i).position > pos) | |||||
break; | |||||
colours.insert (i, ColourPoint (pos, colour)); | |||||
return i; | |||||
} | |||||
void ColourGradient::removeColour (int index) | |||||
{ | |||||
jassert (index > 0 && index < colours.size() - 1); | |||||
colours.remove (index); | |||||
} | |||||
void ColourGradient::multiplyOpacity (const float multiplier) noexcept | |||||
{ | |||||
for (int i = 0; i < colours.size(); ++i) | |||||
{ | |||||
Colour& c = colours.getReference(i).colour; | |||||
c = c.withMultipliedAlpha (multiplier); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
int ColourGradient::getNumColours() const noexcept | |||||
{ | |||||
return colours.size(); | |||||
} | |||||
double ColourGradient::getColourPosition (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, colours.size())) | |||||
return colours.getReference (index).position; | |||||
return 0; | |||||
} | |||||
Colour ColourGradient::getColour (const int index) const noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, colours.size())) | |||||
return colours.getReference (index).colour; | |||||
return Colour(); | |||||
} | |||||
void ColourGradient::setColour (int index, Colour newColour) noexcept | |||||
{ | |||||
if (isPositiveAndBelow (index, colours.size())) | |||||
colours.getReference (index).colour = newColour; | |||||
} | |||||
Colour ColourGradient::getColourAtPosition (const double position) const noexcept | |||||
{ | |||||
jassert (colours.getReference(0).position == 0.0); // the first colour specified has to go at position 0 | |||||
if (position <= 0 || colours.size() <= 1) | |||||
return colours.getReference(0).colour; | |||||
int i = colours.size() - 1; | |||||
while (position < colours.getReference(i).position) | |||||
--i; | |||||
auto& p1 = colours.getReference (i); | |||||
if (i >= colours.size() - 1) | |||||
return p1.colour; | |||||
auto& p2 = colours.getReference (i + 1); | |||||
return p1.colour.interpolatedWith (p2.colour, (float) ((position - p1.position) / (p2.position - p1.position))); | |||||
} | |||||
//============================================================================== | |||||
void ColourGradient::createLookupTable (PixelARGB* const lookupTable, const int numEntries) const noexcept | |||||
{ | |||||
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? | |||||
jassert (colours.size() >= 2); | |||||
jassert (numEntries > 0); | |||||
jassert (colours.getReference(0).position == 0.0); // The first colour specified has to go at position 0 | |||||
PixelARGB pix1 (colours.getReference (0).colour.getPixelARGB()); | |||||
int index = 0; | |||||
for (int j = 1; j < colours.size(); ++j) | |||||
{ | |||||
const ColourPoint& p = colours.getReference (j); | |||||
const int numToDo = roundToInt (p.position * (numEntries - 1)) - index; | |||||
const PixelARGB pix2 (p.colour.getPixelARGB()); | |||||
for (int i = 0; i < numToDo; ++i) | |||||
{ | |||||
jassert (index >= 0 && index < numEntries); | |||||
lookupTable[index] = pix1; | |||||
lookupTable[index].tween (pix2, (uint32) ((i << 8) / numToDo)); | |||||
++index; | |||||
} | |||||
pix1 = pix2; | |||||
} | |||||
while (index < numEntries) | |||||
lookupTable [index++] = pix1; | |||||
} | |||||
int ColourGradient::createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& lookupTable) const | |||||
{ | |||||
JUCE_COLOURGRADIENT_CHECK_COORDS_INITIALISED // Trying to use this object without setting its coordinates? | |||||
jassert (colours.size() >= 2); | |||||
const int numEntries = jlimit (1, jmax (1, (colours.size() - 1) << 8), | |||||
3 * (int) point1.transformedBy (transform) | |||||
.getDistanceFrom (point2.transformedBy (transform))); | |||||
lookupTable.malloc ((size_t) numEntries); | |||||
createLookupTable (lookupTable, numEntries); | |||||
return numEntries; | |||||
} | |||||
bool ColourGradient::isOpaque() const noexcept | |||||
{ | |||||
for (int i = 0; i < colours.size(); ++i) | |||||
if (! colours.getReference(i).colour.isOpaque()) | |||||
return false; | |||||
return true; | |||||
} | |||||
bool ColourGradient::isInvisible() const noexcept | |||||
{ | |||||
for (int i = 0; i < colours.size(); ++i) | |||||
if (! colours.getReference(i).colour.isTransparent()) | |||||
return false; | |||||
return true; | |||||
} | |||||
bool ColourGradient::ColourPoint::operator== (const ColourPoint& other) const noexcept | |||||
{ | |||||
return position == other.position && colour == other.colour; | |||||
} | |||||
bool ColourGradient::ColourPoint::operator!= (const ColourPoint& other) const noexcept | |||||
{ | |||||
return position != other.position || colour != other.colour; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,202 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Describes the layout and colours that should be used to paint a colour gradient. | |||||
@see Graphics::setGradientFill | |||||
*/ | |||||
class JUCE_API ColourGradient | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a gradient object. | |||||
(x1, y1) is the location to draw with colour1. Likewise (x2, y2) is where | |||||
colour2 should be. In between them there's a gradient. | |||||
If isRadial is true, the colours form a circular gradient with (x1, y1) at | |||||
its centre. | |||||
The alpha transparencies of the colours are used, so note that | |||||
if you blend from transparent to a solid colour, the RGB of the transparent | |||||
colour will become visible in parts of the gradient. e.g. blending | |||||
from Colour::transparentBlack to Colours::white will produce a | |||||
muddy grey colour midway, but Colour::transparentWhite to Colours::white | |||||
will be white all the way across. | |||||
@see ColourGradient | |||||
*/ | |||||
ColourGradient (Colour colour1, float x1, float y1, | |||||
Colour colour2, float x2, float y2, | |||||
bool isRadial); | |||||
/** Creates a gradient object. | |||||
point1 is the location to draw with colour1. Likewise point2 is where | |||||
colour2 should be. In between them there's a gradient. | |||||
If isRadial is true, the colours form a circular gradient with point1 at | |||||
its centre. | |||||
The alpha transparencies of the colours are used, so note that | |||||
if you blend from transparent to a solid colour, the RGB of the transparent | |||||
colour will become visible in parts of the gradient. e.g. blending | |||||
from Colour::transparentBlack to Colours::white will produce a | |||||
muddy grey colour midway, but Colour::transparentWhite to Colours::white | |||||
will be white all the way across. | |||||
@see ColourGradient | |||||
*/ | |||||
ColourGradient (Colour colour1, Point<float> point1, | |||||
Colour colour2, Point<float> point2, | |||||
bool isRadial); | |||||
/** Creates an uninitialised gradient. | |||||
If you use this constructor instead of the other one, be sure to set all the | |||||
object's public member variables before using it! | |||||
*/ | |||||
ColourGradient() noexcept; | |||||
/** Destructor */ | |||||
~ColourGradient(); | |||||
//============================================================================== | |||||
/** Removes any colours that have been added. | |||||
This will also remove any start and end colours, so the gradient won't work. You'll | |||||
need to add more colours with addColour(). | |||||
*/ | |||||
void clearColours(); | |||||
/** Adds a colour at a point along the length of the gradient. | |||||
This allows the gradient to go through a spectrum of colours, instead of just a | |||||
start and end colour. | |||||
@param proportionAlongGradient a value between 0 and 1.0, which is the proportion | |||||
of the distance along the line between the two points | |||||
at which the colour should occur. | |||||
@param colour the colour that should be used at this point | |||||
@returns the index at which the new point was added | |||||
*/ | |||||
int addColour (double proportionAlongGradient, | |||||
Colour colour); | |||||
/** Removes one of the colours from the gradient. */ | |||||
void removeColour (int index); | |||||
/** Multiplies the alpha value of all the colours by the given scale factor */ | |||||
void multiplyOpacity (float multiplier) noexcept; | |||||
//============================================================================== | |||||
/** Returns the number of colour-stops that have been added. */ | |||||
int getNumColours() const noexcept; | |||||
/** Returns the position along the length of the gradient of the colour with this index. | |||||
The index is from 0 to getNumColours() - 1. The return value will be between 0.0 and 1.0 | |||||
*/ | |||||
double getColourPosition (int index) const noexcept; | |||||
/** Returns the colour that was added with a given index. | |||||
The index is from 0 to getNumColours() - 1. | |||||
*/ | |||||
Colour getColour (int index) const noexcept; | |||||
/** Changes the colour at a given index. | |||||
The index is from 0 to getNumColours() - 1. | |||||
*/ | |||||
void setColour (int index, Colour newColour) noexcept; | |||||
/** Returns the an interpolated colour at any position along the gradient. | |||||
@param position the position along the gradient, between 0 and 1 | |||||
*/ | |||||
Colour getColourAtPosition (double position) const noexcept; | |||||
//============================================================================== | |||||
/** Creates a set of interpolated premultiplied ARGB values. | |||||
This will resize the HeapBlock, fill it with the colours, and will return the number of | |||||
colours that it added. | |||||
When calling this, the ColourGradient must have at least 2 colour stops specified. | |||||
*/ | |||||
int createLookupTable (const AffineTransform& transform, HeapBlock<PixelARGB>& resultLookupTable) const; | |||||
/** Creates a set of interpolated premultiplied ARGB values. | |||||
This will fill an array of a user-specified size with the gradient, interpolating to fit. | |||||
The numEntries argument specifies the size of the array, and this size must be greater than zero. | |||||
When calling this, the ColourGradient must have at least 2 colour stops specified. | |||||
*/ | |||||
void createLookupTable (PixelARGB* resultLookupTable, int numEntries) const noexcept; | |||||
/** Returns true if all colours are opaque. */ | |||||
bool isOpaque() const noexcept; | |||||
/** Returns true if all colours are completely transparent. */ | |||||
bool isInvisible() const noexcept; | |||||
//============================================================================== | |||||
Point<float> point1, point2; | |||||
/** If true, the gradient should be filled circularly, centred around | |||||
point1, with point2 defining a point on the circumference. | |||||
If false, the gradient is linear between the two points. | |||||
*/ | |||||
bool isRadial; | |||||
bool operator== (const ColourGradient&) const noexcept; | |||||
bool operator!= (const ColourGradient&) const noexcept; | |||||
private: | |||||
//============================================================================== | |||||
struct ColourPoint | |||||
{ | |||||
ColourPoint() noexcept {} | |||||
ColourPoint (const double pos, Colour col) noexcept | |||||
: position (pos), colour (col) | |||||
{} | |||||
bool operator== (const ColourPoint&) const noexcept; | |||||
bool operator!= (const ColourPoint&) const noexcept; | |||||
double position; | |||||
Colour colour; | |||||
}; | |||||
Array<ColourPoint> colours; | |||||
JUCE_LEAK_DETECTOR (ColourGradient) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,335 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
const Colour Colours::transparentBlack (0); | |||||
const Colour Colours::transparentWhite (0x00ffffff); | |||||
const Colour Colours::aliceblue (0xfff0f8ff); | |||||
const Colour Colours::antiquewhite (0xfffaebd7); | |||||
const Colour Colours::aqua (0xff00ffff); | |||||
const Colour Colours::aquamarine (0xff7fffd4); | |||||
const Colour Colours::azure (0xfff0ffff); | |||||
const Colour Colours::beige (0xfff5f5dc); | |||||
const Colour Colours::bisque (0xffffe4c4); | |||||
const Colour Colours::black (0xff000000); | |||||
const Colour Colours::blanchedalmond (0xffffebcd); | |||||
const Colour Colours::blue (0xff0000ff); | |||||
const Colour Colours::blueviolet (0xff8a2be2); | |||||
const Colour Colours::brown (0xffa52a2a); | |||||
const Colour Colours::burlywood (0xffdeb887); | |||||
const Colour Colours::cadetblue (0xff5f9ea0); | |||||
const Colour Colours::chartreuse (0xff7fff00); | |||||
const Colour Colours::chocolate (0xffd2691e); | |||||
const Colour Colours::coral (0xffff7f50); | |||||
const Colour Colours::cornflowerblue (0xff6495ed); | |||||
const Colour Colours::cornsilk (0xfffff8dc); | |||||
const Colour Colours::crimson (0xffdc143c); | |||||
const Colour Colours::cyan (0xff00ffff); | |||||
const Colour Colours::darkblue (0xff00008b); | |||||
const Colour Colours::darkcyan (0xff008b8b); | |||||
const Colour Colours::darkgoldenrod (0xffb8860b); | |||||
const Colour Colours::darkgrey (0xff555555); | |||||
const Colour Colours::darkgreen (0xff006400); | |||||
const Colour Colours::darkkhaki (0xffbdb76b); | |||||
const Colour Colours::darkmagenta (0xff8b008b); | |||||
const Colour Colours::darkolivegreen (0xff556b2f); | |||||
const Colour Colours::darkorange (0xffff8c00); | |||||
const Colour Colours::darkorchid (0xff9932cc); | |||||
const Colour Colours::darkred (0xff8b0000); | |||||
const Colour Colours::darksalmon (0xffe9967a); | |||||
const Colour Colours::darkseagreen (0xff8fbc8f); | |||||
const Colour Colours::darkslateblue (0xff483d8b); | |||||
const Colour Colours::darkslategrey (0xff2f4f4f); | |||||
const Colour Colours::darkturquoise (0xff00ced1); | |||||
const Colour Colours::darkviolet (0xff9400d3); | |||||
const Colour Colours::deeppink (0xffff1493); | |||||
const Colour Colours::deepskyblue (0xff00bfff); | |||||
const Colour Colours::dimgrey (0xff696969); | |||||
const Colour Colours::dodgerblue (0xff1e90ff); | |||||
const Colour Colours::firebrick (0xffb22222); | |||||
const Colour Colours::floralwhite (0xfffffaf0); | |||||
const Colour Colours::forestgreen (0xff228b22); | |||||
const Colour Colours::fuchsia (0xffff00ff); | |||||
const Colour Colours::gainsboro (0xffdcdcdc); | |||||
const Colour Colours::ghostwhite (0xfff8f8ff); | |||||
const Colour Colours::gold (0xffffd700); | |||||
const Colour Colours::goldenrod (0xffdaa520); | |||||
const Colour Colours::grey (0xff808080); | |||||
const Colour Colours::green (0xff008000); | |||||
const Colour Colours::greenyellow (0xffadff2f); | |||||
const Colour Colours::honeydew (0xfff0fff0); | |||||
const Colour Colours::hotpink (0xffff69b4); | |||||
const Colour Colours::indianred (0xffcd5c5c); | |||||
const Colour Colours::indigo (0xff4b0082); | |||||
const Colour Colours::ivory (0xfffffff0); | |||||
const Colour Colours::khaki (0xfff0e68c); | |||||
const Colour Colours::lavender (0xffe6e6fa); | |||||
const Colour Colours::lavenderblush (0xfffff0f5); | |||||
const Colour Colours::lawngreen (0xff7cfc00); | |||||
const Colour Colours::lemonchiffon (0xfffffacd); | |||||
const Colour Colours::lightblue (0xffadd8e6); | |||||
const Colour Colours::lightcoral (0xfff08080); | |||||
const Colour Colours::lightcyan (0xffe0ffff); | |||||
const Colour Colours::lightgoldenrodyellow (0xfffafad2); | |||||
const Colour Colours::lightgreen (0xff90ee90); | |||||
const Colour Colours::lightgrey (0xffd3d3d3); | |||||
const Colour Colours::lightpink (0xffffb6c1); | |||||
const Colour Colours::lightsalmon (0xffffa07a); | |||||
const Colour Colours::lightseagreen (0xff20b2aa); | |||||
const Colour Colours::lightskyblue (0xff87cefa); | |||||
const Colour Colours::lightslategrey (0xff778899); | |||||
const Colour Colours::lightsteelblue (0xffb0c4de); | |||||
const Colour Colours::lightyellow (0xffffffe0); | |||||
const Colour Colours::lime (0xff00ff00); | |||||
const Colour Colours::limegreen (0xff32cd32); | |||||
const Colour Colours::linen (0xfffaf0e6); | |||||
const Colour Colours::magenta (0xffff00ff); | |||||
const Colour Colours::maroon (0xff800000); | |||||
const Colour Colours::mediumaquamarine (0xff66cdaa); | |||||
const Colour Colours::mediumblue (0xff0000cd); | |||||
const Colour Colours::mediumorchid (0xffba55d3); | |||||
const Colour Colours::mediumpurple (0xff9370db); | |||||
const Colour Colours::mediumseagreen (0xff3cb371); | |||||
const Colour Colours::mediumslateblue (0xff7b68ee); | |||||
const Colour Colours::mediumspringgreen (0xff00fa9a); | |||||
const Colour Colours::mediumturquoise (0xff48d1cc); | |||||
const Colour Colours::mediumvioletred (0xffc71585); | |||||
const Colour Colours::midnightblue (0xff191970); | |||||
const Colour Colours::mintcream (0xfff5fffa); | |||||
const Colour Colours::mistyrose (0xffffe4e1); | |||||
const Colour Colours::moccasin (0xffffe4b5); | |||||
const Colour Colours::navajowhite (0xffffdead); | |||||
const Colour Colours::navy (0xff000080); | |||||
const Colour Colours::oldlace (0xfffdf5e6); | |||||
const Colour Colours::olive (0xff808000); | |||||
const Colour Colours::olivedrab (0xff6b8e23); | |||||
const Colour Colours::orange (0xffffa500); | |||||
const Colour Colours::orangered (0xffff4500); | |||||
const Colour Colours::orchid (0xffda70d6); | |||||
const Colour Colours::palegoldenrod (0xffeee8aa); | |||||
const Colour Colours::palegreen (0xff98fb98); | |||||
const Colour Colours::paleturquoise (0xffafeeee); | |||||
const Colour Colours::palevioletred (0xffdb7093); | |||||
const Colour Colours::papayawhip (0xffffefd5); | |||||
const Colour Colours::peachpuff (0xffffdab9); | |||||
const Colour Colours::peru (0xffcd853f); | |||||
const Colour Colours::pink (0xffffc0cb); | |||||
const Colour Colours::plum (0xffdda0dd); | |||||
const Colour Colours::powderblue (0xffb0e0e6); | |||||
const Colour Colours::purple (0xff800080); | |||||
const Colour Colours::rebeccapurple (0xff663399); | |||||
const Colour Colours::red (0xffff0000); | |||||
const Colour Colours::rosybrown (0xffbc8f8f); | |||||
const Colour Colours::royalblue (0xff4169e1); | |||||
const Colour Colours::saddlebrown (0xff8b4513); | |||||
const Colour Colours::salmon (0xfffa8072); | |||||
const Colour Colours::sandybrown (0xfff4a460); | |||||
const Colour Colours::seagreen (0xff2e8b57); | |||||
const Colour Colours::seashell (0xfffff5ee); | |||||
const Colour Colours::sienna (0xffa0522d); | |||||
const Colour Colours::silver (0xffc0c0c0); | |||||
const Colour Colours::skyblue (0xff87ceeb); | |||||
const Colour Colours::slateblue (0xff6a5acd); | |||||
const Colour Colours::slategrey (0xff708090); | |||||
const Colour Colours::snow (0xfffffafa); | |||||
const Colour Colours::springgreen (0xff00ff7f); | |||||
const Colour Colours::steelblue (0xff4682b4); | |||||
const Colour Colours::tan (0xffd2b48c); | |||||
const Colour Colours::teal (0xff008080); | |||||
const Colour Colours::thistle (0xffd8bfd8); | |||||
const Colour Colours::tomato (0xffff6347); | |||||
const Colour Colours::turquoise (0xff40e0d0); | |||||
const Colour Colours::violet (0xffee82ee); | |||||
const Colour Colours::wheat (0xfff5deb3); | |||||
const Colour Colours::white (0xffffffff); | |||||
const Colour Colours::whitesmoke (0xfff5f5f5); | |||||
const Colour Colours::yellow (0xffffff00); | |||||
const Colour Colours::yellowgreen (0xff9acd32); | |||||
//============================================================================== | |||||
Colour Colours::findColourForName (const String& colourName, | |||||
Colour defaultColour) | |||||
{ | |||||
static const uint32 presets[] = | |||||
{ | |||||
// (first value is the string's hashcode, second is ARGB) | |||||
0x05978fff, 0xff000000, /* black */ | |||||
0x06bdcc29, 0xffffffff, /* white */ | |||||
0x002e305a, 0xff0000ff, /* blue */ | |||||
0x00308adf, 0xff808080, /* grey */ | |||||
0x05e0cf03, 0xff008000, /* green */ | |||||
0x0001b891, 0xffff0000, /* red */ | |||||
0xd43c6474, 0xffffff00, /* yellow */ | |||||
0x620886da, 0xfff0f8ff, /* aliceblue */ | |||||
0x20a2676a, 0xfffaebd7, /* antiquewhite */ | |||||
0x002dcebc, 0xff00ffff, /* aqua */ | |||||
0x46bb5f7e, 0xff7fffd4, /* aquamarine */ | |||||
0x0590228f, 0xfff0ffff, /* azure */ | |||||
0x05947fe4, 0xfff5f5dc, /* beige */ | |||||
0xad388e35, 0xffffe4c4, /* bisque */ | |||||
0x00674f7e, 0xffffebcd, /* blanchedalmond */ | |||||
0x39129959, 0xff8a2be2, /* blueviolet */ | |||||
0x059a8136, 0xffa52a2a, /* brown */ | |||||
0x89cea8f9, 0xffdeb887, /* burlywood */ | |||||
0x0fa260cf, 0xff5f9ea0, /* cadetblue */ | |||||
0x6b748956, 0xff7fff00, /* chartreuse */ | |||||
0x2903623c, 0xffd2691e, /* chocolate */ | |||||
0x05a74431, 0xffff7f50, /* coral */ | |||||
0x618d42dd, 0xff6495ed, /* cornflowerblue */ | |||||
0xe4b479fd, 0xfffff8dc, /* cornsilk */ | |||||
0x3d8c4edf, 0xffdc143c, /* crimson */ | |||||
0x002ed323, 0xff00ffff, /* cyan */ | |||||
0x67cc74d0, 0xff00008b, /* darkblue */ | |||||
0x67cd1799, 0xff008b8b, /* darkcyan */ | |||||
0x31bbd168, 0xffb8860b, /* darkgoldenrod */ | |||||
0x67cecf55, 0xff555555, /* darkgrey */ | |||||
0x920b194d, 0xff006400, /* darkgreen */ | |||||
0x923edd4c, 0xffbdb76b, /* darkkhaki */ | |||||
0x5c293873, 0xff8b008b, /* darkmagenta */ | |||||
0x6b6671fe, 0xff556b2f, /* darkolivegreen */ | |||||
0xbcfd2524, 0xffff8c00, /* darkorange */ | |||||
0xbcfdf799, 0xff9932cc, /* darkorchid */ | |||||
0x55ee0d5b, 0xff8b0000, /* darkred */ | |||||
0xc2e5f564, 0xffe9967a, /* darksalmon */ | |||||
0x61be858a, 0xff8fbc8f, /* darkseagreen */ | |||||
0xc2b0f2bd, 0xff483d8b, /* darkslateblue */ | |||||
0xc2b34d42, 0xff2f4f4f, /* darkslategrey */ | |||||
0x7cf2b06b, 0xff00ced1, /* darkturquoise */ | |||||
0xc8769375, 0xff9400d3, /* darkviolet */ | |||||
0x25832862, 0xffff1493, /* deeppink */ | |||||
0xfcad568f, 0xff00bfff, /* deepskyblue */ | |||||
0x634c8b67, 0xff696969, /* dimgrey */ | |||||
0x45c1ce55, 0xff1e90ff, /* dodgerblue */ | |||||
0xef19e3cb, 0xffb22222, /* firebrick */ | |||||
0xb852b195, 0xfffffaf0, /* floralwhite */ | |||||
0xd086fd06, 0xff228b22, /* forestgreen */ | |||||
0xe106b6d7, 0xffff00ff, /* fuchsia */ | |||||
0x7880d61e, 0xffdcdcdc, /* gainsboro */ | |||||
0x2018a2fa, 0xfff8f8ff, /* ghostwhite */ | |||||
0x00308060, 0xffffd700, /* gold */ | |||||
0xb3b3bc1e, 0xffdaa520, /* goldenrod */ | |||||
0xbab8a537, 0xffadff2f, /* greenyellow */ | |||||
0xe4cacafb, 0xfff0fff0, /* honeydew */ | |||||
0x41892743, 0xffff69b4, /* hotpink */ | |||||
0xd5796f1a, 0xffcd5c5c, /* indianred */ | |||||
0xb969fed2, 0xff4b0082, /* indigo */ | |||||
0x05fef6a9, 0xfffffff0, /* ivory */ | |||||
0x06149302, 0xfff0e68c, /* khaki */ | |||||
0xad5a05c7, 0xffe6e6fa, /* lavender */ | |||||
0x7c4d5b99, 0xfffff0f5, /* lavenderblush */ | |||||
0x41cc4377, 0xff7cfc00, /* lawngreen */ | |||||
0x195756f0, 0xfffffacd, /* lemonchiffon */ | |||||
0x28e4ea70, 0xffadd8e6, /* lightblue */ | |||||
0xf3c7ccdb, 0xfff08080, /* lightcoral */ | |||||
0x28e58d39, 0xffe0ffff, /* lightcyan */ | |||||
0x21234e3c, 0xfffafad2, /* lightgoldenrodyellow */ | |||||
0xf40157ad, 0xff90ee90, /* lightgreen */ | |||||
0x28e744f5, 0xffd3d3d3, /* lightgrey */ | |||||
0x28eb3b8c, 0xffffb6c1, /* lightpink */ | |||||
0x9fb78304, 0xffffa07a, /* lightsalmon */ | |||||
0x50632b2a, 0xff20b2aa, /* lightseagreen */ | |||||
0x68fb7b25, 0xff87cefa, /* lightskyblue */ | |||||
0xa8a35ba2, 0xff778899, /* lightslategrey */ | |||||
0xa20d484f, 0xffb0c4de, /* lightsteelblue */ | |||||
0xaa2cf10a, 0xffffffe0, /* lightyellow */ | |||||
0x0032afd5, 0xff00ff00, /* lime */ | |||||
0x607bbc4e, 0xff32cd32, /* limegreen */ | |||||
0x06234efa, 0xfffaf0e6, /* linen */ | |||||
0x316858a9, 0xffff00ff, /* magenta */ | |||||
0xbf8ca470, 0xff800000, /* maroon */ | |||||
0xbd58e0b3, 0xff66cdaa, /* mediumaquamarine */ | |||||
0x967dfd4f, 0xff0000cd, /* mediumblue */ | |||||
0x056f5c58, 0xffba55d3, /* mediumorchid */ | |||||
0x07556b71, 0xff9370db, /* mediumpurple */ | |||||
0x5369b689, 0xff3cb371, /* mediumseagreen */ | |||||
0x066be19e, 0xff7b68ee, /* mediumslateblue */ | |||||
0x3256b281, 0xff00fa9a, /* mediumspringgreen */ | |||||
0xc0ad9f4c, 0xff48d1cc, /* mediumturquoise */ | |||||
0x628e63dd, 0xffc71585, /* mediumvioletred */ | |||||
0x168eb32a, 0xff191970, /* midnightblue */ | |||||
0x4306b960, 0xfff5fffa, /* mintcream */ | |||||
0x4cbc0e6b, 0xffffe4e1, /* mistyrose */ | |||||
0xd9447d59, 0xffffe4b5, /* moccasin */ | |||||
0xe97218a6, 0xffffdead, /* navajowhite */ | |||||
0x00337bb6, 0xff000080, /* navy */ | |||||
0xadd2d33e, 0xfffdf5e6, /* oldlace */ | |||||
0x064ee1db, 0xff808000, /* olive */ | |||||
0x9e33a98a, 0xff6b8e23, /* olivedrab */ | |||||
0xc3de262e, 0xffffa500, /* orange */ | |||||
0x58bebba3, 0xffff4500, /* orangered */ | |||||
0xc3def8a3, 0xffda70d6, /* orchid */ | |||||
0x28cb4834, 0xffeee8aa, /* palegoldenrod */ | |||||
0x3d9dd619, 0xff98fb98, /* palegreen */ | |||||
0x74022737, 0xffafeeee, /* paleturquoise */ | |||||
0x15e2ebc8, 0xffdb7093, /* palevioletred */ | |||||
0x5fd898e2, 0xffffefd5, /* papayawhip */ | |||||
0x93e1b776, 0xffffdab9, /* peachpuff */ | |||||
0x003472f8, 0xffcd853f, /* peru */ | |||||
0x00348176, 0xffffc0cb, /* pink */ | |||||
0x00348d94, 0xffdda0dd, /* plum */ | |||||
0xd036be93, 0xffb0e0e6, /* powderblue */ | |||||
0xc5c507bc, 0xff800080, /* purple */ | |||||
0xf381f607, 0xff663399, /* rebeccapurple */ | |||||
0xa89d65b3, 0xffbc8f8f, /* rosybrown */ | |||||
0xbd9413e1, 0xff4169e1, /* royalblue */ | |||||
0xf456044f, 0xff8b4513, /* saddlebrown */ | |||||
0xc9c6f66e, 0xfffa8072, /* salmon */ | |||||
0x0bb131e1, 0xfff4a460, /* sandybrown */ | |||||
0x34636c14, 0xff2e8b57, /* seagreen */ | |||||
0x3507fb41, 0xfffff5ee, /* seashell */ | |||||
0xca348772, 0xffa0522d, /* sienna */ | |||||
0xca37d30d, 0xffc0c0c0, /* silver */ | |||||
0x80da74fb, 0xff87ceeb, /* skyblue */ | |||||
0x44a8dd73, 0xff6a5acd, /* slateblue */ | |||||
0x44ab37f8, 0xff708090, /* slategrey */ | |||||
0x0035f183, 0xfffffafa, /* snow */ | |||||
0xd5440d16, 0xff00ff7f, /* springgreen */ | |||||
0x3e1524a5, 0xff4682b4, /* steelblue */ | |||||
0x0001bfa1, 0xffd2b48c, /* tan */ | |||||
0x0036425c, 0xff008080, /* teal */ | |||||
0xafc8858f, 0xffd8bfd8, /* thistle */ | |||||
0xcc41600a, 0xffff6347, /* tomato */ | |||||
0xfeea9b21, 0xff40e0d0, /* turquoise */ | |||||
0xcf57947f, 0xffee82ee, /* violet */ | |||||
0x06bdbae7, 0xfff5deb3, /* wheat */ | |||||
0x10802ee6, 0xfff5f5f5, /* whitesmoke */ | |||||
0xe1b5130f, 0xff9acd32 /* yellowgreen */ | |||||
}; | |||||
const uint32 hash = (uint32) colourName.trim().toLowerCase().hashCode(); | |||||
for (int i = 0; i < numElementsInArray (presets); i += 2) | |||||
if (presets [i] == hash) | |||||
return Colour (presets [i + 1]); | |||||
return defaultColour; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,109 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Contains a set of predefined named colours (mostly standard HTML colours) | |||||
@see Colour, Colours::greyLevel | |||||
*/ | |||||
class Colours | |||||
{ | |||||
public: | |||||
static JUCE_API const Colour | |||||
//============================================================================== | |||||
transparentBlack, /**< ARGB = 0x00000000 */ | |||||
transparentWhite, /**< ARGB = 0x00ffffff */ | |||||
//============================================================================== | |||||
black, /**< ARGB = 0xff000000 */ | |||||
white, /**< ARGB = 0xffffffff */ | |||||
blue, /**< ARGB = 0xff0000ff */ | |||||
grey, /**< ARGB = 0xff808080 */ | |||||
green, /**< ARGB = 0xff008000 */ | |||||
red, /**< ARGB = 0xffff0000 */ | |||||
yellow, /**< ARGB = 0xffffff00 */ | |||||
//============================================================================== | |||||
aliceblue, antiquewhite, aqua, aquamarine, | |||||
azure, beige, bisque, blanchedalmond, | |||||
blueviolet, brown, burlywood, cadetblue, | |||||
chartreuse, chocolate, coral, cornflowerblue, | |||||
cornsilk, crimson, cyan, darkblue, | |||||
darkcyan, darkgoldenrod, darkgrey, darkgreen, | |||||
darkkhaki, darkmagenta, darkolivegreen, darkorange, | |||||
darkorchid, darkred, darksalmon, darkseagreen, | |||||
darkslateblue, darkslategrey, darkturquoise, darkviolet, | |||||
deeppink, deepskyblue, dimgrey, dodgerblue, | |||||
firebrick, floralwhite, forestgreen, fuchsia, | |||||
gainsboro, ghostwhite, gold, goldenrod, | |||||
greenyellow, honeydew, hotpink, indianred, | |||||
indigo, ivory, khaki, lavender, | |||||
lavenderblush, lawngreen, lemonchiffon, lightblue, | |||||
lightcoral, lightcyan, lightgoldenrodyellow, lightgreen, | |||||
lightgrey, lightpink, lightsalmon, lightseagreen, | |||||
lightskyblue, lightslategrey, lightsteelblue, lightyellow, | |||||
lime, limegreen, linen, magenta, | |||||
maroon, mediumaquamarine, mediumblue, mediumorchid, | |||||
mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, | |||||
mediumturquoise, mediumvioletred, midnightblue, mintcream, | |||||
mistyrose, moccasin, navajowhite, navy, | |||||
oldlace, olive, olivedrab, orange, | |||||
orangered, orchid, palegoldenrod, palegreen, | |||||
paleturquoise, palevioletred, papayawhip, peachpuff, | |||||
peru, pink, plum, powderblue, | |||||
purple, rebeccapurple, rosybrown, royalblue, | |||||
saddlebrown, salmon, sandybrown, seagreen, | |||||
seashell, sienna, silver, skyblue, | |||||
slateblue, slategrey, snow, springgreen, | |||||
steelblue, tan, teal, thistle, | |||||
tomato, turquoise, violet, wheat, | |||||
whitesmoke, yellowgreen; | |||||
/** Attempts to look up a string in the list of known colour names, and return | |||||
the appropriate colour. | |||||
A non-case-sensitive search is made of the list of predefined colours, and | |||||
if a match is found, that colour is returned. If no match is found, the | |||||
colour passed in as the defaultColour parameter is returned. | |||||
*/ | |||||
static JUCE_API Colour findColourForName (const String& colourName, | |||||
Colour defaultColour); | |||||
private: | |||||
//============================================================================== | |||||
// this isn't a class you should ever instantiate - it's just here for the | |||||
// static values in it. | |||||
Colours(); | |||||
JUCE_DECLARE_NON_COPYABLE (Colours) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
FillType::FillType() noexcept | |||||
: colour (0xff000000) | |||||
{ | |||||
} | |||||
FillType::FillType (Colour c) noexcept | |||||
: colour (c) | |||||
{ | |||||
} | |||||
FillType::FillType (const ColourGradient& gradient_) | |||||
: colour (0xff000000), gradient (new ColourGradient (gradient_)) | |||||
{ | |||||
} | |||||
FillType::FillType (const Image& image_, const AffineTransform& transform_) noexcept | |||||
: colour (0xff000000), image (image_), transform (transform_) | |||||
{ | |||||
} | |||||
FillType::FillType (const FillType& other) | |||||
: colour (other.colour), | |||||
gradient (other.gradient.createCopy()), | |||||
image (other.image), | |||||
transform (other.transform) | |||||
{ | |||||
} | |||||
FillType& FillType::operator= (const FillType& other) | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
colour = other.colour; | |||||
gradient = other.gradient.createCopy(); | |||||
image = other.image; | |||||
transform = other.transform; | |||||
} | |||||
return *this; | |||||
} | |||||
FillType::FillType (FillType&& other) noexcept | |||||
: colour (other.colour), | |||||
gradient (other.gradient.release()), | |||||
image (static_cast<Image&&> (other.image)), | |||||
transform (other.transform) | |||||
{ | |||||
} | |||||
FillType& FillType::operator= (FillType&& other) noexcept | |||||
{ | |||||
jassert (this != &other); // hopefully the compiler should make this situation impossible! | |||||
colour = other.colour; | |||||
gradient = other.gradient.release(); | |||||
image = static_cast<Image&&> (other.image); | |||||
transform = other.transform; | |||||
return *this; | |||||
} | |||||
FillType::~FillType() noexcept | |||||
{ | |||||
} | |||||
bool FillType::operator== (const FillType& other) const | |||||
{ | |||||
return colour == other.colour && image == other.image | |||||
&& transform == other.transform | |||||
&& (gradient == other.gradient | |||||
|| (gradient != nullptr && other.gradient != nullptr && *gradient == *other.gradient)); | |||||
} | |||||
bool FillType::operator!= (const FillType& other) const | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
void FillType::setColour (Colour newColour) noexcept | |||||
{ | |||||
gradient = nullptr; | |||||
image = Image(); | |||||
colour = newColour; | |||||
} | |||||
void FillType::setGradient (const ColourGradient& newGradient) | |||||
{ | |||||
if (gradient != nullptr) | |||||
{ | |||||
*gradient = newGradient; | |||||
} | |||||
else | |||||
{ | |||||
image = Image(); | |||||
gradient = new ColourGradient (newGradient); | |||||
colour = Colours::black; | |||||
} | |||||
} | |||||
void FillType::setTiledImage (const Image& image_, const AffineTransform& transform_) noexcept | |||||
{ | |||||
gradient = nullptr; | |||||
image = image_; | |||||
transform = transform_; | |||||
colour = Colours::black; | |||||
} | |||||
void FillType::setOpacity (const float newOpacity) noexcept | |||||
{ | |||||
colour = colour.withAlpha (newOpacity); | |||||
} | |||||
bool FillType::isInvisible() const noexcept | |||||
{ | |||||
return colour.isTransparent() || (gradient != nullptr && gradient->isInvisible()); | |||||
} | |||||
FillType FillType::transformed (const AffineTransform& t) const | |||||
{ | |||||
FillType f (*this); | |||||
f.transform = f.transform.followedBy (t); | |||||
return f; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,150 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a colour or fill pattern to use for rendering paths. | |||||
This is used by the Graphics and DrawablePath classes as a way to encapsulate | |||||
a brush type. It can either be a solid colour, a gradient, or a tiled image. | |||||
@see Graphics::setFillType, DrawablePath::setFill | |||||
*/ | |||||
class JUCE_API FillType | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a default fill type, of solid black. */ | |||||
FillType() noexcept; | |||||
/** Creates a fill type of a solid colour. | |||||
@see setColour | |||||
*/ | |||||
FillType (Colour colour) noexcept; | |||||
/** Creates a gradient fill type. | |||||
@see setGradient | |||||
*/ | |||||
FillType (const ColourGradient& gradient); | |||||
/** Creates a tiled image fill type. The transform allows you to set the scaling, offset | |||||
and rotation of the pattern. | |||||
@see setTiledImage | |||||
*/ | |||||
FillType (const Image& image, const AffineTransform& transform) noexcept; | |||||
/** Creates a copy of another FillType. */ | |||||
FillType (const FillType&); | |||||
/** Makes a copy of another FillType. */ | |||||
FillType& operator= (const FillType&); | |||||
/** Move constructor */ | |||||
FillType (FillType&&) noexcept; | |||||
/** Move assignment operator */ | |||||
FillType& operator= (FillType&&) noexcept; | |||||
/** Destructor. */ | |||||
~FillType() noexcept; | |||||
//============================================================================== | |||||
/** Returns true if this is a solid colour fill, and not a gradient or image. */ | |||||
bool isColour() const noexcept { return gradient == nullptr && image.isNull(); } | |||||
/** Returns true if this is a gradient fill. */ | |||||
bool isGradient() const noexcept { return gradient != nullptr; } | |||||
/** Returns true if this is a tiled image pattern fill. */ | |||||
bool isTiledImage() const noexcept { return image.isValid(); } | |||||
/** Turns this object into a solid colour fill. | |||||
If the object was an image or gradient, those fields will no longer be valid. */ | |||||
void setColour (Colour newColour) noexcept; | |||||
/** Turns this object into a gradient fill. */ | |||||
void setGradient (const ColourGradient& newGradient); | |||||
/** Turns this object into a tiled image fill type. The transform allows you to set | |||||
the scaling, offset and rotation of the pattern. | |||||
*/ | |||||
void setTiledImage (const Image& image, const AffineTransform& transform) noexcept; | |||||
/** Changes the opacity that should be used. | |||||
If the fill is a solid colour, this just changes the opacity of that colour. For | |||||
gradients and image tiles, it changes the opacity that will be used for them. | |||||
*/ | |||||
void setOpacity (float newOpacity) noexcept; | |||||
/** Returns the current opacity to be applied to the colour, gradient, or image. | |||||
@see setOpacity | |||||
*/ | |||||
float getOpacity() const noexcept { return colour.getFloatAlpha(); } | |||||
/** Returns true if this fill type is completely transparent. */ | |||||
bool isInvisible() const noexcept; | |||||
/** Returns a copy of this fill, adding the specified transform applied to the | |||||
existing transform. | |||||
*/ | |||||
FillType transformed (const AffineTransform& transform) const; | |||||
//============================================================================== | |||||
/** The solid colour being used. | |||||
If the fill type is not a solid colour, the alpha channel of this colour indicates | |||||
the opacity that should be used for the fill, and the RGB channels are ignored. | |||||
*/ | |||||
Colour colour; | |||||
/** Returns the gradient that should be used for filling. | |||||
This will be zero if the object is some other type of fill. | |||||
If a gradient is active, the overall opacity with which it should be applied | |||||
is indicated by the alpha channel of the colour variable. | |||||
*/ | |||||
ScopedPointer<ColourGradient> gradient; | |||||
/** The image that should be used for tiling. | |||||
If an image fill is active, the overall opacity with which it should be applied | |||||
is indicated by the alpha channel of the colour variable. | |||||
*/ | |||||
Image image; | |||||
/** The transform that should be applied to the image or gradient that's being drawn. */ | |||||
AffineTransform transform; | |||||
//============================================================================== | |||||
bool operator== (const FillType&) const; | |||||
bool operator!= (const FillType&) const; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (FillType) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,757 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
#if JUCE_MSVC | |||||
#pragma pack (push, 1) | |||||
#endif | |||||
class PixelRGB; | |||||
class PixelAlpha; | |||||
inline uint32 maskPixelComponents (uint32 x) noexcept | |||||
{ | |||||
return (x >> 8) & 0x00ff00ff; | |||||
} | |||||
inline uint32 clampPixelComponents (uint32 x) noexcept | |||||
{ | |||||
return (x | (0x01000100 - maskPixelComponents (x))) & 0x00ff00ff; | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Represents a 32-bit INTERNAL pixel with premultiplied alpha, and can perform compositing | |||||
operations with it. | |||||
This is used internally by the imaging classes. | |||||
@see PixelRGB | |||||
*/ | |||||
class JUCE_API PixelARGB | |||||
{ | |||||
public: | |||||
/** Creates a pixel without defining its colour. */ | |||||
PixelARGB() noexcept {} | |||||
~PixelARGB() noexcept {} | |||||
PixelARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept | |||||
{ | |||||
components.b = b; | |||||
components.g = g; | |||||
components.r = r; | |||||
components.a = a; | |||||
} | |||||
//============================================================================== | |||||
/** Returns a uint32 which represents the pixel in a platform dependent format. */ | |||||
forcedinline uint32 getNativeARGB() const noexcept { return internal; } | |||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||||
forcedinline uint32 getInARGBMaskOrder() const noexcept | |||||
{ | |||||
#if JUCE_ANDROID | |||||
return (uint32) ((components.a << 24) | (components.r << 16) | (components.g << 8) | (components.b << 0)); | |||||
#else | |||||
return getNativeARGB(); | |||||
#endif | |||||
} | |||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||||
inline uint32 getInARGBMemoryOrder() const noexcept | |||||
{ | |||||
#if JUCE_BIG_ENDIAN | |||||
return getInARGBMaskOrder(); | |||||
#else | |||||
return (uint32) ((components.b << 24) | (components.g << 16) | (components.r << 8) | components.a); | |||||
#endif | |||||
} | |||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent. */ | |||||
forcedinline uint32 getEvenBytes() const noexcept { return 0x00ff00ff & internal; } | |||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent. */ | |||||
forcedinline uint32 getOddBytes() const noexcept { return 0x00ff00ff & (internal >> 8); } | |||||
//============================================================================== | |||||
forcedinline uint8 getAlpha() const noexcept { return components.a; } | |||||
forcedinline uint8 getRed() const noexcept { return components.r; } | |||||
forcedinline uint8 getGreen() const noexcept { return components.g; } | |||||
forcedinline uint8 getBlue() const noexcept { return components.b; } | |||||
#if JUCE_GCC | |||||
// NB these are here as a workaround because GCC refuses to bind to packed values. | |||||
forcedinline uint8& getAlpha() noexcept { return comps [indexA]; } | |||||
forcedinline uint8& getRed() noexcept { return comps [indexR]; } | |||||
forcedinline uint8& getGreen() noexcept { return comps [indexG]; } | |||||
forcedinline uint8& getBlue() noexcept { return comps [indexB]; } | |||||
#else | |||||
forcedinline uint8& getAlpha() noexcept { return components.a; } | |||||
forcedinline uint8& getRed() noexcept { return components.r; } | |||||
forcedinline uint8& getGreen() noexcept { return components.g; } | |||||
forcedinline uint8& getBlue() noexcept { return components.b; } | |||||
#endif | |||||
//============================================================================== | |||||
/** Copies another pixel colour over this one. | |||||
This doesn't blend it - this colour is simply replaced by the other one. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void set (const Pixel& src) noexcept | |||||
{ | |||||
internal = src.getNativeARGB(); | |||||
} | |||||
//============================================================================== | |||||
/** Sets the pixel's colour from individual components. */ | |||||
void setARGB (const uint8 a, const uint8 r, const uint8 g, const uint8 b) noexcept | |||||
{ | |||||
components.b = b; | |||||
components.g = g; | |||||
components.r = r; | |||||
components.a = a; | |||||
} | |||||
//============================================================================== | |||||
/** Blends another pixel onto this one. | |||||
This takes into account the opacity of the pixel being overlaid, and blends | |||||
it accordingly. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src) noexcept | |||||
{ | |||||
uint32 rb = src.getEvenBytes(); | |||||
uint32 ag = src.getOddBytes(); | |||||
const uint32 alpha = 0x100 - (ag >> 16); | |||||
rb += maskPixelComponents (getEvenBytes() * alpha); | |||||
ag += maskPixelComponents (getOddBytes() * alpha); | |||||
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8); | |||||
} | |||||
/** Blends another pixel onto this one. | |||||
This takes into account the opacity of the pixel being overlaid, and blends | |||||
it accordingly. | |||||
*/ | |||||
forcedinline void blend (const PixelRGB src) noexcept; | |||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||||
being used, so this can blend semi-transparently from a PixelRGB argument. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||||
{ | |||||
uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); | |||||
uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); | |||||
const uint32 alpha = 0x100 - (ag >> 16); | |||||
rb += maskPixelComponents (getEvenBytes() * alpha); | |||||
ag += maskPixelComponents (getOddBytes() * alpha); | |||||
internal = clampPixelComponents (rb) | (clampPixelComponents (ag) << 8); | |||||
} | |||||
/** Blends another pixel with this one, creating a colour that is somewhere | |||||
between the two, as specified by the amount. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||||
{ | |||||
uint32 dEvenBytes = getEvenBytes(); | |||||
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); | |||||
dEvenBytes &= 0x00ff00ff; | |||||
uint32 dOddBytes = getOddBytes(); | |||||
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); | |||||
dOddBytes &= 0x00ff00ff; | |||||
dOddBytes <<= 8; | |||||
dOddBytes |= dEvenBytes; | |||||
internal = dOddBytes; | |||||
} | |||||
//============================================================================== | |||||
/** Replaces the colour's alpha value with another one. */ | |||||
forcedinline void setAlpha (const uint8 newAlpha) noexcept | |||||
{ | |||||
components.a = newAlpha; | |||||
} | |||||
/** Multiplies the colour's alpha value with another one. */ | |||||
forcedinline void multiplyAlpha (int multiplier) noexcept | |||||
{ | |||||
// increment alpha by 1, so that if multiplier == 255 (full alpha), | |||||
// this function will not change the values. | |||||
++multiplier; | |||||
internal = ((((uint32) multiplier) * getOddBytes()) & 0xff00ff00) | |||||
| (((((uint32) multiplier) * getEvenBytes()) >> 8) & 0x00ff00ff); | |||||
} | |||||
forcedinline void multiplyAlpha (const float multiplier) noexcept | |||||
{ | |||||
multiplyAlpha ((int) (multiplier * 255.0f)); | |||||
} | |||||
inline PixelARGB getUnpremultiplied() const noexcept { PixelARGB p (internal); p.unpremultiply(); return p; } | |||||
/** Premultiplies the pixel's RGB values by its alpha. */ | |||||
forcedinline void premultiply() noexcept | |||||
{ | |||||
const uint32 alpha = components.a; | |||||
if (alpha < 0xff) | |||||
{ | |||||
if (alpha == 0) | |||||
{ | |||||
components.b = 0; | |||||
components.g = 0; | |||||
components.r = 0; | |||||
} | |||||
else | |||||
{ | |||||
components.b = (uint8) ((components.b * alpha + 0x7f) >> 8); | |||||
components.g = (uint8) ((components.g * alpha + 0x7f) >> 8); | |||||
components.r = (uint8) ((components.r * alpha + 0x7f) >> 8); | |||||
} | |||||
} | |||||
} | |||||
/** Unpremultiplies the pixel's RGB values. */ | |||||
forcedinline void unpremultiply() noexcept | |||||
{ | |||||
const uint32 alpha = components.a; | |||||
if (alpha < 0xff) | |||||
{ | |||||
if (alpha == 0) | |||||
{ | |||||
components.b = 0; | |||||
components.g = 0; | |||||
components.r = 0; | |||||
} | |||||
else | |||||
{ | |||||
components.b = (uint8) jmin ((uint32) 0xffu, (components.b * 0xffu) / alpha); | |||||
components.g = (uint8) jmin ((uint32) 0xffu, (components.g * 0xffu) / alpha); | |||||
components.r = (uint8) jmin ((uint32) 0xffu, (components.r * 0xffu) / alpha); | |||||
} | |||||
} | |||||
} | |||||
forcedinline void desaturate() noexcept | |||||
{ | |||||
if (components.a < 0xff && components.a > 0) | |||||
{ | |||||
const int newUnpremultipliedLevel = (0xff * ((int) components.r + (int) components.g + (int) components.b) / (3 * components.a)); | |||||
components.r = components.g = components.b | |||||
= (uint8) ((newUnpremultipliedLevel * components.a + 0x7f) >> 8); | |||||
} | |||||
else | |||||
{ | |||||
components.r = components.g = components.b | |||||
= (uint8) (((int) components.r + (int) components.g + (int) components.b) / 3); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
/** The indexes of the different components in the byte layout of this type of colour. */ | |||||
#if JUCE_ANDROID | |||||
#if JUCE_BIG_ENDIAN | |||||
enum { indexA = 0, indexR = 3, indexG = 2, indexB = 1 }; | |||||
#else | |||||
enum { indexA = 3, indexR = 0, indexG = 1, indexB = 2 }; | |||||
#endif | |||||
#else | |||||
#if JUCE_BIG_ENDIAN | |||||
enum { indexA = 0, indexR = 1, indexG = 2, indexB = 3 }; | |||||
#else | |||||
enum { indexA = 3, indexR = 2, indexG = 1, indexB = 0 }; | |||||
#endif | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
PixelARGB (const uint32 internalValue) noexcept | |||||
: internal (internalValue) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
struct Components | |||||
{ | |||||
#if JUCE_ANDROID | |||||
#if JUCE_BIG_ENDIAN | |||||
uint8 a, b, g, r; | |||||
#else | |||||
uint8 r, g, b, a; | |||||
#endif | |||||
#else | |||||
#if JUCE_BIG_ENDIAN | |||||
uint8 a, r, g, b; | |||||
#else | |||||
uint8 b, g, r, a; | |||||
#endif | |||||
#endif | |||||
} JUCE_PACKED; | |||||
union | |||||
{ | |||||
uint32 internal; | |||||
Components components; | |||||
#if JUCE_GCC | |||||
uint8 comps[4]; // helper struct needed because gcc does not allow references to packed union members | |||||
#endif | |||||
}; | |||||
} | |||||
#ifndef DOXYGEN | |||||
JUCE_PACKED | |||||
#endif | |||||
; | |||||
//============================================================================== | |||||
/** | |||||
Represents a 24-bit RGB pixel, and can perform compositing operations on it. | |||||
This is used internally by the imaging classes. | |||||
@see PixelARGB | |||||
*/ | |||||
class JUCE_API PixelRGB | |||||
{ | |||||
public: | |||||
/** Creates a pixel without defining its colour. */ | |||||
PixelRGB() noexcept {} | |||||
~PixelRGB() noexcept {} | |||||
//============================================================================== | |||||
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible | |||||
with the native format of a PixelARGB. | |||||
@see PixelARGB::getNativeARGB */ | |||||
forcedinline uint32 getNativeARGB() const noexcept | |||||
{ | |||||
#if JUCE_ANDROID | |||||
return (uint32) ((0xff << 24) | r | (g << 8) | (b << 16)); | |||||
#else | |||||
return (uint32) ((0xff << 24) | b | (g << 8) | (r << 16)); | |||||
#endif | |||||
} | |||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||||
forcedinline uint32 getInARGBMaskOrder() const noexcept | |||||
{ | |||||
#if JUCE_ANDROID | |||||
return (uint32) ((0xff << 24) | (r << 16) | (g << 8) | (b << 0)); | |||||
#else | |||||
return getNativeARGB(); | |||||
#endif | |||||
} | |||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||||
inline uint32 getInARGBMemoryOrder() const noexcept | |||||
{ | |||||
#if JUCE_BIG_ENDIAN | |||||
return getInARGBMaskOrder(); | |||||
#else | |||||
return (uint32) ((b << 24) | (g << 16) | (r << 8) | 0xff); | |||||
#endif | |||||
} | |||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent but compatible with the | |||||
return value of getEvenBytes of the PixelARGB class. | |||||
@see PixelARGB::getEvenBytes */ | |||||
forcedinline uint32 getEvenBytes() const noexcept | |||||
{ | |||||
#if JUCE_ANDROID | |||||
return (uint32) (r | (b << 16)); | |||||
#else | |||||
return (uint32) (b | (r << 16)); | |||||
#endif | |||||
} | |||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent but compatible with the | |||||
return value of getOddBytes of the PixelARGB class. | |||||
@see PixelARGB::getOddBytes */ | |||||
forcedinline uint32 getOddBytes() const noexcept { return (uint32)0xff0000 | g; } | |||||
//============================================================================== | |||||
forcedinline uint8 getAlpha() const noexcept { return 0xff; } | |||||
forcedinline uint8 getRed() const noexcept { return r; } | |||||
forcedinline uint8 getGreen() const noexcept { return g; } | |||||
forcedinline uint8 getBlue() const noexcept { return b; } | |||||
forcedinline uint8& getRed() noexcept { return r; } | |||||
forcedinline uint8& getGreen() noexcept { return g; } | |||||
forcedinline uint8& getBlue() noexcept { return b; } | |||||
//============================================================================== | |||||
/** Copies another pixel colour over this one. | |||||
This doesn't blend it - this colour is simply replaced by the other one. | |||||
Because PixelRGB has no alpha channel, any alpha value in the source pixel | |||||
is thrown away. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void set (const Pixel& src) noexcept | |||||
{ | |||||
b = src.getBlue(); | |||||
g = src.getGreen(); | |||||
r = src.getRed(); | |||||
} | |||||
/** Sets the pixel's colour from individual components. */ | |||||
void setARGB (const uint8, const uint8 red, const uint8 green, const uint8 blue) noexcept | |||||
{ | |||||
r = red; | |||||
g = green; | |||||
b = blue; | |||||
} | |||||
//============================================================================== | |||||
/** Blends another pixel onto this one. | |||||
This takes into account the opacity of the pixel being overlaid, and blends | |||||
it accordingly. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src) noexcept | |||||
{ | |||||
const uint32 alpha = (uint32) (0x100 - src.getAlpha()); | |||||
// getEvenBytes returns 0x00rr00bb on non-android | |||||
uint32 rb = clampPixelComponents (src.getEvenBytes() + maskPixelComponents (getEvenBytes() * alpha)); | |||||
// getOddBytes returns 0x00aa00gg on non-android | |||||
uint32 ag = clampPixelComponents (src.getOddBytes() + ((g * alpha) >> 8)); | |||||
g = (uint8) (ag & 0xff); | |||||
#if JUCE_ANDROID | |||||
b = (uint8) (rb >> 16); | |||||
r = (uint8) (rb & 0xff); | |||||
#else | |||||
r = (uint8) (rb >> 16); | |||||
b = (uint8) (rb & 0xff); | |||||
#endif | |||||
} | |||||
forcedinline void blend (const PixelRGB src) noexcept | |||||
{ | |||||
set (src); | |||||
} | |||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||||
being used, so this can blend semi-transparently from a PixelRGB argument. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||||
{ | |||||
uint32 ag = maskPixelComponents (extraAlpha * src.getOddBytes()); | |||||
uint32 rb = maskPixelComponents (extraAlpha * src.getEvenBytes()); | |||||
const uint32 alpha = 0x100 - (ag >> 16); | |||||
ag = clampPixelComponents (ag + (g * alpha >> 8)); | |||||
rb = clampPixelComponents (rb + maskPixelComponents (getEvenBytes() * alpha)); | |||||
g = (uint8) (ag & 0xff); | |||||
#if JUCE_ANDROID | |||||
b = (uint8) (rb >> 16); | |||||
r = (uint8) (rb & 0xff); | |||||
#else | |||||
r = (uint8) (rb >> 16); | |||||
b = (uint8) (rb & 0xff); | |||||
#endif | |||||
} | |||||
/** Blends another pixel with this one, creating a colour that is somewhere | |||||
between the two, as specified by the amount. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||||
{ | |||||
uint32 dEvenBytes = getEvenBytes(); | |||||
dEvenBytes += (((src.getEvenBytes() - dEvenBytes) * amount) >> 8); | |||||
uint32 dOddBytes = getOddBytes(); | |||||
dOddBytes += (((src.getOddBytes() - dOddBytes) * amount) >> 8); | |||||
g = (uint8) (dOddBytes & 0xff); // dOddBytes = 0x00aa00gg | |||||
#if JUCE_ANDROID | |||||
r = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00bb00rr | |||||
b = (uint8) (dEvenBytes >> 16); | |||||
#else | |||||
b = (uint8) (dEvenBytes & 0xff); // dEvenBytes = 0x00rr00bb | |||||
r = (uint8) (dEvenBytes >> 16); | |||||
#endif | |||||
} | |||||
//============================================================================== | |||||
/** This method is included for compatibility with the PixelARGB class. */ | |||||
forcedinline void setAlpha (const uint8) noexcept {} | |||||
/** Multiplies the colour's alpha value with another one. */ | |||||
forcedinline void multiplyAlpha (int) noexcept {} | |||||
/** Multiplies the colour's alpha value with another one. */ | |||||
forcedinline void multiplyAlpha (float) noexcept {} | |||||
/** Premultiplies the pixel's RGB values by its alpha. */ | |||||
forcedinline void premultiply() noexcept {} | |||||
/** Unpremultiplies the pixel's RGB values. */ | |||||
forcedinline void unpremultiply() noexcept {} | |||||
forcedinline void desaturate() noexcept | |||||
{ | |||||
r = g = b = (uint8) (((int) r + (int) g + (int) b) / 3); | |||||
} | |||||
//============================================================================== | |||||
/** The indexes of the different components in the byte layout of this type of colour. */ | |||||
#if JUCE_MAC | |||||
enum { indexR = 0, indexG = 1, indexB = 2 }; | |||||
#else | |||||
enum { indexR = 2, indexG = 1, indexB = 0 }; | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
PixelRGB (const uint32 internal) noexcept | |||||
{ | |||||
#if JUCE_ANDROID | |||||
b = (uint8) (internal >> 16); | |||||
g = (uint8) (internal >> 8); | |||||
r = (uint8) (internal); | |||||
#else | |||||
r = (uint8) (internal >> 16); | |||||
g = (uint8) (internal >> 8); | |||||
b = (uint8) (internal); | |||||
#endif | |||||
} | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
uint8 r, g, b; | |||||
#else | |||||
uint8 b, g, r; | |||||
#endif | |||||
} | |||||
#ifndef DOXYGEN | |||||
JUCE_PACKED | |||||
#endif | |||||
; | |||||
forcedinline void PixelARGB::blend (const PixelRGB src) noexcept | |||||
{ | |||||
set (src); | |||||
} | |||||
//============================================================================== | |||||
/** | |||||
Represents an 8-bit single-channel pixel, and can perform compositing operations on it. | |||||
This is used internally by the imaging classes. | |||||
@see PixelARGB, PixelRGB | |||||
*/ | |||||
class JUCE_API PixelAlpha | |||||
{ | |||||
public: | |||||
/** Creates a pixel without defining its colour. */ | |||||
PixelAlpha() noexcept {} | |||||
~PixelAlpha() noexcept {} | |||||
//============================================================================== | |||||
/** Returns a uint32 which represents the pixel in a platform dependent format which is compatible | |||||
with the native format of a PixelARGB. | |||||
@see PixelARGB::getNativeARGB */ | |||||
forcedinline uint32 getNativeARGB() const noexcept { return (uint32) ((a << 24) | (a << 16) | (a << 8) | a); } | |||||
/** Returns a uint32 which will be in argb order as if constructed with the following mask operation | |||||
((alpha << 24) | (red << 16) | (green << 8) | blue). */ | |||||
forcedinline uint32 getInARGBMaskOrder() const noexcept { return getNativeARGB(); } | |||||
/** Returns a uint32 which when written to memory, will be in the order a, r, g, b. In other words, | |||||
if the return-value is read as a uint8 array then the elements will be in the order of a, r, g, b*/ | |||||
inline uint32 getInARGBMemoryOrder() const noexcept { return getNativeARGB(); } | |||||
/** Return channels with an even index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent but compatible with the | |||||
return value of getEvenBytes of the PixelARGB class. | |||||
@see PixelARGB::getEvenBytes */ | |||||
forcedinline uint32 getEvenBytes() const noexcept { return (uint32) ((a << 16) | a); } | |||||
/** Return channels with an odd index and insert zero bytes between them. This is useful for blending | |||||
operations. The exact channels which are returned is platform dependent but compatible with the | |||||
return value of getOddBytes of the PixelARGB class. | |||||
@see PixelARGB::getOddBytes */ | |||||
forcedinline uint32 getOddBytes() const noexcept { return (uint32) ((a << 16) | a); } | |||||
//============================================================================== | |||||
forcedinline uint8 getAlpha() const noexcept { return a; } | |||||
forcedinline uint8& getAlpha() noexcept { return a; } | |||||
forcedinline uint8 getRed() const noexcept { return 0; } | |||||
forcedinline uint8 getGreen() const noexcept { return 0; } | |||||
forcedinline uint8 getBlue() const noexcept { return 0; } | |||||
//============================================================================== | |||||
/** Copies another pixel colour over this one. | |||||
This doesn't blend it - this colour is simply replaced by the other one. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void set (const Pixel& src) noexcept | |||||
{ | |||||
a = src.getAlpha(); | |||||
} | |||||
/** Sets the pixel's colour from individual components. */ | |||||
forcedinline void setARGB (const uint8 a_, const uint8 /*r*/, const uint8 /*g*/, const uint8 /*b*/) noexcept | |||||
{ | |||||
a = a_; | |||||
} | |||||
//============================================================================== | |||||
/** Blends another pixel onto this one. | |||||
This takes into account the opacity of the pixel being overlaid, and blends | |||||
it accordingly. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src) noexcept | |||||
{ | |||||
const int srcA = src.getAlpha(); | |||||
a = (uint8) ((a * (0x100 - srcA) >> 8) + srcA); | |||||
} | |||||
/** Blends another pixel onto this one, applying an extra multiplier to its opacity. | |||||
The opacity of the pixel being overlaid is scaled by the extraAlpha factor before | |||||
being used, so this can blend semi-transparently from a PixelRGB argument. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void blend (const Pixel& src, uint32 extraAlpha) noexcept | |||||
{ | |||||
++extraAlpha; | |||||
const int srcAlpha = (int) ((extraAlpha * src.getAlpha()) >> 8); | |||||
a = (uint8) ((a * (0x100 - srcAlpha) >> 8) + srcAlpha); | |||||
} | |||||
/** Blends another pixel with this one, creating a colour that is somewhere | |||||
between the two, as specified by the amount. | |||||
*/ | |||||
template <class Pixel> | |||||
forcedinline void tween (const Pixel& src, const uint32 amount) noexcept | |||||
{ | |||||
a += ((src.getAlpha() - a) * amount) >> 8; | |||||
} | |||||
//============================================================================== | |||||
/** Replaces the colour's alpha value with another one. */ | |||||
forcedinline void setAlpha (const uint8 newAlpha) noexcept | |||||
{ | |||||
a = newAlpha; | |||||
} | |||||
/** Multiplies the colour's alpha value with another one. */ | |||||
forcedinline void multiplyAlpha (int multiplier) noexcept | |||||
{ | |||||
++multiplier; | |||||
a = (uint8) ((a * multiplier) >> 8); | |||||
} | |||||
forcedinline void multiplyAlpha (const float multiplier) noexcept | |||||
{ | |||||
a = (uint8) (a * multiplier); | |||||
} | |||||
/** Premultiplies the pixel's RGB values by its alpha. */ | |||||
forcedinline void premultiply() noexcept {} | |||||
/** Unpremultiplies the pixel's RGB values. */ | |||||
forcedinline void unpremultiply() noexcept {} | |||||
forcedinline void desaturate() noexcept {} | |||||
//============================================================================== | |||||
/** The indexes of the different components in the byte layout of this type of colour. */ | |||||
enum { indexA = 0 }; | |||||
private: | |||||
//============================================================================== | |||||
PixelAlpha (const uint32 internal) noexcept | |||||
{ | |||||
a = (uint8) (internal >> 24); | |||||
} | |||||
//============================================================================== | |||||
uint8 a; | |||||
} | |||||
#ifndef DOXYGEN | |||||
JUCE_PACKED | |||||
#endif | |||||
; | |||||
#if JUCE_MSVC | |||||
#pragma pack (pop) | |||||
#endif | |||||
} // namespace juce |
@@ -0,0 +1,699 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace | |||||
{ | |||||
template <typename Type> | |||||
Rectangle<Type> coordsToRectangle (Type x, Type y, Type w, Type h) noexcept | |||||
{ | |||||
#if JUCE_DEBUG | |||||
const int maxVal = 0x3fffffff; | |||||
jassert ((int) x >= -maxVal && (int) x <= maxVal | |||||
&& (int) y >= -maxVal && (int) y <= maxVal | |||||
&& (int) w >= 0 && (int) w <= maxVal | |||||
&& (int) h >= 0 && (int) h <= maxVal); | |||||
#endif | |||||
return { x, y, w, h }; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
LowLevelGraphicsContext::LowLevelGraphicsContext() {} | |||||
LowLevelGraphicsContext::~LowLevelGraphicsContext() {} | |||||
//============================================================================== | |||||
Graphics::Graphics (const Image& imageToDrawOnto) | |||||
: context (*imageToDrawOnto.createLowLevelContext()), | |||||
contextToDelete (&context) | |||||
{ | |||||
jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | |||||
} | |||||
Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept | |||||
: context (internalContext) | |||||
{ | |||||
} | |||||
Graphics::~Graphics() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void Graphics::resetToDefaultState() | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setFill (FillType()); | |||||
context.setFont (Font()); | |||||
context.setInterpolationQuality (Graphics::mediumResamplingQuality); | |||||
} | |||||
bool Graphics::isVectorDevice() const | |||||
{ | |||||
return context.isVectorDevice(); | |||||
} | |||||
bool Graphics::reduceClipRegion (Rectangle<int> area) | |||||
{ | |||||
saveStateIfPending(); | |||||
return context.clipToRectangle (area); | |||||
} | |||||
bool Graphics::reduceClipRegion (int x, int y, int w, int h) | |||||
{ | |||||
return reduceClipRegion (coordsToRectangle (x, y, w, h)); | |||||
} | |||||
bool Graphics::reduceClipRegion (const RectangleList<int>& clipRegion) | |||||
{ | |||||
saveStateIfPending(); | |||||
return context.clipToRectangleList (clipRegion); | |||||
} | |||||
bool Graphics::reduceClipRegion (const Path& path, const AffineTransform& transform) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.clipToPath (path, transform); | |||||
return ! context.isClipEmpty(); | |||||
} | |||||
bool Graphics::reduceClipRegion (const Image& image, const AffineTransform& transform) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.clipToImageAlpha (image, transform); | |||||
return ! context.isClipEmpty(); | |||||
} | |||||
void Graphics::excludeClipRegion (Rectangle<int> rectangleToExclude) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.excludeClipRectangle (rectangleToExclude); | |||||
} | |||||
bool Graphics::isClipEmpty() const | |||||
{ | |||||
return context.isClipEmpty(); | |||||
} | |||||
Rectangle<int> Graphics::getClipBounds() const | |||||
{ | |||||
return context.getClipBounds(); | |||||
} | |||||
void Graphics::saveState() | |||||
{ | |||||
saveStateIfPending(); | |||||
saveStatePending = true; | |||||
} | |||||
void Graphics::restoreState() | |||||
{ | |||||
if (saveStatePending) | |||||
saveStatePending = false; | |||||
else | |||||
context.restoreState(); | |||||
} | |||||
void Graphics::saveStateIfPending() | |||||
{ | |||||
if (saveStatePending) | |||||
{ | |||||
saveStatePending = false; | |||||
context.saveState(); | |||||
} | |||||
} | |||||
void Graphics::setOrigin (Point<int> newOrigin) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setOrigin (newOrigin); | |||||
} | |||||
void Graphics::setOrigin (int x, int y) | |||||
{ | |||||
setOrigin ({ x, y }); | |||||
} | |||||
void Graphics::addTransform (const AffineTransform& transform) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.addTransform (transform); | |||||
} | |||||
bool Graphics::clipRegionIntersects (Rectangle<int> area) const | |||||
{ | |||||
return context.clipRegionIntersects (area); | |||||
} | |||||
void Graphics::beginTransparencyLayer (float layerOpacity) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.beginTransparencyLayer (layerOpacity); | |||||
} | |||||
void Graphics::endTransparencyLayer() | |||||
{ | |||||
context.endTransparencyLayer(); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::setColour (Colour newColour) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setFill (newColour); | |||||
} | |||||
void Graphics::setOpacity (const float newOpacity) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setOpacity (newOpacity); | |||||
} | |||||
void Graphics::setGradientFill (const ColourGradient& gradient) | |||||
{ | |||||
setFillType (gradient); | |||||
} | |||||
void Graphics::setTiledImageFill (const Image& imageToUse, const int anchorX, const int anchorY, const float opacity) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setFill (FillType (imageToUse, AffineTransform::translation ((float) anchorX, (float) anchorY))); | |||||
context.setOpacity (opacity); | |||||
} | |||||
void Graphics::setFillType (const FillType& newFill) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setFill (newFill); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::setFont (const Font& newFont) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setFont (newFont); | |||||
} | |||||
void Graphics::setFont (const float newFontHeight) | |||||
{ | |||||
setFont (context.getFont().withHeight (newFontHeight)); | |||||
} | |||||
Font Graphics::getCurrentFont() const | |||||
{ | |||||
return context.getFont(); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, | |||||
Justification justification) const | |||||
{ | |||||
if (text.isNotEmpty()) | |||||
{ | |||||
// Don't pass any vertical placement flags to this method - they'll be ignored. | |||||
jassert (justification.getOnlyVerticalFlags() == 0); | |||||
auto flags = justification.getOnlyHorizontalFlags(); | |||||
if (flags == Justification::right && startX < context.getClipBounds().getX()) | |||||
return; | |||||
if (flags == Justification::left && startX > context.getClipBounds().getRight()) | |||||
return; | |||||
GlyphArrangement arr; | |||||
arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); | |||||
if (flags != Justification::left) | |||||
{ | |||||
auto w = arr.getBoundingBox (0, -1, true).getWidth(); | |||||
if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) | |||||
w /= 2.0f; | |||||
arr.draw (*this, AffineTransform::translation (-w, 0)); | |||||
} | |||||
else | |||||
{ | |||||
arr.draw (*this); | |||||
} | |||||
} | |||||
} | |||||
void Graphics::drawMultiLineText (const String& text, const int startX, | |||||
const int baselineY, const int maximumLineWidth) const | |||||
{ | |||||
if (text.isNotEmpty() | |||||
&& startX < context.getClipBounds().getRight()) | |||||
{ | |||||
GlyphArrangement arr; | |||||
arr.addJustifiedText (context.getFont(), text, | |||||
(float) startX, (float) baselineY, (float) maximumLineWidth, | |||||
Justification::left); | |||||
arr.draw (*this); | |||||
} | |||||
} | |||||
void Graphics::drawText (const String& text, Rectangle<float> area, | |||||
Justification justificationType, bool useEllipsesIfTooBig) const | |||||
{ | |||||
if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) | |||||
{ | |||||
GlyphArrangement arr; | |||||
arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, | |||||
area.getWidth(), useEllipsesIfTooBig); | |||||
arr.justifyGlyphs (0, arr.getNumGlyphs(), | |||||
area.getX(), area.getY(), area.getWidth(), area.getHeight(), | |||||
justificationType); | |||||
arr.draw (*this); | |||||
} | |||||
} | |||||
void Graphics::drawText (const String& text, Rectangle<int> area, | |||||
Justification justificationType, bool useEllipsesIfTooBig) const | |||||
{ | |||||
drawText (text, area.toFloat(), justificationType, useEllipsesIfTooBig); | |||||
} | |||||
void Graphics::drawText (const String& text, int x, int y, int width, int height, | |||||
Justification justificationType, const bool useEllipsesIfTooBig) const | |||||
{ | |||||
drawText (text, coordsToRectangle (x, y, width, height), justificationType, useEllipsesIfTooBig); | |||||
} | |||||
void Graphics::drawFittedText (const String& text, Rectangle<int> area, | |||||
Justification justification, | |||||
const int maximumNumberOfLines, | |||||
const float minimumHorizontalScale) const | |||||
{ | |||||
if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area)) | |||||
{ | |||||
GlyphArrangement arr; | |||||
arr.addFittedText (context.getFont(), text, | |||||
(float) area.getX(), (float) area.getY(), | |||||
(float) area.getWidth(), (float) area.getHeight(), | |||||
justification, | |||||
maximumNumberOfLines, | |||||
minimumHorizontalScale); | |||||
arr.draw (*this); | |||||
} | |||||
} | |||||
void Graphics::drawFittedText (const String& text, int x, int y, int width, int height, | |||||
Justification justification, | |||||
const int maximumNumberOfLines, | |||||
const float minimumHorizontalScale) const | |||||
{ | |||||
drawFittedText (text, coordsToRectangle (x, y, width, height), | |||||
justification, maximumNumberOfLines, minimumHorizontalScale); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::fillRect (Rectangle<int> r) const | |||||
{ | |||||
context.fillRect (r, false); | |||||
} | |||||
void Graphics::fillRect (Rectangle<float> r) const | |||||
{ | |||||
context.fillRect (r); | |||||
} | |||||
void Graphics::fillRect (int x, int y, int width, int height) const | |||||
{ | |||||
context.fillRect (coordsToRectangle (x, y, width, height), false); | |||||
} | |||||
void Graphics::fillRect (float x, float y, float width, float height) const | |||||
{ | |||||
fillRect (coordsToRectangle (x, y, width, height)); | |||||
} | |||||
void Graphics::fillRectList (const RectangleList<float>& rectangles) const | |||||
{ | |||||
context.fillRectList (rectangles); | |||||
} | |||||
void Graphics::fillRectList (const RectangleList<int>& rects) const | |||||
{ | |||||
for (auto& r : rects) | |||||
context.fillRect (r, false); | |||||
} | |||||
void Graphics::fillAll() const | |||||
{ | |||||
fillRect (context.getClipBounds()); | |||||
} | |||||
void Graphics::fillAll (Colour colourToUse) const | |||||
{ | |||||
if (! colourToUse.isTransparent()) | |||||
{ | |||||
auto clip = context.getClipBounds(); | |||||
context.saveState(); | |||||
context.setFill (colourToUse); | |||||
context.fillRect (clip, false); | |||||
context.restoreState(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void Graphics::fillPath (const Path& path) const | |||||
{ | |||||
if (! (context.isClipEmpty() || path.isEmpty())) | |||||
context.fillPath (path, AffineTransform()); | |||||
} | |||||
void Graphics::fillPath (const Path& path, const AffineTransform& transform) const | |||||
{ | |||||
if (! (context.isClipEmpty() || path.isEmpty())) | |||||
context.fillPath (path, transform); | |||||
} | |||||
void Graphics::strokePath (const Path& path, | |||||
const PathStrokeType& strokeType, | |||||
const AffineTransform& transform) const | |||||
{ | |||||
Path stroke; | |||||
strokeType.createStrokedPath (stroke, path, transform, context.getPhysicalPixelScaleFactor()); | |||||
fillPath (stroke); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::drawRect (float x, float y, float width, float height, float lineThickness) const | |||||
{ | |||||
drawRect (coordsToRectangle (x, y, width, height), lineThickness); | |||||
} | |||||
void Graphics::drawRect (int x, int y, int width, int height, int lineThickness) const | |||||
{ | |||||
drawRect (coordsToRectangle (x, y, width, height), lineThickness); | |||||
} | |||||
void Graphics::drawRect (Rectangle<int> r, int lineThickness) const | |||||
{ | |||||
drawRect (r.toFloat(), (float) lineThickness); | |||||
} | |||||
void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const | |||||
{ | |||||
jassert (r.getWidth() >= 0.0f && r.getHeight() >= 0.0f); | |||||
RectangleList<float> rects; | |||||
rects.addWithoutMerging (r.removeFromTop (lineThickness)); | |||||
rects.addWithoutMerging (r.removeFromBottom (lineThickness)); | |||||
rects.addWithoutMerging (r.removeFromLeft (lineThickness)); | |||||
rects.addWithoutMerging (r.removeFromRight (lineThickness)); | |||||
context.fillRectList (rects); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::fillEllipse (Rectangle<float> area) const | |||||
{ | |||||
Path p; | |||||
p.addEllipse (area); | |||||
fillPath (p); | |||||
} | |||||
void Graphics::fillEllipse (float x, float y, float w, float h) const | |||||
{ | |||||
fillEllipse (coordsToRectangle (x, y, w, h)); | |||||
} | |||||
void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const | |||||
{ | |||||
drawEllipse (coordsToRectangle (x, y, width, height), lineThickness); | |||||
} | |||||
void Graphics::drawEllipse (Rectangle<float> area, float lineThickness) const | |||||
{ | |||||
Path p; | |||||
if (area.getWidth() == area.getHeight()) | |||||
{ | |||||
// For a circle, we can avoid having to generate a stroke | |||||
p.addEllipse (area.expanded (lineThickness * 0.5f)); | |||||
p.addEllipse (area.reduced (lineThickness * 0.5f)); | |||||
p.setUsingNonZeroWinding (false); | |||||
fillPath (p); | |||||
} | |||||
else | |||||
{ | |||||
p.addEllipse (area); | |||||
strokePath (p, PathStrokeType (lineThickness)); | |||||
} | |||||
} | |||||
void Graphics::fillRoundedRectangle (float x, float y, float width, float height, float cornerSize) const | |||||
{ | |||||
fillRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize); | |||||
} | |||||
void Graphics::fillRoundedRectangle (Rectangle<float> r, const float cornerSize) const | |||||
{ | |||||
Path p; | |||||
p.addRoundedRectangle (r, cornerSize); | |||||
fillPath (p); | |||||
} | |||||
void Graphics::drawRoundedRectangle (float x, float y, float width, float height, | |||||
float cornerSize, float lineThickness) const | |||||
{ | |||||
drawRoundedRectangle (coordsToRectangle (x, y, width, height), cornerSize, lineThickness); | |||||
} | |||||
void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float lineThickness) const | |||||
{ | |||||
Path p; | |||||
p.addRoundedRectangle (r, cornerSize); | |||||
strokePath (p, PathStrokeType (lineThickness)); | |||||
} | |||||
void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const | |||||
{ | |||||
Path p; | |||||
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | |||||
fillPath (p); | |||||
} | |||||
void Graphics::fillCheckerBoard (Rectangle<int> area, | |||||
const int checkWidth, const int checkHeight, | |||||
Colour colour1, Colour colour2) const | |||||
{ | |||||
jassert (checkWidth > 0 && checkHeight > 0); // can't be zero or less! | |||||
if (checkWidth > 0 && checkHeight > 0) | |||||
{ | |||||
context.saveState(); | |||||
if (colour1 == colour2) | |||||
{ | |||||
context.setFill (colour1); | |||||
context.fillRect (area, false); | |||||
} | |||||
else | |||||
{ | |||||
auto clipped = context.getClipBounds().getIntersection (area); | |||||
if (! clipped.isEmpty()) | |||||
{ | |||||
context.clipToRectangle (clipped); | |||||
const int checkNumX = (clipped.getX() - area.getX()) / checkWidth; | |||||
const int checkNumY = (clipped.getY() - area.getY()) / checkHeight; | |||||
const int startX = area.getX() + checkNumX * checkWidth; | |||||
const int startY = area.getY() + checkNumY * checkHeight; | |||||
const int right = clipped.getRight(); | |||||
const int bottom = clipped.getBottom(); | |||||
for (int i = 0; i < 2; ++i) | |||||
{ | |||||
context.setFill (i == ((checkNumX ^ checkNumY) & 1) ? colour1 : colour2); | |||||
int cy = i; | |||||
for (int y = startY; y < bottom; y += checkHeight) | |||||
for (int x = startX + (cy++ & 1) * checkWidth; x < right; x += checkWidth * 2) | |||||
context.fillRect (Rectangle<int> (x, y, checkWidth, checkHeight), false); | |||||
} | |||||
} | |||||
} | |||||
context.restoreState(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void Graphics::drawVerticalLine (const int x, float top, float bottom) const | |||||
{ | |||||
if (top < bottom) | |||||
context.fillRect (Rectangle<float> ((float) x, top, 1.0f, bottom - top)); | |||||
} | |||||
void Graphics::drawHorizontalLine (const int y, float left, float right) const | |||||
{ | |||||
if (left < right) | |||||
context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f)); | |||||
} | |||||
void Graphics::drawLine (Line<float> line) const | |||||
{ | |||||
context.drawLine (line); | |||||
} | |||||
void Graphics::drawLine (float x1, float y1, float x2, float y2) const | |||||
{ | |||||
context.drawLine (Line<float> (x1, y1, x2, y2)); | |||||
} | |||||
void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThickness) const | |||||
{ | |||||
drawLine (Line<float> (x1, y1, x2, y2), lineThickness); | |||||
} | |||||
void Graphics::drawLine (Line<float> line, const float lineThickness) const | |||||
{ | |||||
Path p; | |||||
p.addLineSegment (line, lineThickness); | |||||
fillPath (p); | |||||
} | |||||
void Graphics::drawDashedLine (Line<float> line, const float* dashLengths, | |||||
int numDashLengths, float lineThickness, int n) const | |||||
{ | |||||
jassert (n >= 0 && n < numDashLengths); // your start index must be valid! | |||||
const Point<double> delta ((line.getEnd() - line.getStart()).toDouble()); | |||||
const double totalLen = delta.getDistanceFromOrigin(); | |||||
if (totalLen >= 0.1) | |||||
{ | |||||
const double onePixAlpha = 1.0 / totalLen; | |||||
for (double alpha = 0.0; alpha < 1.0;) | |||||
{ | |||||
jassert (dashLengths[n] > 0); // can't have zero-length dashes! | |||||
const double lastAlpha = alpha; | |||||
alpha += dashLengths [n] * onePixAlpha; | |||||
n = (n + 1) % numDashLengths; | |||||
if ((n & 1) != 0) | |||||
{ | |||||
const Line<float> segment (line.getStart() + (delta * lastAlpha).toFloat(), | |||||
line.getStart() + (delta * jmin (1.0, alpha)).toFloat()); | |||||
if (lineThickness != 1.0f) | |||||
drawLine (segment, lineThickness); | |||||
else | |||||
context.drawLine (segment); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void Graphics::setImageResamplingQuality (const Graphics::ResamplingQuality newQuality) | |||||
{ | |||||
saveStateIfPending(); | |||||
context.setInterpolationQuality (newQuality); | |||||
} | |||||
//============================================================================== | |||||
void Graphics::drawImageAt (const Image& imageToDraw, int x, int y, bool fillAlphaChannel) const | |||||
{ | |||||
drawImageTransformed (imageToDraw, | |||||
AffineTransform::translation ((float) x, (float) y), | |||||
fillAlphaChannel); | |||||
} | |||||
void Graphics::drawImage (const Image& imageToDraw, Rectangle<float> targetArea, | |||||
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const | |||||
{ | |||||
if (imageToDraw.isValid()) | |||||
drawImageTransformed (imageToDraw, | |||||
placementWithinTarget.getTransformToFit (imageToDraw.getBounds().toFloat(), targetArea), | |||||
fillAlphaChannelWithCurrentBrush); | |||||
} | |||||
void Graphics::drawImageWithin (const Image& imageToDraw, int dx, int dy, int dw, int dh, | |||||
RectanglePlacement placementWithinTarget, bool fillAlphaChannelWithCurrentBrush) const | |||||
{ | |||||
drawImage (imageToDraw, coordsToRectangle (dx, dy, dw, dh).toFloat(), | |||||
placementWithinTarget, fillAlphaChannelWithCurrentBrush); | |||||
} | |||||
void Graphics::drawImage (const Image& imageToDraw, | |||||
int dx, int dy, int dw, int dh, | |||||
int sx, int sy, int sw, int sh, | |||||
const bool fillAlphaChannelWithCurrentBrush) const | |||||
{ | |||||
if (imageToDraw.isValid() && context.clipRegionIntersects (coordsToRectangle (dx, dy, dw, dh))) | |||||
drawImageTransformed (imageToDraw.getClippedImage (coordsToRectangle (sx, sy, sw, sh)), | |||||
AffineTransform::scale (dw / (float) sw, dh / (float) sh) | |||||
.translated ((float) dx, (float) dy), | |||||
fillAlphaChannelWithCurrentBrush); | |||||
} | |||||
void Graphics::drawImageTransformed (const Image& imageToDraw, | |||||
const AffineTransform& transform, | |||||
const bool fillAlphaChannelWithCurrentBrush) const | |||||
{ | |||||
if (imageToDraw.isValid() && ! context.isClipEmpty()) | |||||
{ | |||||
if (fillAlphaChannelWithCurrentBrush) | |||||
{ | |||||
context.saveState(); | |||||
context.clipToImageAlpha (imageToDraw, transform); | |||||
fillAll(); | |||||
context.restoreState(); | |||||
} | |||||
else | |||||
{ | |||||
context.drawImage (imageToDraw, transform); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
Graphics::ScopedSaveState::ScopedSaveState (Graphics& g) : context (g) | |||||
{ | |||||
context.saveState(); | |||||
} | |||||
Graphics::ScopedSaveState::~ScopedSaveState() | |||||
{ | |||||
context.restoreState(); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,746 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A graphics context, used for drawing a component or image. | |||||
When a Component needs painting, a Graphics context is passed to its | |||||
Component::paint() method, and this you then call methods within this | |||||
object to actually draw the component's content. | |||||
A Graphics can also be created from an image, to allow drawing directly onto | |||||
that image. | |||||
@see Component::paint | |||||
*/ | |||||
class JUCE_API Graphics | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a Graphics object to draw directly onto the given image. | |||||
The graphics object that is created will be set up to draw onto the image, | |||||
with the context's clipping area being the entire size of the image, and its | |||||
origin being the image's origin. To draw into a subsection of an image, use the | |||||
reduceClipRegion() and setOrigin() methods. | |||||
Obviously you shouldn't delete the image before this context is deleted. | |||||
*/ | |||||
explicit Graphics (const Image& imageToDrawOnto); | |||||
/** Destructor. */ | |||||
~Graphics(); | |||||
//============================================================================== | |||||
/** Changes the current drawing colour. | |||||
This sets the colour that will now be used for drawing operations - it also | |||||
sets the opacity to that of the colour passed-in. | |||||
If a brush is being used when this method is called, the brush will be deselected, | |||||
and any subsequent drawing will be done with a solid colour brush instead. | |||||
@see setOpacity | |||||
*/ | |||||
void setColour (Colour newColour); | |||||
/** Changes the opacity to use with the current colour. | |||||
If a solid colour is being used for drawing, this changes its opacity | |||||
to this new value (i.e. it doesn't multiply the colour's opacity by this amount). | |||||
If a gradient is being used, this will have no effect on it. | |||||
A value of 0.0 is completely transparent, 1.0 is completely opaque. | |||||
*/ | |||||
void setOpacity (float newOpacity); | |||||
/** Sets the context to use a gradient for its fill pattern. | |||||
*/ | |||||
void setGradientFill (const ColourGradient& gradient); | |||||
/** Sets the context to use a tiled image pattern for filling. | |||||
Make sure that you don't delete this image while it's still being used by | |||||
this context! | |||||
*/ | |||||
void setTiledImageFill (const Image& imageToUse, | |||||
int anchorX, int anchorY, | |||||
float opacity); | |||||
/** Changes the current fill settings. | |||||
@see setColour, setGradientFill, setTiledImageFill | |||||
*/ | |||||
void setFillType (const FillType& newFill); | |||||
//============================================================================== | |||||
/** Changes the font to use for subsequent text-drawing functions. | |||||
@see drawSingleLineText, drawMultiLineText, drawText, drawFittedText | |||||
*/ | |||||
void setFont (const Font& newFont); | |||||
/** Changes the size of the currently-selected font. | |||||
This is a convenient shortcut that changes the context's current font to a | |||||
different size. The typeface won't be changed. | |||||
@see Font | |||||
*/ | |||||
void setFont (float newFontHeight); | |||||
/** Returns the currently selected font. */ | |||||
Font getCurrentFont() const; | |||||
/** Draws a one-line text string. | |||||
This will use the current colour (or brush) to fill the text. The font is the last | |||||
one specified by setFont(). | |||||
@param text the string to draw | |||||
@param startX the position to draw the left-hand edge of the text | |||||
@param baselineY the position of the text's baseline | |||||
@param justification the horizontal flags indicate which end of the text string is | |||||
anchored at the specified point. | |||||
@see drawMultiLineText, drawText, drawFittedText, GlyphArrangement::addLineOfText | |||||
*/ | |||||
void drawSingleLineText (const String& text, | |||||
int startX, int baselineY, | |||||
Justification justification = Justification::left) const; | |||||
/** Draws text across multiple lines. | |||||
This will break the text onto a new line where there's a new-line or | |||||
carriage-return character, or at a word-boundary when the text becomes wider | |||||
than the size specified by the maximumLineWidth parameter. | |||||
@see setFont, drawSingleLineText, drawFittedText, GlyphArrangement::addJustifiedText | |||||
*/ | |||||
void drawMultiLineText (const String& text, | |||||
int startX, int baselineY, | |||||
int maximumLineWidth) const; | |||||
/** Draws a line of text within a specified rectangle. | |||||
The text will be positioned within the rectangle based on the justification | |||||
flags passed-in. If the string is too long to fit inside the rectangle, it will | |||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||||
flag is true). | |||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||||
*/ | |||||
void drawText (const String& text, | |||||
int x, int y, int width, int height, | |||||
Justification justificationType, | |||||
bool useEllipsesIfTooBig = true) const; | |||||
/** Draws a line of text within a specified rectangle. | |||||
The text will be positioned within the rectangle based on the justification | |||||
flags passed-in. If the string is too long to fit inside the rectangle, it will | |||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||||
flag is true). | |||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||||
*/ | |||||
void drawText (const String& text, | |||||
Rectangle<int> area, | |||||
Justification justificationType, | |||||
bool useEllipsesIfTooBig = true) const; | |||||
/** Draws a line of text within a specified rectangle. | |||||
The text will be positioned within the rectangle based on the justification | |||||
flags passed-in. If the string is too long to fit inside the rectangle, it will | |||||
either be truncated or will have ellipsis added to its end (if the useEllipsesIfTooBig | |||||
flag is true). | |||||
@see drawSingleLineText, drawFittedText, drawMultiLineText, GlyphArrangement::addJustifiedText | |||||
*/ | |||||
void drawText (const String& text, | |||||
Rectangle<float> area, | |||||
Justification justificationType, | |||||
bool useEllipsesIfTooBig = true) const; | |||||
/** Tries to draw a text string inside a given space. | |||||
This does its best to make the given text readable within the specified rectangle, | |||||
so it useful for labelling things. | |||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||||
it's been truncated. | |||||
A Justification parameter lets you specify how the text is laid out within the rectangle, | |||||
both horizontally and vertically. | |||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||||
can set this value to 1.0f. Pass 0 if you want it to use a default value. | |||||
@see GlyphArrangement::addFittedText | |||||
*/ | |||||
void drawFittedText (const String& text, | |||||
int x, int y, int width, int height, | |||||
Justification justificationFlags, | |||||
int maximumNumberOfLines, | |||||
float minimumHorizontalScale = 0.0f) const; | |||||
/** Tries to draw a text string inside a given space. | |||||
This does its best to make the given text readable within the specified rectangle, | |||||
so it useful for labelling things. | |||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||||
it's been truncated. | |||||
A Justification parameter lets you specify how the text is laid out within the rectangle, | |||||
both horizontally and vertically. | |||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||||
can set this value to 1.0f. Pass 0 if you want it to use a default value. | |||||
@see GlyphArrangement::addFittedText | |||||
*/ | |||||
void drawFittedText (const String& text, | |||||
Rectangle<int> area, | |||||
Justification justificationFlags, | |||||
int maximumNumberOfLines, | |||||
float minimumHorizontalScale = 0.0f) const; | |||||
//============================================================================== | |||||
/** Fills the context's entire clip region with the current colour or brush. | |||||
(See also the fillAll (Colour) method which is a quick way of filling | |||||
it with a given colour). | |||||
*/ | |||||
void fillAll() const; | |||||
/** Fills the context's entire clip region with a given colour. | |||||
This leaves the context's current colour and brush unchanged, it just | |||||
uses the specified colour temporarily. | |||||
*/ | |||||
void fillAll (Colour colourToUse) const; | |||||
//============================================================================== | |||||
/** Fills a rectangle with the current colour or brush. | |||||
@see drawRect, fillRoundedRectangle | |||||
*/ | |||||
void fillRect (Rectangle<int> rectangle) const; | |||||
/** Fills a rectangle with the current colour or brush. | |||||
@see drawRect, fillRoundedRectangle | |||||
*/ | |||||
void fillRect (Rectangle<float> rectangle) const; | |||||
/** Fills a rectangle with the current colour or brush. | |||||
@see drawRect, fillRoundedRectangle | |||||
*/ | |||||
void fillRect (int x, int y, int width, int height) const; | |||||
/** Fills a rectangle with the current colour or brush. | |||||
@see drawRect, fillRoundedRectangle | |||||
*/ | |||||
void fillRect (float x, float y, float width, float height) const; | |||||
/** Fills a set of rectangles using the current colour or brush. | |||||
If you have a lot of rectangles to draw, it may be more efficient | |||||
to create a RectangleList and use this method than to call fillRect() | |||||
multiple times. | |||||
*/ | |||||
void fillRectList (const RectangleList<float>& rectangles) const; | |||||
/** Fills a set of rectangles using the current colour or brush. | |||||
If you have a lot of rectangles to draw, it may be more efficient | |||||
to create a RectangleList and use this method than to call fillRect() | |||||
multiple times. | |||||
*/ | |||||
void fillRectList (const RectangleList<int>& rectangles) const; | |||||
/** Uses the current colour or brush to fill a rectangle with rounded corners. | |||||
@see drawRoundedRectangle, Path::addRoundedRectangle | |||||
*/ | |||||
void fillRoundedRectangle (float x, float y, float width, float height, | |||||
float cornerSize) const; | |||||
/** Uses the current colour or brush to fill a rectangle with rounded corners. | |||||
@see drawRoundedRectangle, Path::addRoundedRectangle | |||||
*/ | |||||
void fillRoundedRectangle (Rectangle<float> rectangle, | |||||
float cornerSize) const; | |||||
/** Fills a rectangle with a checkerboard pattern, alternating between two colours. */ | |||||
void fillCheckerBoard (Rectangle<int> area, | |||||
int checkWidth, int checkHeight, | |||||
Colour colour1, Colour colour2) const; | |||||
/** Draws a rectangular outline, using the current colour or brush. | |||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||||
@see fillRect | |||||
*/ | |||||
void drawRect (int x, int y, int width, int height, int lineThickness = 1) const; | |||||
/** Draws a rectangular outline, using the current colour or brush. | |||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||||
@see fillRect | |||||
*/ | |||||
void drawRect (float x, float y, float width, float height, float lineThickness = 1.0f) const; | |||||
/** Draws a rectangular outline, using the current colour or brush. | |||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||||
@see fillRect | |||||
*/ | |||||
void drawRect (Rectangle<int> rectangle, int lineThickness = 1) const; | |||||
/** Draws a rectangular outline, using the current colour or brush. | |||||
The lines are drawn inside the given rectangle, and greater line thicknesses extend inwards. | |||||
@see fillRect | |||||
*/ | |||||
void drawRect (Rectangle<float> rectangle, float lineThickness = 1.0f) const; | |||||
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. | |||||
@see fillRoundedRectangle, Path::addRoundedRectangle | |||||
*/ | |||||
void drawRoundedRectangle (float x, float y, float width, float height, | |||||
float cornerSize, float lineThickness) const; | |||||
/** Uses the current colour or brush to draw the outline of a rectangle with rounded corners. | |||||
@see fillRoundedRectangle, Path::addRoundedRectangle | |||||
*/ | |||||
void drawRoundedRectangle (Rectangle<float> rectangle, | |||||
float cornerSize, float lineThickness) const; | |||||
//============================================================================== | |||||
/** Fills an ellipse with the current colour or brush. | |||||
The ellipse is drawn to fit inside the given rectangle. | |||||
@see drawEllipse, Path::addEllipse | |||||
*/ | |||||
void fillEllipse (float x, float y, float width, float height) const; | |||||
/** Fills an ellipse with the current colour or brush. | |||||
The ellipse is drawn to fit inside the given rectangle. | |||||
@see drawEllipse, Path::addEllipse | |||||
*/ | |||||
void fillEllipse (Rectangle<float> area) const; | |||||
/** Draws an elliptical stroke using the current colour or brush. | |||||
@see fillEllipse, Path::addEllipse | |||||
*/ | |||||
void drawEllipse (float x, float y, float width, float height, | |||||
float lineThickness) const; | |||||
/** Draws an elliptical stroke using the current colour or brush. | |||||
@see fillEllipse, Path::addEllipse | |||||
*/ | |||||
void drawEllipse (Rectangle<float> area, float lineThickness) const; | |||||
//============================================================================== | |||||
/** Draws a line between two points. | |||||
The line is 1 pixel wide and drawn with the current colour or brush. | |||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||||
it's better to use fillRect() instead unless you really need an angled line. | |||||
*/ | |||||
void drawLine (float startX, float startY, float endX, float endY) const; | |||||
/** Draws a line between two points with a given thickness. | |||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||||
it's better to use fillRect() instead unless you really need an angled line. | |||||
@see Path::addLineSegment | |||||
*/ | |||||
void drawLine (float startX, float startY, float endX, float endY, float lineThickness) const; | |||||
/** Draws a line between two points. | |||||
The line is 1 pixel wide and drawn with the current colour or brush. | |||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||||
it's better to use fillRect() instead unless you really need an angled line. | |||||
*/ | |||||
void drawLine (Line<float> line) const; | |||||
/** Draws a line between two points with a given thickness. | |||||
@see Path::addLineSegment | |||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | |||||
it's better to use fillRect() instead unless you really need an angled line. | |||||
*/ | |||||
void drawLine (Line<float> line, float lineThickness) const; | |||||
/** Draws a dashed line using a custom set of dash-lengths. | |||||
@param line the line to draw | |||||
@param dashLengths a series of lengths to specify the on/off lengths - e.g. | |||||
{ 4, 5, 6, 7 } will draw a line of 4 pixels, skip 5 pixels, | |||||
draw 6 pixels, skip 7 pixels, and then repeat. | |||||
@param numDashLengths the number of elements in the array (this must be an even number). | |||||
@param lineThickness the thickness of the line to draw | |||||
@param dashIndexToStartFrom the index in the dash-length array to use for the first segment | |||||
@see PathStrokeType::createDashedStroke | |||||
*/ | |||||
void drawDashedLine (Line<float> line, | |||||
const float* dashLengths, int numDashLengths, | |||||
float lineThickness = 1.0f, | |||||
int dashIndexToStartFrom = 0) const; | |||||
/** Draws a vertical line of pixels at a given x position. | |||||
The x position is an integer, but the top and bottom of the line can be sub-pixel | |||||
positions, and these will be anti-aliased if necessary. | |||||
The bottom parameter must be greater than or equal to the top parameter. | |||||
*/ | |||||
void drawVerticalLine (int x, float top, float bottom) const; | |||||
/** Draws a horizontal line of pixels at a given y position. | |||||
The y position is an integer, but the left and right ends of the line can be sub-pixel | |||||
positions, and these will be anti-aliased if necessary. | |||||
The right parameter must be greater than or equal to the left parameter. | |||||
*/ | |||||
void drawHorizontalLine (int y, float left, float right) const; | |||||
//============================================================================== | |||||
/** Fills a path using the currently selected colour or brush. */ | |||||
void fillPath (const Path& path) const; | |||||
/** Fills a path using the currently selected colour or brush, and adds a transform. */ | |||||
void fillPath (const Path& path, const AffineTransform& transform) const; | |||||
/** Draws a path's outline using the currently selected colour or brush. */ | |||||
void strokePath (const Path& path, | |||||
const PathStrokeType& strokeType, | |||||
const AffineTransform& transform = {}) const; | |||||
/** Draws a line with an arrowhead at its end. | |||||
@param line the line to draw | |||||
@param lineThickness the thickness of the line | |||||
@param arrowheadWidth the width of the arrow head (perpendicular to the line) | |||||
@param arrowheadLength the length of the arrow head (along the length of the line) | |||||
*/ | |||||
void drawArrow (Line<float> line, | |||||
float lineThickness, | |||||
float arrowheadWidth, | |||||
float arrowheadLength) const; | |||||
//============================================================================== | |||||
/** Types of rendering quality that can be specified when drawing images. | |||||
@see Graphics::setImageResamplingQuality | |||||
*/ | |||||
enum ResamplingQuality | |||||
{ | |||||
lowResamplingQuality = 0, /**< Just uses a nearest-neighbour algorithm for resampling. */ | |||||
mediumResamplingQuality = 1, /**< Uses bilinear interpolation for upsampling and area-averaging for downsampling. */ | |||||
highResamplingQuality = 2, /**< Uses bicubic interpolation for upsampling and area-averaging for downsampling. */ | |||||
}; | |||||
/** Changes the quality that will be used when resampling images. | |||||
By default a Graphics object will be set to mediumRenderingQuality. | |||||
@see Graphics::drawImage, Graphics::drawImageTransformed, Graphics::drawImageWithin | |||||
*/ | |||||
void setImageResamplingQuality (const ResamplingQuality newQuality); | |||||
/** Draws an image. | |||||
This will draw the whole of an image, positioning its top-left corner at the | |||||
given coordinates, and keeping its size the same. This is the simplest image | |||||
drawing method - the others give more control over the scaling and clipping | |||||
of the images. | |||||
Images are composited using the context's current opacity, so if you | |||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||||
(or setColour() with an opaque colour) before drawing images. | |||||
*/ | |||||
void drawImageAt (const Image& imageToDraw, int topLeftX, int topLeftY, | |||||
bool fillAlphaChannelWithCurrentBrush = false) const; | |||||
/** Draws part of an image, rescaling it to fit in a given target region. | |||||
The specified area of the source image is rescaled and drawn to fill the | |||||
specifed destination rectangle. | |||||
Images are composited using the context's current opacity, so if you | |||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||||
(or setColour() with an opaque colour) before drawing images. | |||||
@param imageToDraw the image to overlay | |||||
@param destX the left of the destination rectangle | |||||
@param destY the top of the destination rectangle | |||||
@param destWidth the width of the destination rectangle | |||||
@param destHeight the height of the destination rectangle | |||||
@param sourceX the left of the rectangle to copy from the source image | |||||
@param sourceY the top of the rectangle to copy from the source image | |||||
@param sourceWidth the width of the rectangle to copy from the source image | |||||
@param sourceHeight the height of the rectangle to copy from the source image | |||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the source image's pixels, | |||||
the source image's alpha channel is used as a mask with | |||||
which to fill the destination using the current colour | |||||
or brush. (If the source is has no alpha channel, then | |||||
it will just fill the target with a solid rectangle) | |||||
@see setImageResamplingQuality, drawImageAt, drawImageWithin, fillAlphaMap | |||||
*/ | |||||
void drawImage (const Image& imageToDraw, | |||||
int destX, int destY, int destWidth, int destHeight, | |||||
int sourceX, int sourceY, int sourceWidth, int sourceHeight, | |||||
bool fillAlphaChannelWithCurrentBrush = false) const; | |||||
/** Draws an image, having applied an affine transform to it. | |||||
This lets you throw the image around in some wacky ways, rotate it, shear, | |||||
scale it, etc. | |||||
Images are composited using the context's current opacity, so if you | |||||
don't want it to be drawn semi-transparently, be sure to call setOpacity (1.0f) | |||||
(or setColour() with an opaque colour) before drawing images. | |||||
If fillAlphaChannelWithCurrentBrush is set to true, then the image's RGB channels | |||||
are ignored and it is filled with the current brush, masked by its alpha channel. | |||||
If you want to render only a subsection of an image, use Image::getClippedImage() to | |||||
create the section that you need. | |||||
@see setImageResamplingQuality, drawImage | |||||
*/ | |||||
void drawImageTransformed (const Image& imageToDraw, | |||||
const AffineTransform& transform, | |||||
bool fillAlphaChannelWithCurrentBrush = false) const; | |||||
/** Draws an image to fit within a designated rectangle. | |||||
@param imageToDraw the source image to draw | |||||
@param targetArea the target rectangle to fit it into | |||||
@param placementWithinTarget this specifies how the image should be positioned | |||||
within the target rectangle - see the RectanglePlacement | |||||
class for more details about this. | |||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its | |||||
alpha channel will be used as a mask with which to | |||||
draw with the current brush or colour. This is | |||||
similar to fillAlphaMap(), and see also drawImage() | |||||
@see drawImage, drawImageTransformed, drawImageAt, RectanglePlacement | |||||
*/ | |||||
void drawImage (const Image& imageToDraw, Rectangle<float> targetArea, | |||||
RectanglePlacement placementWithinTarget = RectanglePlacement::stretchToFit, | |||||
bool fillAlphaChannelWithCurrentBrush = false) const; | |||||
/** Draws an image to fit within a designated rectangle. | |||||
If the image is too big or too small for the space, it will be rescaled | |||||
to fit as nicely as it can do without affecting its aspect ratio. It will | |||||
then be placed within the target rectangle according to the justification flags | |||||
specified. | |||||
@param imageToDraw the source image to draw | |||||
@param destX top-left of the target rectangle to fit it into | |||||
@param destY top-left of the target rectangle to fit it into | |||||
@param destWidth size of the target rectangle to fit the image into | |||||
@param destHeight size of the target rectangle to fit the image into | |||||
@param placementWithinTarget this specifies how the image should be positioned | |||||
within the target rectangle - see the RectanglePlacement | |||||
class for more details about this. | |||||
@param fillAlphaChannelWithCurrentBrush if true, then instead of drawing the image, just its | |||||
alpha channel will be used as a mask with which to | |||||
draw with the current brush or colour. This is | |||||
similar to fillAlphaMap(), and see also drawImage() | |||||
@see setImageResamplingQuality, drawImage, drawImageTransformed, drawImageAt, RectanglePlacement | |||||
*/ | |||||
void drawImageWithin (const Image& imageToDraw, | |||||
int destX, int destY, int destWidth, int destHeight, | |||||
RectanglePlacement placementWithinTarget, | |||||
bool fillAlphaChannelWithCurrentBrush = false) const; | |||||
//============================================================================== | |||||
/** Returns the position of the bounding box for the current clipping region. | |||||
@see getClipRegion, clipRegionIntersects | |||||
*/ | |||||
Rectangle<int> getClipBounds() const; | |||||
/** Checks whether a rectangle overlaps the context's clipping region. | |||||
If this returns false, no part of the given area can be drawn onto, so this | |||||
method can be used to optimise a component's paint() method, by letting it | |||||
avoid drawing complex objects that aren't within the region being repainted. | |||||
*/ | |||||
bool clipRegionIntersects (Rectangle<int> area) const; | |||||
/** Intersects the current clipping region with another region. | |||||
@returns true if the resulting clipping region is non-zero in size | |||||
@see setOrigin, clipRegionIntersects | |||||
*/ | |||||
bool reduceClipRegion (int x, int y, int width, int height); | |||||
/** Intersects the current clipping region with another region. | |||||
@returns true if the resulting clipping region is non-zero in size | |||||
@see setOrigin, clipRegionIntersects | |||||
*/ | |||||
bool reduceClipRegion (Rectangle<int> area); | |||||
/** Intersects the current clipping region with a rectangle list region. | |||||
@returns true if the resulting clipping region is non-zero in size | |||||
@see setOrigin, clipRegionIntersects | |||||
*/ | |||||
bool reduceClipRegion (const RectangleList<int>& clipRegion); | |||||
/** Intersects the current clipping region with a path. | |||||
@returns true if the resulting clipping region is non-zero in size | |||||
@see reduceClipRegion | |||||
*/ | |||||
bool reduceClipRegion (const Path& path, const AffineTransform& transform = AffineTransform()); | |||||
/** Intersects the current clipping region with an image's alpha-channel. | |||||
The current clipping path is intersected with the area covered by this image's | |||||
alpha-channel, after the image has been transformed by the specified matrix. | |||||
@param image the image whose alpha-channel should be used. If the image doesn't | |||||
have an alpha-channel, it is treated as entirely opaque. | |||||
@param transform a matrix to apply to the image | |||||
@returns true if the resulting clipping region is non-zero in size | |||||
@see reduceClipRegion | |||||
*/ | |||||
bool reduceClipRegion (const Image& image, const AffineTransform& transform); | |||||
/** Excludes a rectangle to stop it being drawn into. */ | |||||
void excludeClipRegion (Rectangle<int> rectangleToExclude); | |||||
/** Returns true if no drawing can be done because the clip region is zero. */ | |||||
bool isClipEmpty() const; | |||||
//============================================================================== | |||||
/** Saves the current graphics state on an internal stack. | |||||
To restore the state, use restoreState(). | |||||
@see ScopedSaveState | |||||
*/ | |||||
void saveState(); | |||||
/** Restores a graphics state that was previously saved with saveState(). | |||||
@see ScopedSaveState | |||||
*/ | |||||
void restoreState(); | |||||
/** Uses RAII to save and restore the state of a graphics context. | |||||
On construction, this calls Graphics::saveState(), and on destruction it calls | |||||
Graphics::restoreState() on the Graphics object that you supply. | |||||
*/ | |||||
class ScopedSaveState | |||||
{ | |||||
public: | |||||
ScopedSaveState (Graphics&); | |||||
~ScopedSaveState(); | |||||
private: | |||||
Graphics& context; | |||||
JUCE_DECLARE_NON_COPYABLE (ScopedSaveState) | |||||
}; | |||||
//============================================================================== | |||||
/** Begins rendering to an off-screen bitmap which will later be flattened onto the current | |||||
context with the given opacity. | |||||
The context uses an internal stack of temporary image layers to do this. When you've | |||||
finished drawing to the layer, call endTransparencyLayer() to complete the operation and | |||||
composite the finished layer. Every call to beginTransparencyLayer() MUST be matched | |||||
by a corresponding call to endTransparencyLayer()! | |||||
This call also saves the current state, and endTransparencyLayer() restores it. | |||||
*/ | |||||
void beginTransparencyLayer (float layerOpacity); | |||||
/** Completes a drawing operation to a temporary semi-transparent buffer. | |||||
See beginTransparencyLayer() for more details. | |||||
*/ | |||||
void endTransparencyLayer(); | |||||
/** Moves the position of the context's origin. | |||||
This changes the position that the context considers to be (0, 0) to | |||||
the specified position. | |||||
So if you call setOrigin with (100, 100), then the position that was previously | |||||
referred to as (100, 100) will subsequently be considered to be (0, 0). | |||||
@see reduceClipRegion, addTransform | |||||
*/ | |||||
void setOrigin (Point<int> newOrigin); | |||||
/** Moves the position of the context's origin. | |||||
This changes the position that the context considers to be (0, 0) to | |||||
the specified position. | |||||
So if you call setOrigin (100, 100), then the position that was previously | |||||
referred to as (100, 100) will subsequently be considered to be (0, 0). | |||||
@see reduceClipRegion, addTransform | |||||
*/ | |||||
void setOrigin (int newOriginX, int newOriginY); | |||||
/** Adds a transformation which will be performed on all the graphics operations that | |||||
the context subsequently performs. | |||||
After calling this, all the coordinates that are passed into the context will be | |||||
transformed by this matrix. | |||||
@see setOrigin | |||||
*/ | |||||
void addTransform (const AffineTransform& transform); | |||||
/** Resets the current colour, brush, and font to default settings. */ | |||||
void resetToDefaultState(); | |||||
/** Returns true if this context is drawing to a vector-based device, such as a printer. */ | |||||
bool isVectorDevice() const; | |||||
//============================================================================== | |||||
/** Create a graphics that draws with a given low-level renderer. | |||||
This method is intended for use only by people who know what they're doing. | |||||
Note that the LowLevelGraphicsContext will NOT be deleted by this object. | |||||
*/ | |||||
Graphics (LowLevelGraphicsContext&) noexcept; | |||||
/** @internal */ | |||||
LowLevelGraphicsContext& getInternalContext() const noexcept { return context; } | |||||
private: | |||||
//============================================================================== | |||||
LowLevelGraphicsContext& context; | |||||
ScopedPointer<LowLevelGraphicsContext> contextToDelete; | |||||
bool saveStatePending = false; | |||||
void saveStateIfPending(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,101 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Interface class for graphics context objects, used internally by the Graphics class. | |||||
Users are not supposed to create instances of this class directly - do your drawing | |||||
via the Graphics object instead. | |||||
It's a base class for different types of graphics context, that may perform software-based | |||||
or OS-accelerated rendering. | |||||
E.g. the LowLevelGraphicsSoftwareRenderer renders onto an image in memory, but other | |||||
subclasses could render directly to a windows HDC, a Quartz context, or an OpenGL | |||||
context. | |||||
*/ | |||||
class JUCE_API LowLevelGraphicsContext | |||||
{ | |||||
protected: | |||||
//============================================================================== | |||||
LowLevelGraphicsContext(); | |||||
public: | |||||
virtual ~LowLevelGraphicsContext(); | |||||
/** Returns true if this device is vector-based, e.g. a printer. */ | |||||
virtual bool isVectorDevice() const = 0; | |||||
//============================================================================== | |||||
/** Moves the origin to a new position. | |||||
The coordinates are relative to the current origin, and indicate the new position | |||||
of (0, 0). | |||||
*/ | |||||
virtual void setOrigin (Point<int>) = 0; | |||||
virtual void addTransform (const AffineTransform&) = 0; | |||||
virtual float getPhysicalPixelScaleFactor() = 0; | |||||
virtual bool clipToRectangle (const Rectangle<int>&) = 0; | |||||
virtual bool clipToRectangleList (const RectangleList<int>&) = 0; | |||||
virtual void excludeClipRectangle (const Rectangle<int>&) = 0; | |||||
virtual void clipToPath (const Path&, const AffineTransform&) = 0; | |||||
virtual void clipToImageAlpha (const Image&, const AffineTransform&) = 0; | |||||
virtual bool clipRegionIntersects (const Rectangle<int>&) = 0; | |||||
virtual Rectangle<int> getClipBounds() const = 0; | |||||
virtual bool isClipEmpty() const = 0; | |||||
virtual void saveState() = 0; | |||||
virtual void restoreState() = 0; | |||||
virtual void beginTransparencyLayer (float opacity) = 0; | |||||
virtual void endTransparencyLayer() = 0; | |||||
//============================================================================== | |||||
virtual void setFill (const FillType&) = 0; | |||||
virtual void setOpacity (float) = 0; | |||||
virtual void setInterpolationQuality (Graphics::ResamplingQuality) = 0; | |||||
//============================================================================== | |||||
virtual void fillRect (const Rectangle<int>&, bool replaceExistingContents) = 0; | |||||
virtual void fillRect (const Rectangle<float>&) = 0; | |||||
virtual void fillRectList (const RectangleList<float>&) = 0; | |||||
virtual void fillPath (const Path&, const AffineTransform&) = 0; | |||||
virtual void drawImage (const Image&, const AffineTransform&) = 0; | |||||
virtual void drawLine (const Line<float>&) = 0; | |||||
virtual void setFont (const Font&) = 0; | |||||
virtual const Font& getFont() = 0; | |||||
virtual void drawGlyph (int glyphNumber, const AffineTransform&) = 0; | |||||
virtual bool drawTextLayout (const AttributedString&, const Rectangle<float>&) { return false; } | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,540 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
// this will throw an assertion if you try to draw something that's not | |||||
// possible in postscript | |||||
#define WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS 0 | |||||
//============================================================================== | |||||
#if JUCE_DEBUG && WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS | |||||
#define notPossibleInPostscriptAssert jassertfalse | |||||
#else | |||||
#define notPossibleInPostscriptAssert | |||||
#endif | |||||
//============================================================================== | |||||
LowLevelGraphicsPostScriptRenderer::LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, | |||||
const String& documentTitle, | |||||
const int totalWidth_, | |||||
const int totalHeight_) | |||||
: out (resultingPostScript), | |||||
totalWidth (totalWidth_), | |||||
totalHeight (totalHeight_), | |||||
needToClip (true) | |||||
{ | |||||
stateStack.add (new SavedState()); | |||||
stateStack.getLast()->clip = Rectangle<int> (totalWidth_, totalHeight_); | |||||
const float scale = jmin ((520.0f / totalWidth_), (750.0f / totalHeight)); | |||||
out << "%!PS-Adobe-3.0 EPSF-3.0" | |||||
"\n%%BoundingBox: 0 0 600 824" | |||||
"\n%%Pages: 0" | |||||
"\n%%Creator: ROLI Ltd. JUCE" | |||||
"\n%%Title: " << documentTitle << | |||||
"\n%%CreationDate: none" | |||||
"\n%%LanguageLevel: 2" | |||||
"\n%%EndComments" | |||||
"\n%%BeginProlog" | |||||
"\n%%BeginResource: JRes" | |||||
"\n/bd {bind def} bind def" | |||||
"\n/c {setrgbcolor} bd" | |||||
"\n/m {moveto} bd" | |||||
"\n/l {lineto} bd" | |||||
"\n/rl {rlineto} bd" | |||||
"\n/ct {curveto} bd" | |||||
"\n/cp {closepath} bd" | |||||
"\n/pr {3 index 3 index moveto 1 index 0 rlineto 0 1 index rlineto pop neg 0 rlineto pop pop closepath} bd" | |||||
"\n/doclip {initclip newpath} bd" | |||||
"\n/endclip {clip newpath} bd" | |||||
"\n%%EndResource" | |||||
"\n%%EndProlog" | |||||
"\n%%BeginSetup" | |||||
"\n%%EndSetup" | |||||
"\n%%Page: 1 1" | |||||
"\n%%BeginPageSetup" | |||||
"\n%%EndPageSetup\n\n" | |||||
<< "40 800 translate\n" | |||||
<< scale << ' ' << scale << " scale\n\n"; | |||||
} | |||||
LowLevelGraphicsPostScriptRenderer::~LowLevelGraphicsPostScriptRenderer() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
bool LowLevelGraphicsPostScriptRenderer::isVectorDevice() const | |||||
{ | |||||
return true; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::setOrigin (Point<int> o) | |||||
{ | |||||
if (! o.isOrigin()) | |||||
{ | |||||
stateStack.getLast()->xOffset += o.x; | |||||
stateStack.getLast()->yOffset += o.y; | |||||
needToClip = true; | |||||
} | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::addTransform (const AffineTransform& /*transform*/) | |||||
{ | |||||
//xxx | |||||
jassertfalse; | |||||
} | |||||
float LowLevelGraphicsPostScriptRenderer::getPhysicalPixelScaleFactor() { return 1.0f; } | |||||
bool LowLevelGraphicsPostScriptRenderer::clipToRectangle (const Rectangle<int>& r) | |||||
{ | |||||
needToClip = true; | |||||
return stateStack.getLast()->clip.clipTo (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||||
} | |||||
bool LowLevelGraphicsPostScriptRenderer::clipToRectangleList (const RectangleList<int>& clipRegion) | |||||
{ | |||||
needToClip = true; | |||||
return stateStack.getLast()->clip.clipTo (clipRegion); | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::excludeClipRectangle (const Rectangle<int>& r) | |||||
{ | |||||
needToClip = true; | |||||
stateStack.getLast()->clip.subtract (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::clipToPath (const Path& path, const AffineTransform& transform) | |||||
{ | |||||
writeClip(); | |||||
Path p (path); | |||||
p.applyTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); | |||||
writePath (p); | |||||
out << "clip\n"; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::clipToImageAlpha (const Image& /*sourceImage*/, const AffineTransform& /*transform*/) | |||||
{ | |||||
needToClip = true; | |||||
jassertfalse; // xxx | |||||
} | |||||
bool LowLevelGraphicsPostScriptRenderer::clipRegionIntersects (const Rectangle<int>& r) | |||||
{ | |||||
return stateStack.getLast()->clip.intersectsRectangle (r.translated (stateStack.getLast()->xOffset, stateStack.getLast()->yOffset)); | |||||
} | |||||
Rectangle<int> LowLevelGraphicsPostScriptRenderer::getClipBounds() const | |||||
{ | |||||
return stateStack.getLast()->clip.getBounds().translated (-stateStack.getLast()->xOffset, | |||||
-stateStack.getLast()->yOffset); | |||||
} | |||||
bool LowLevelGraphicsPostScriptRenderer::isClipEmpty() const | |||||
{ | |||||
return stateStack.getLast()->clip.isEmpty(); | |||||
} | |||||
//============================================================================== | |||||
LowLevelGraphicsPostScriptRenderer::SavedState::SavedState() | |||||
: xOffset (0), | |||||
yOffset (0) | |||||
{ | |||||
} | |||||
LowLevelGraphicsPostScriptRenderer::SavedState::~SavedState() | |||||
{ | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::saveState() | |||||
{ | |||||
stateStack.add (new SavedState (*stateStack.getLast())); | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::restoreState() | |||||
{ | |||||
jassert (stateStack.size() > 0); | |||||
if (stateStack.size() > 0) | |||||
stateStack.removeLast(); | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::beginTransparencyLayer (float) | |||||
{ | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::endTransparencyLayer() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::writeClip() | |||||
{ | |||||
if (needToClip) | |||||
{ | |||||
needToClip = false; | |||||
out << "doclip "; | |||||
int itemsOnLine = 0; | |||||
for (auto& i : stateStack.getLast()->clip) | |||||
{ | |||||
if (++itemsOnLine == 6) | |||||
{ | |||||
itemsOnLine = 0; | |||||
out << '\n'; | |||||
} | |||||
out << i.getX() << ' ' << -i.getY() << ' ' | |||||
<< i.getWidth() << ' ' << -i.getHeight() << " pr "; | |||||
} | |||||
out << "endclip\n"; | |||||
} | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::writeColour (Colour colour) | |||||
{ | |||||
Colour c (Colours::white.overlaidWith (colour)); | |||||
if (lastColour != c) | |||||
{ | |||||
lastColour = c; | |||||
out << String (c.getFloatRed(), 3) << ' ' | |||||
<< String (c.getFloatGreen(), 3) << ' ' | |||||
<< String (c.getFloatBlue(), 3) << " c\n"; | |||||
} | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::writeXY (const float x, const float y) const | |||||
{ | |||||
out << String (x, 2) << ' ' | |||||
<< String (-y, 2) << ' '; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::writePath (const Path& path) const | |||||
{ | |||||
out << "newpath "; | |||||
float lastX = 0.0f; | |||||
float lastY = 0.0f; | |||||
int itemsOnLine = 0; | |||||
Path::Iterator i (path); | |||||
while (i.next()) | |||||
{ | |||||
if (++itemsOnLine == 4) | |||||
{ | |||||
itemsOnLine = 0; | |||||
out << '\n'; | |||||
} | |||||
switch (i.elementType) | |||||
{ | |||||
case Path::Iterator::startNewSubPath: | |||||
writeXY (i.x1, i.y1); | |||||
lastX = i.x1; | |||||
lastY = i.y1; | |||||
out << "m "; | |||||
break; | |||||
case Path::Iterator::lineTo: | |||||
writeXY (i.x1, i.y1); | |||||
lastX = i.x1; | |||||
lastY = i.y1; | |||||
out << "l "; | |||||
break; | |||||
case Path::Iterator::quadraticTo: | |||||
{ | |||||
const float cp1x = lastX + (i.x1 - lastX) * 2.0f / 3.0f; | |||||
const float cp1y = lastY + (i.y1 - lastY) * 2.0f / 3.0f; | |||||
const float cp2x = cp1x + (i.x2 - lastX) / 3.0f; | |||||
const float cp2y = cp1y + (i.y2 - lastY) / 3.0f; | |||||
writeXY (cp1x, cp1y); | |||||
writeXY (cp2x, cp2y); | |||||
writeXY (i.x2, i.y2); | |||||
out << "ct "; | |||||
lastX = i.x2; | |||||
lastY = i.y2; | |||||
} | |||||
break; | |||||
case Path::Iterator::cubicTo: | |||||
writeXY (i.x1, i.y1); | |||||
writeXY (i.x2, i.y2); | |||||
writeXY (i.x3, i.y3); | |||||
out << "ct "; | |||||
lastX = i.x3; | |||||
lastY = i.y3; | |||||
break; | |||||
case Path::Iterator::closePath: | |||||
out << "cp "; | |||||
break; | |||||
default: | |||||
jassertfalse; | |||||
break; | |||||
} | |||||
} | |||||
out << '\n'; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::writeTransform (const AffineTransform& trans) const | |||||
{ | |||||
out << "[ " | |||||
<< trans.mat00 << ' ' | |||||
<< trans.mat10 << ' ' | |||||
<< trans.mat01 << ' ' | |||||
<< trans.mat11 << ' ' | |||||
<< trans.mat02 << ' ' | |||||
<< trans.mat12 << " ] concat "; | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::setFill (const FillType& fillType) | |||||
{ | |||||
stateStack.getLast()->fillType = fillType; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::setOpacity (float /*opacity*/) | |||||
{ | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::setInterpolationQuality (Graphics::ResamplingQuality /*quality*/) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<int>& r, const bool /*replaceExistingContents*/) | |||||
{ | |||||
fillRect (r.toFloat()); | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::fillRect (const Rectangle<float>& r) | |||||
{ | |||||
if (stateStack.getLast()->fillType.isColour()) | |||||
{ | |||||
writeClip(); | |||||
writeColour (stateStack.getLast()->fillType.colour); | |||||
Rectangle<float> r2 (r.translated ((float) stateStack.getLast()->xOffset, | |||||
(float) stateStack.getLast()->yOffset)); | |||||
out << r2.getX() << ' ' << -r2.getBottom() << ' ' << r2.getWidth() << ' ' << r2.getHeight() << " rectfill\n"; | |||||
} | |||||
else | |||||
{ | |||||
Path p; | |||||
p.addRectangle (r); | |||||
fillPath (p, AffineTransform()); | |||||
} | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::fillRectList (const RectangleList<float>& list) | |||||
{ | |||||
fillPath (list.toPath(), AffineTransform()); | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const AffineTransform& t) | |||||
{ | |||||
if (stateStack.getLast()->fillType.isColour()) | |||||
{ | |||||
writeClip(); | |||||
Path p (path); | |||||
p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, | |||||
(float) stateStack.getLast()->yOffset)); | |||||
writePath (p); | |||||
writeColour (stateStack.getLast()->fillType.colour); | |||||
out << "fill\n"; | |||||
} | |||||
else if (stateStack.getLast()->fillType.isGradient()) | |||||
{ | |||||
// this doesn't work correctly yet - it could be improved to handle solid gradients, but | |||||
// postscript can't do semi-transparent ones. | |||||
notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file | |||||
writeClip(); | |||||
out << "gsave "; | |||||
{ | |||||
Path p (path); | |||||
p.applyTransform (t.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset)); | |||||
writePath (p); | |||||
out << "clip\n"; | |||||
} | |||||
const Rectangle<int> bounds (stateStack.getLast()->clip.getBounds()); | |||||
// ideally this would draw lots of lines or ellipses to approximate the gradient, but for the | |||||
// time-being, this just fills it with the average colour.. | |||||
writeColour (stateStack.getLast()->fillType.gradient->getColourAtPosition (0.5f)); | |||||
out << bounds.getX() << ' ' << -bounds.getBottom() << ' ' << bounds.getWidth() << ' ' << bounds.getHeight() << " rectfill\n"; | |||||
out << "grestore\n"; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::writeImage (const Image& im, | |||||
const int sx, const int sy, | |||||
const int maxW, const int maxH) const | |||||
{ | |||||
out << "{<\n"; | |||||
const int w = jmin (maxW, im.getWidth()); | |||||
const int h = jmin (maxH, im.getHeight()); | |||||
int charsOnLine = 0; | |||||
const Image::BitmapData srcData (im, 0, 0, w, h); | |||||
Colour pixel; | |||||
for (int y = h; --y >= 0;) | |||||
{ | |||||
for (int x = 0; x < w; ++x) | |||||
{ | |||||
const uint8* pixelData = srcData.getPixelPointer (x, y); | |||||
if (x >= sx && y >= sy) | |||||
{ | |||||
if (im.isARGB()) | |||||
{ | |||||
PixelARGB p (*(const PixelARGB*) pixelData); | |||||
p.unpremultiply(); | |||||
pixel = Colours::white.overlaidWith (Colour (p)); | |||||
} | |||||
else if (im.isRGB()) | |||||
{ | |||||
pixel = Colour (*((const PixelRGB*) pixelData)); | |||||
} | |||||
else | |||||
{ | |||||
pixel = Colour ((uint8) 0, (uint8) 0, (uint8) 0, *pixelData); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
pixel = Colours::transparentWhite; | |||||
} | |||||
const uint8 pixelValues[3] = { pixel.getRed(), pixel.getGreen(), pixel.getBlue() }; | |||||
out << String::toHexString (pixelValues, 3, 0); | |||||
charsOnLine += 3; | |||||
if (charsOnLine > 100) | |||||
{ | |||||
out << '\n'; | |||||
charsOnLine = 0; | |||||
} | |||||
} | |||||
} | |||||
out << "\n>}\n"; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::drawImage (const Image& sourceImage, const AffineTransform& transform) | |||||
{ | |||||
const int w = sourceImage.getWidth(); | |||||
const int h = sourceImage.getHeight(); | |||||
writeClip(); | |||||
out << "gsave "; | |||||
writeTransform (transform.translated ((float) stateStack.getLast()->xOffset, (float) stateStack.getLast()->yOffset) | |||||
.scaled (1.0f, -1.0f)); | |||||
RectangleList<int> imageClip; | |||||
sourceImage.createSolidAreaMask (imageClip, 0.5f); | |||||
out << "newpath "; | |||||
int itemsOnLine = 0; | |||||
for (auto& i : imageClip) | |||||
{ | |||||
if (++itemsOnLine == 6) | |||||
{ | |||||
out << '\n'; | |||||
itemsOnLine = 0; | |||||
} | |||||
out << i.getX() << ' ' << i.getY() << ' ' << i.getWidth() << ' ' << i.getHeight() << " pr "; | |||||
} | |||||
out << " clip newpath\n"; | |||||
out << w << ' ' << h << " scale\n"; | |||||
out << w << ' ' << h << " 8 [" << w << " 0 0 -" << h << ' ' << (int) 0 << ' ' << h << " ]\n"; | |||||
writeImage (sourceImage, 0, 0, w, h); | |||||
out << "false 3 colorimage grestore\n"; | |||||
needToClip = true; | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::drawLine (const Line <float>& line) | |||||
{ | |||||
Path p; | |||||
p.addLineSegment (line, 1.0f); | |||||
fillPath (p, AffineTransform()); | |||||
} | |||||
//============================================================================== | |||||
void LowLevelGraphicsPostScriptRenderer::setFont (const Font& newFont) | |||||
{ | |||||
stateStack.getLast()->font = newFont; | |||||
} | |||||
const Font& LowLevelGraphicsPostScriptRenderer::getFont() | |||||
{ | |||||
return stateStack.getLast()->font; | |||||
} | |||||
void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const AffineTransform& transform) | |||||
{ | |||||
Path p; | |||||
Font& font = stateStack.getLast()->font; | |||||
font.getTypeface()->getOutlineForGlyph (glyphNumber, p); | |||||
fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform)); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,120 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
An implementation of LowLevelGraphicsContext that turns the drawing operations | |||||
into a PostScript document. | |||||
*/ | |||||
class JUCE_API LowLevelGraphicsPostScriptRenderer : public LowLevelGraphicsContext | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
LowLevelGraphicsPostScriptRenderer (OutputStream& resultingPostScript, | |||||
const String& documentTitle, | |||||
int totalWidth, | |||||
int totalHeight); | |||||
~LowLevelGraphicsPostScriptRenderer(); | |||||
//============================================================================== | |||||
bool isVectorDevice() const override; | |||||
void setOrigin (Point<int>) override; | |||||
void addTransform (const AffineTransform&) override; | |||||
float getPhysicalPixelScaleFactor() override; | |||||
bool clipToRectangle (const Rectangle<int>&) override; | |||||
bool clipToRectangleList (const RectangleList<int>&) override; | |||||
void excludeClipRectangle (const Rectangle<int>&) override; | |||||
void clipToPath (const Path&, const AffineTransform&) override; | |||||
void clipToImageAlpha (const Image&, const AffineTransform&) override; | |||||
void saveState() override; | |||||
void restoreState() override; | |||||
void beginTransparencyLayer (float) override; | |||||
void endTransparencyLayer() override; | |||||
bool clipRegionIntersects (const Rectangle<int>&) override; | |||||
Rectangle<int> getClipBounds() const override; | |||||
bool isClipEmpty() const override; | |||||
//============================================================================== | |||||
void setFill (const FillType&) override; | |||||
void setOpacity (float) override; | |||||
void setInterpolationQuality (Graphics::ResamplingQuality) override; | |||||
//============================================================================== | |||||
void fillRect (const Rectangle<int>&, bool replaceExistingContents) override; | |||||
void fillRect (const Rectangle<float>&) override; | |||||
void fillRectList (const RectangleList<float>&) override; | |||||
void fillPath (const Path&, const AffineTransform&) override; | |||||
void drawImage (const Image&, const AffineTransform&) override; | |||||
void drawLine (const Line <float>&) override; | |||||
//============================================================================== | |||||
const Font& getFont() override; | |||||
void setFont (const Font&) override; | |||||
void drawGlyph (int glyphNumber, const AffineTransform&) override; | |||||
protected: | |||||
//============================================================================== | |||||
OutputStream& out; | |||||
int totalWidth, totalHeight; | |||||
bool needToClip; | |||||
Colour lastColour; | |||||
struct SavedState | |||||
{ | |||||
SavedState(); | |||||
~SavedState(); | |||||
RectangleList<int> clip; | |||||
int xOffset, yOffset; | |||||
FillType fillType; | |||||
Font font; | |||||
private: | |||||
SavedState& operator= (const SavedState&); | |||||
}; | |||||
OwnedArray <SavedState> stateStack; | |||||
void writeClip(); | |||||
void writeColour (Colour colour); | |||||
void writePath (const Path&) const; | |||||
void writeXY (float x, float y) const; | |||||
void writeTransform (const AffineTransform&) const; | |||||
void writeImage (const Image&, int sx, int sy, int maxW, int maxH) const; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsPostScriptRenderer) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,45 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image) | |||||
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||||
(new RenderingHelpers::SoftwareRendererSavedState (image, image.getBounds())) | |||||
{ | |||||
} | |||||
LowLevelGraphicsSoftwareRenderer::LowLevelGraphicsSoftwareRenderer (const Image& image, Point<int> origin, | |||||
const RectangleList<int>& initialClip) | |||||
: RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||||
(new RenderingHelpers::SoftwareRendererSavedState (image, initialClip, origin)) | |||||
{ | |||||
} | |||||
LowLevelGraphicsSoftwareRenderer::~LowLevelGraphicsSoftwareRenderer() {} | |||||
} // namespace juce |
@@ -0,0 +1,56 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A lowest-common-denominator implementation of LowLevelGraphicsContext that does all | |||||
its rendering in memory. | |||||
User code is not supposed to create instances of this class directly - do all your | |||||
rendering via the Graphics class instead. | |||||
*/ | |||||
class JUCE_API LowLevelGraphicsSoftwareRenderer : public RenderingHelpers::StackBasedLowLevelGraphicsContext<RenderingHelpers::SoftwareRendererSavedState> | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a context to render into an image. */ | |||||
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto); | |||||
/** Creates a context to render into a clipped subsection of an image. */ | |||||
LowLevelGraphicsSoftwareRenderer (const Image& imageToRenderOnto, Point<int> origin, | |||||
const RectangleList<int>& initialClip); | |||||
/** Destructor. */ | |||||
~LowLevelGraphicsSoftwareRenderer(); | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LowLevelGraphicsSoftwareRenderer) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,189 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
static inline void blurDataTriplets (uint8* d, int num, const int delta) noexcept | |||||
{ | |||||
uint32 last = d[0]; | |||||
d[0] = (uint8) ((d[0] + d[delta] + 1) / 3); | |||||
d += delta; | |||||
num -= 2; | |||||
do | |||||
{ | |||||
const uint32 newLast = d[0]; | |||||
d[0] = (uint8) ((last + d[0] + d[delta] + 1) / 3); | |||||
d += delta; | |||||
last = newLast; | |||||
} | |||||
while (--num > 0); | |||||
d[0] = (uint8) ((last + d[0] + 1) / 3); | |||||
} | |||||
static void blurSingleChannelImage (uint8* const data, const int width, const int height, | |||||
const int lineStride, const int repetitions) noexcept | |||||
{ | |||||
jassert (width > 2 && height > 2); | |||||
for (int y = 0; y < height; ++y) | |||||
for (int i = repetitions; --i >= 0;) | |||||
blurDataTriplets (data + lineStride * y, width, 1); | |||||
for (int x = 0; x < width; ++x) | |||||
for (int i = repetitions; --i >= 0;) | |||||
blurDataTriplets (data + x, height, lineStride); | |||||
} | |||||
static void blurSingleChannelImage (Image& image, int radius) | |||||
{ | |||||
const Image::BitmapData bm (image, Image::BitmapData::readWrite); | |||||
blurSingleChannelImage (bm.data, bm.width, bm.height, bm.lineStride, 2 * radius); | |||||
} | |||||
//============================================================================== | |||||
DropShadow::DropShadow() noexcept | |||||
: colour (0x90000000), radius (4) | |||||
{ | |||||
} | |||||
DropShadow::DropShadow (Colour shadowColour, const int r, Point<int> o) noexcept | |||||
: colour (shadowColour), radius (r), offset (o) | |||||
{ | |||||
jassert (radius > 0); | |||||
} | |||||
void DropShadow::drawForImage (Graphics& g, const Image& srcImage) const | |||||
{ | |||||
jassert (radius > 0); | |||||
if (srcImage.isValid()) | |||||
{ | |||||
Image shadowImage (srcImage.convertedToFormat (Image::SingleChannel)); | |||||
shadowImage.duplicateIfShared(); | |||||
blurSingleChannelImage (shadowImage, radius); | |||||
g.setColour (colour); | |||||
g.drawImageAt (shadowImage, offset.x, offset.y, true); | |||||
} | |||||
} | |||||
void DropShadow::drawForPath (Graphics& g, const Path& path) const | |||||
{ | |||||
jassert (radius > 0); | |||||
const Rectangle<int> area ((path.getBounds().getSmallestIntegerContainer() + offset) | |||||
.expanded (radius + 1) | |||||
.getIntersection (g.getClipBounds().expanded (radius + 1))); | |||||
if (area.getWidth() > 2 && area.getHeight() > 2) | |||||
{ | |||||
Image renderedPath (Image::SingleChannel, area.getWidth(), area.getHeight(), true); | |||||
{ | |||||
Graphics g2 (renderedPath); | |||||
g2.setColour (Colours::white); | |||||
g2.fillPath (path, AffineTransform::translation ((float) (offset.x - area.getX()), | |||||
(float) (offset.y - area.getY()))); | |||||
} | |||||
blurSingleChannelImage (renderedPath, radius); | |||||
g.setColour (colour); | |||||
g.drawImageAt (renderedPath, area.getX(), area.getY(), true); | |||||
} | |||||
} | |||||
static void drawShadowSection (Graphics& g, ColourGradient& cg, Rectangle<float> area, | |||||
bool isCorner, float centreX, float centreY, float edgeX, float edgeY) | |||||
{ | |||||
cg.point1 = area.getRelativePoint (centreX, centreY); | |||||
cg.point2 = area.getRelativePoint (edgeX, edgeY); | |||||
cg.isRadial = isCorner; | |||||
g.setGradientFill (cg); | |||||
g.fillRect (area); | |||||
} | |||||
void DropShadow::drawForRectangle (Graphics& g, const Rectangle<int>& targetArea) const | |||||
{ | |||||
ColourGradient cg (colour, 0, 0, colour.withAlpha (0.0f), 0, 0, false); | |||||
for (float i = 0.05f; i < 1.0f; i += 0.1f) | |||||
cg.addColour (1.0 - i, colour.withMultipliedAlpha (i * i)); | |||||
const float radiusInset = (radius + 1) / 2.0f; | |||||
const float expandedRadius = radius + radiusInset; | |||||
const Rectangle<float> area (targetArea.toFloat().reduced (radiusInset) + offset.toFloat()); | |||||
Rectangle<float> r (area.expanded (expandedRadius)); | |||||
Rectangle<float> top (r.removeFromTop (expandedRadius)); | |||||
Rectangle<float> bottom (r.removeFromBottom (expandedRadius)); | |||||
drawShadowSection (g, cg, top.removeFromLeft (expandedRadius), true, 1.0f, 1.0f, 0, 1.0f); | |||||
drawShadowSection (g, cg, top.removeFromRight (expandedRadius), true, 0, 1.0f, 1.0f, 1.0f); | |||||
drawShadowSection (g, cg, top, false, 0, 1.0f, 0, 0); | |||||
drawShadowSection (g, cg, bottom.removeFromLeft (expandedRadius), true, 1.0f, 0, 0, 0); | |||||
drawShadowSection (g, cg, bottom.removeFromRight (expandedRadius), true, 0, 0, 1.0f, 0); | |||||
drawShadowSection (g, cg, bottom, false, 0, 0, 0, 1.0f); | |||||
drawShadowSection (g, cg, r.removeFromLeft (expandedRadius), false, 1.0f, 0, 0, 0); | |||||
drawShadowSection (g, cg, r.removeFromRight (expandedRadius), false, 0, 0, 1.0f, 0); | |||||
g.setColour (colour); | |||||
g.fillRect (area); | |||||
} | |||||
//============================================================================== | |||||
DropShadowEffect::DropShadowEffect() {} | |||||
DropShadowEffect::~DropShadowEffect() {} | |||||
void DropShadowEffect::setShadowProperties (const DropShadow& newShadow) | |||||
{ | |||||
shadow = newShadow; | |||||
} | |||||
void DropShadowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) | |||||
{ | |||||
DropShadow s (shadow); | |||||
s.radius = roundToInt (s.radius * scaleFactor); | |||||
s.colour = s.colour.withMultipliedAlpha (alpha); | |||||
s.offset.x = roundToInt (s.offset.x * scaleFactor); | |||||
s.offset.y = roundToInt (s.offset.y * scaleFactor); | |||||
s.drawForImage (g, image); | |||||
g.setOpacity (alpha); | |||||
g.drawImageAt (image, 0, 0); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,110 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Defines a drop-shadow effect. | |||||
*/ | |||||
struct JUCE_API DropShadow | |||||
{ | |||||
/** Creates a default drop-shadow effect. */ | |||||
DropShadow() noexcept; | |||||
/** Creates a drop-shadow object with the given parameters. */ | |||||
DropShadow (Colour shadowColour, int radius, Point<int> offset) noexcept; | |||||
/** Renders a drop-shadow based on the alpha-channel of the given image. */ | |||||
void drawForImage (Graphics& g, const Image& srcImage) const; | |||||
/** Renders a drop-shadow based on the shape of a path. */ | |||||
void drawForPath (Graphics& g, const Path& path) const; | |||||
/** Renders a drop-shadow for a rectangle. | |||||
Note that for speed, this approximates the shadow using gradients. | |||||
*/ | |||||
void drawForRectangle (Graphics& g, const Rectangle<int>& area) const; | |||||
/** The colour with which to render the shadow. | |||||
In most cases you'll probably want to leave this as black with an alpha | |||||
value of around 0.5 | |||||
*/ | |||||
Colour colour; | |||||
/** The approximate spread of the shadow. */ | |||||
int radius; | |||||
/** The offset of the shadow. */ | |||||
Point<int> offset; | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
An effect filter that adds a drop-shadow behind the image's content. | |||||
(This will only work on images/components that aren't opaque, of course). | |||||
When added to a component, this effect will draw a soft-edged | |||||
shadow based on what gets drawn inside it. The shadow will also | |||||
be applied to the component's children. | |||||
For speed, this doesn't use a proper gaussian blur, but cheats by | |||||
using a simple bilinear filter. If you need a really high-quality | |||||
shadow, check out ImageConvolutionKernel::createGaussianBlur() | |||||
@see Component::setComponentEffect | |||||
*/ | |||||
class JUCE_API DropShadowEffect : public ImageEffectFilter | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a default drop-shadow effect. | |||||
To customise the shadow's appearance, use the setShadowProperties() method. | |||||
*/ | |||||
DropShadowEffect(); | |||||
/** Destructor. */ | |||||
~DropShadowEffect(); | |||||
//============================================================================== | |||||
/** Sets up parameters affecting the shadow's appearance. */ | |||||
void setShadowProperties (const DropShadow& newShadow); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void applyEffect (Image& sourceImage, Graphics& destContext, float scaleFactor, float alpha) override; | |||||
private: | |||||
//============================================================================== | |||||
DropShadow shadow; | |||||
JUCE_LEAK_DETECTOR (DropShadowEffect) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,58 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
GlowEffect::GlowEffect() {} | |||||
GlowEffect::~GlowEffect() {} | |||||
void GlowEffect::setGlowProperties (float newRadius, Colour newColour, Point<int> pos) | |||||
{ | |||||
radius = newRadius; | |||||
colour = newColour; | |||||
offset = pos; | |||||
} | |||||
void GlowEffect::applyEffect (Image& image, Graphics& g, float scaleFactor, float alpha) | |||||
{ | |||||
Image temp (image.getFormat(), image.getWidth(), image.getHeight(), true); | |||||
ImageConvolutionKernel blurKernel (roundToInt (radius * scaleFactor * 2.0f)); | |||||
blurKernel.createGaussianBlur (radius); | |||||
blurKernel.rescaleAllValues (radius); | |||||
blurKernel.applyToImage (temp, image, image.getBounds()); | |||||
g.setColour (colour.withMultipliedAlpha (alpha)); | |||||
g.drawImageAt (temp, offset.x, offset.y, true); | |||||
g.setOpacity (alpha); | |||||
g.drawImageAt (image, offset.x, offset.y, false); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,75 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A component effect that adds a coloured blur around the component's contents. | |||||
(This will only work on non-opaque components). | |||||
@see Component::setComponentEffect, DropShadowEffect | |||||
*/ | |||||
class JUCE_API GlowEffect : public ImageEffectFilter | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a default 'glow' effect. | |||||
To customise its appearance, use the setGlowProperties() method. | |||||
*/ | |||||
GlowEffect(); | |||||
/** Destructor. */ | |||||
~GlowEffect(); | |||||
//============================================================================== | |||||
/** Sets the glow's radius and colour. | |||||
The radius is how large the blur should be, and the colour is | |||||
used to render it (for a less intense glow, lower the colour's | |||||
opacity). | |||||
*/ | |||||
void setGlowProperties (float newRadius, | |||||
Colour newColour, | |||||
Point<int> offset = {}); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void applyEffect (Image&, Graphics&, float scaleFactor, float alpha) override; | |||||
private: | |||||
//============================================================================== | |||||
float radius = 2.0f; | |||||
Colour colour { Colours::white }; | |||||
Point<int> offset; | |||||
JUCE_LEAK_DETECTOR (GlowEffect) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,70 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A graphical effect filter that can be applied to components. | |||||
An ImageEffectFilter can be applied to the image that a component | |||||
paints before it hits the screen. | |||||
This is used for adding effects like shadows, blurs, etc. | |||||
@see Component::setComponentEffect | |||||
*/ | |||||
class JUCE_API ImageEffectFilter | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Overridden to render the effect. | |||||
The implementation of this method must use the image that is passed in | |||||
as its source, and should render its output to the graphics context passed in. | |||||
@param sourceImage the image that the source component has just rendered with | |||||
its paint() method. The image may or may not have an alpha | |||||
channel, depending on whether the component is opaque. | |||||
@param destContext the graphics context to use to draw the resultant image. | |||||
@param scaleFactor a scale factor that has been applied to the image - e.g. if | |||||
this is 2, then the image is actually scaled-up to twice the | |||||
original resolution | |||||
@param alpha the alpha with which to draw the resultant image to the | |||||
target context | |||||
*/ | |||||
virtual void applyEffect (Image& sourceImage, | |||||
Graphics& destContext, | |||||
float scaleFactor, | |||||
float alpha) = 0; | |||||
/** Destructor. */ | |||||
virtual ~ImageEffectFilter() {} | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,356 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace | |||||
{ | |||||
int getLength (const Array<AttributedString::Attribute>& atts) noexcept | |||||
{ | |||||
return atts.size() != 0 ? atts.getReference (atts.size() - 1).range.getEnd() : 0; | |||||
} | |||||
void splitAttributeRanges (Array<AttributedString::Attribute>& atts, int position) | |||||
{ | |||||
for (int i = atts.size(); --i >= 0;) | |||||
{ | |||||
const AttributedString::Attribute& att = atts.getReference (i); | |||||
const int offset = position - att.range.getStart(); | |||||
if (offset >= 0) | |||||
{ | |||||
if (offset > 0 && position < att.range.getEnd()) | |||||
{ | |||||
atts.insert (i + 1, att); | |||||
atts.getReference (i).range.setEnd (position); | |||||
atts.getReference (i + 1).range.setStart (position); | |||||
} | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
Range<int> splitAttributeRanges (Array<AttributedString::Attribute>& atts, Range<int> newRange) | |||||
{ | |||||
newRange = newRange.getIntersectionWith (Range<int> (0, getLength (atts))); | |||||
if (! newRange.isEmpty()) | |||||
{ | |||||
splitAttributeRanges (atts, newRange.getStart()); | |||||
splitAttributeRanges (atts, newRange.getEnd()); | |||||
} | |||||
return newRange; | |||||
} | |||||
void mergeAdjacentRanges (Array<AttributedString::Attribute>& atts) | |||||
{ | |||||
for (int i = atts.size() - 1; --i >= 0;) | |||||
{ | |||||
AttributedString::Attribute& a1 = atts.getReference (i); | |||||
AttributedString::Attribute& a2 = atts.getReference (i + 1); | |||||
if (a1.colour == a2.colour && a1.font == a2.font) | |||||
{ | |||||
a1.range.setEnd (a2.range.getEnd()); | |||||
atts.remove (i + 1); | |||||
if (i < atts.size() - 1) | |||||
++i; | |||||
} | |||||
} | |||||
} | |||||
void appendRange (Array<AttributedString::Attribute>& atts, | |||||
int length, const Font* f, const Colour* c) | |||||
{ | |||||
if (atts.size() == 0) | |||||
{ | |||||
atts.add (AttributedString::Attribute (Range<int> (0, length), | |||||
f != nullptr ? *f : Font(), | |||||
c != nullptr ? *c : Colour (0xff000000))); | |||||
} | |||||
else | |||||
{ | |||||
const int start = getLength (atts); | |||||
atts.add (AttributedString::Attribute (Range<int> (start, start + length), | |||||
f != nullptr ? *f : atts.getReference (atts.size() - 1).font, | |||||
c != nullptr ? *c : atts.getReference (atts.size() - 1).colour)); | |||||
mergeAdjacentRanges (atts); | |||||
} | |||||
} | |||||
void applyFontAndColour (Array<AttributedString::Attribute>& atts, | |||||
Range<int> range, const Font* f, const Colour* c) | |||||
{ | |||||
range = splitAttributeRanges (atts, range); | |||||
for (int i = 0; i < atts.size(); ++i) | |||||
{ | |||||
AttributedString::Attribute& att = atts.getReference (i); | |||||
if (range.getStart() < att.range.getEnd()) | |||||
{ | |||||
if (range.getEnd() <= att.range.getStart()) | |||||
break; | |||||
if (c != nullptr) att.colour = *c; | |||||
if (f != nullptr) att.font = *f; | |||||
} | |||||
} | |||||
mergeAdjacentRanges (atts); | |||||
} | |||||
void truncate (Array<AttributedString::Attribute>& atts, int newLength) | |||||
{ | |||||
splitAttributeRanges (atts, newLength); | |||||
for (int i = atts.size(); --i >= 0;) | |||||
if (atts.getReference (i).range.getStart() >= newLength) | |||||
atts.remove (i); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
AttributedString::Attribute::Attribute() noexcept : colour (0xff000000) {} | |||||
AttributedString::Attribute::~Attribute() noexcept {} | |||||
AttributedString::Attribute::Attribute (Attribute&& other) noexcept | |||||
: range (other.range), | |||||
font (static_cast<Font&&> (other.font)), | |||||
colour (other.colour) | |||||
{ | |||||
} | |||||
AttributedString::Attribute& AttributedString::Attribute::operator= (Attribute&& other) noexcept | |||||
{ | |||||
range = other.range; | |||||
font = static_cast<Font&&> (other.font); | |||||
colour = other.colour; | |||||
return *this; | |||||
} | |||||
AttributedString::Attribute::Attribute (const Attribute& other) noexcept | |||||
: range (other.range), | |||||
font (other.font), | |||||
colour (other.colour) | |||||
{ | |||||
} | |||||
AttributedString::Attribute& AttributedString::Attribute::operator= (const Attribute& other) noexcept | |||||
{ | |||||
range = other.range; | |||||
font = other.font; | |||||
colour = other.colour; | |||||
return *this; | |||||
} | |||||
AttributedString::Attribute::Attribute (Range<int> r, const Font& f, Colour c) noexcept | |||||
: range (r), font (f), colour (c) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
AttributedString::AttributedString() | |||||
: lineSpacing (0.0f), | |||||
justification (Justification::left), | |||||
wordWrap (AttributedString::byWord), | |||||
readingDirection (AttributedString::natural) | |||||
{ | |||||
} | |||||
AttributedString::AttributedString (const String& newString) | |||||
: lineSpacing (0.0f), | |||||
justification (Justification::left), | |||||
wordWrap (AttributedString::byWord), | |||||
readingDirection (AttributedString::natural) | |||||
{ | |||||
setText (newString); | |||||
} | |||||
AttributedString::AttributedString (const AttributedString& other) | |||||
: text (other.text), | |||||
lineSpacing (other.lineSpacing), | |||||
justification (other.justification), | |||||
wordWrap (other.wordWrap), | |||||
readingDirection (other.readingDirection), | |||||
attributes (other.attributes) | |||||
{ | |||||
} | |||||
AttributedString& AttributedString::operator= (const AttributedString& other) | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
text = other.text; | |||||
lineSpacing = other.lineSpacing; | |||||
justification = other.justification; | |||||
wordWrap = other.wordWrap; | |||||
readingDirection = other.readingDirection; | |||||
attributes = other.attributes; | |||||
} | |||||
return *this; | |||||
} | |||||
AttributedString::AttributedString (AttributedString&& other) noexcept | |||||
: text (static_cast<String&&> (other.text)), | |||||
lineSpacing (other.lineSpacing), | |||||
justification (other.justification), | |||||
wordWrap (other.wordWrap), | |||||
readingDirection (other.readingDirection), | |||||
attributes (static_cast<Array<Attribute>&&> (other.attributes)) | |||||
{ | |||||
} | |||||
AttributedString& AttributedString::operator= (AttributedString&& other) noexcept | |||||
{ | |||||
text = static_cast<String&&> (other.text); | |||||
lineSpacing = other.lineSpacing; | |||||
justification = other.justification; | |||||
wordWrap = other.wordWrap; | |||||
readingDirection = other.readingDirection; | |||||
attributes = static_cast<Array<Attribute>&&> (other.attributes); | |||||
return *this; | |||||
} | |||||
AttributedString::~AttributedString() noexcept {} | |||||
void AttributedString::setText (const String& newText) | |||||
{ | |||||
const int newLength = newText.length(); | |||||
const int oldLength = getLength (attributes); | |||||
if (newLength > oldLength) | |||||
appendRange (attributes, newLength - oldLength, nullptr, nullptr); | |||||
else if (newLength < oldLength) | |||||
truncate (attributes, newLength); | |||||
text = newText; | |||||
} | |||||
void AttributedString::append (const String& textToAppend) | |||||
{ | |||||
text += textToAppend; | |||||
appendRange (attributes, textToAppend.length(), nullptr, nullptr); | |||||
} | |||||
void AttributedString::append (const String& textToAppend, const Font& font) | |||||
{ | |||||
text += textToAppend; | |||||
appendRange (attributes, textToAppend.length(), &font, nullptr); | |||||
} | |||||
void AttributedString::append (const String& textToAppend, Colour colour) | |||||
{ | |||||
text += textToAppend; | |||||
appendRange (attributes, textToAppend.length(), nullptr, &colour); | |||||
} | |||||
void AttributedString::append (const String& textToAppend, const Font& font, Colour colour) | |||||
{ | |||||
text += textToAppend; | |||||
appendRange (attributes, textToAppend.length(), &font, &colour); | |||||
} | |||||
void AttributedString::append (const AttributedString& other) | |||||
{ | |||||
const int originalLength = getLength (attributes); | |||||
const int originalNumAtts = attributes.size(); | |||||
text += other.text; | |||||
attributes.addArray (other.attributes); | |||||
for (int i = originalNumAtts; i < attributes.size(); ++i) | |||||
attributes.getReference (i).range += originalLength; | |||||
mergeAdjacentRanges (attributes); | |||||
} | |||||
void AttributedString::clear() | |||||
{ | |||||
text.clear(); | |||||
attributes.clear(); | |||||
} | |||||
void AttributedString::setJustification (Justification newJustification) noexcept | |||||
{ | |||||
justification = newJustification; | |||||
} | |||||
void AttributedString::setWordWrap (WordWrap newWordWrap) noexcept | |||||
{ | |||||
wordWrap = newWordWrap; | |||||
} | |||||
void AttributedString::setReadingDirection (ReadingDirection newReadingDirection) noexcept | |||||
{ | |||||
readingDirection = newReadingDirection; | |||||
} | |||||
void AttributedString::setLineSpacing (const float newLineSpacing) noexcept | |||||
{ | |||||
lineSpacing = newLineSpacing; | |||||
} | |||||
void AttributedString::setColour (Range<int> range, Colour colour) | |||||
{ | |||||
applyFontAndColour (attributes, range, nullptr, &colour); | |||||
} | |||||
void AttributedString::setFont (Range<int> range, const Font& font) | |||||
{ | |||||
applyFontAndColour (attributes, range, &font, nullptr); | |||||
} | |||||
void AttributedString::setColour (Colour colour) | |||||
{ | |||||
setColour (Range<int> (0, getLength (attributes)), colour); | |||||
} | |||||
void AttributedString::setFont (const Font& font) | |||||
{ | |||||
setFont (Range<int> (0, getLength (attributes)), font); | |||||
} | |||||
void AttributedString::draw (Graphics& g, const Rectangle<float>& area) const | |||||
{ | |||||
if (text.isNotEmpty() && g.clipRegionIntersects (area.getSmallestIntegerContainer())) | |||||
{ | |||||
jassert (text.length() == getLength (attributes)); | |||||
if (! g.getInternalContext().drawTextLayout (*this, area)) | |||||
{ | |||||
TextLayout layout; | |||||
layout.createLayout (*this, area.getWidth()); | |||||
layout.draw (g, area); | |||||
} | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,207 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A text string with a set of colour/font settings that are associated with sub-ranges | |||||
of the text. | |||||
An attributed string lets you create a string with varied fonts, colours, word-wrapping, | |||||
layout, etc., and draw it using AttributedString::draw(). | |||||
@see TextLayout | |||||
*/ | |||||
class JUCE_API AttributedString | |||||
{ | |||||
public: | |||||
/** Creates an empty attributed string. */ | |||||
AttributedString(); | |||||
/** Creates an attributed string with the given text. */ | |||||
explicit AttributedString (const String& text); | |||||
AttributedString (const AttributedString&); | |||||
AttributedString& operator= (const AttributedString&); | |||||
AttributedString (AttributedString&&) noexcept; | |||||
AttributedString& operator= (AttributedString&&) noexcept; | |||||
/** Destructor. */ | |||||
~AttributedString() noexcept; | |||||
//============================================================================== | |||||
/** Returns the complete text of this attributed string. */ | |||||
const String& getText() const noexcept { return text; } | |||||
/** Replaces all the text. | |||||
This will change the text, but won't affect any of the colour or font attributes | |||||
that have been added. | |||||
*/ | |||||
void setText (const String& newText); | |||||
/** Appends some text (with a default font and colour). */ | |||||
void append (const String& textToAppend); | |||||
/** Appends some text, with a specified font, and the default colour (black). */ | |||||
void append (const String& textToAppend, const Font& font); | |||||
/** Appends some text, with a specified colour, and the default font. */ | |||||
void append (const String& textToAppend, Colour colour); | |||||
/** Appends some text, with a specified font and colour. */ | |||||
void append (const String& textToAppend, const Font& font, Colour colour); | |||||
/** Appends another AttributedString to this one. | |||||
Note that this will only append the text, fonts, and colours - it won't copy any | |||||
other properties such as justification, line-spacing, etc from the other object. | |||||
*/ | |||||
void append (const AttributedString& other); | |||||
/** Resets the string, clearing all text and attributes. | |||||
Note that this won't affect global settings like the justification type, | |||||
word-wrap mode, etc. | |||||
*/ | |||||
void clear(); | |||||
//============================================================================== | |||||
/** Draws this string within the given area. | |||||
The layout of the string within the rectangle is controlled by the justification | |||||
value passed to setJustification(). | |||||
*/ | |||||
void draw (Graphics& g, const Rectangle<float>& area) const; | |||||
//============================================================================== | |||||
/** Returns the justification that should be used for laying-out the text. | |||||
This may include both vertical and horizontal flags. | |||||
*/ | |||||
Justification getJustification() const noexcept { return justification; } | |||||
/** Sets the justification that should be used for laying-out the text. | |||||
This may include both vertical and horizontal flags. | |||||
*/ | |||||
void setJustification (Justification newJustification) noexcept; | |||||
//============================================================================== | |||||
/** Types of word-wrap behaviour. | |||||
@see getWordWrap, setWordWrap | |||||
*/ | |||||
enum WordWrap | |||||
{ | |||||
none, /**< No word-wrapping: lines extend indefinitely. */ | |||||
byWord, /**< Lines are wrapped on a word boundary. */ | |||||
byChar, /**< Lines are wrapped on a character boundary. */ | |||||
}; | |||||
/** Returns the word-wrapping behaviour. */ | |||||
WordWrap getWordWrap() const noexcept { return wordWrap; } | |||||
/** Sets the word-wrapping behaviour. */ | |||||
void setWordWrap (WordWrap newWordWrap) noexcept; | |||||
//============================================================================== | |||||
/** Types of reading direction that can be used. | |||||
@see getReadingDirection, setReadingDirection | |||||
*/ | |||||
enum ReadingDirection | |||||
{ | |||||
natural, | |||||
leftToRight, | |||||
rightToLeft, | |||||
}; | |||||
/** Returns the reading direction for the text. */ | |||||
ReadingDirection getReadingDirection() const noexcept { return readingDirection; } | |||||
/** Sets the reading direction that should be used for the text. */ | |||||
void setReadingDirection (ReadingDirection newReadingDirection) noexcept; | |||||
//============================================================================== | |||||
/** Returns the extra line-spacing distance. */ | |||||
float getLineSpacing() const noexcept { return lineSpacing; } | |||||
/** Sets an extra line-spacing distance. */ | |||||
void setLineSpacing (float newLineSpacing) noexcept; | |||||
//============================================================================== | |||||
/** An attribute that has been applied to a range of characters in an AttributedString. */ | |||||
class JUCE_API Attribute | |||||
{ | |||||
public: | |||||
Attribute() noexcept; | |||||
~Attribute() noexcept; | |||||
Attribute (const Attribute&) noexcept; | |||||
Attribute& operator= (const Attribute&) noexcept; | |||||
Attribute (Attribute&&) noexcept; | |||||
Attribute& operator= (Attribute&&) noexcept; | |||||
/** Creates an attribute that specifies the font and colour for a range of characters. */ | |||||
Attribute (Range<int> range, const Font& font, Colour colour) noexcept; | |||||
/** The range of characters to which this attribute will be applied. */ | |||||
Range<int> range; | |||||
/** The font for this range of characters. */ | |||||
Font font; | |||||
/** The colour for this range of characters. */ | |||||
Colour colour; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (Attribute) | |||||
}; | |||||
/** Returns the number of attributes that have been added to this string. */ | |||||
int getNumAttributes() const noexcept { return attributes.size(); } | |||||
/** Returns one of the string's attributes. | |||||
The index provided must be less than getNumAttributes(), and >= 0. | |||||
*/ | |||||
const Attribute& getAttribute (int index) const noexcept { return attributes.getReference (index); } | |||||
//============================================================================== | |||||
/** Adds a colour attribute for the specified range. */ | |||||
void setColour (Range<int> range, Colour colour); | |||||
/** Removes all existing colour attributes, and applies this colour to the whole string. */ | |||||
void setColour (Colour colour); | |||||
/** Adds a font attribute for the specified range. */ | |||||
void setFont (Range<int> range, const Font& font); | |||||
/** Removes all existing font attributes, and applies this font to the whole string. */ | |||||
void setFont (const Font& font); | |||||
private: | |||||
String text; | |||||
float lineSpacing; | |||||
Justification justification; | |||||
WordWrap wordWrap; | |||||
ReadingDirection readingDirection; | |||||
Array<Attribute> attributes; | |||||
JUCE_LEAK_DETECTOR (AttributedString) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,413 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
class CustomTypeface::GlyphInfo | |||||
{ | |||||
public: | |||||
GlyphInfo (const juce_wchar c, const Path& p, const float w) noexcept | |||||
: character (c), path (p), width (w) | |||||
{ | |||||
} | |||||
struct KerningPair | |||||
{ | |||||
juce_wchar character2; | |||||
float kerningAmount; | |||||
}; | |||||
void addKerningPair (const juce_wchar subsequentCharacter, | |||||
const float extraKerningAmount) noexcept | |||||
{ | |||||
KerningPair kp; | |||||
kp.character2 = subsequentCharacter; | |||||
kp.kerningAmount = extraKerningAmount; | |||||
kerningPairs.add (kp); | |||||
} | |||||
float getHorizontalSpacing (const juce_wchar subsequentCharacter) const noexcept | |||||
{ | |||||
if (subsequentCharacter != 0) | |||||
for (int i = kerningPairs.size(); --i >= 0;) | |||||
if (kerningPairs.getReference(i).character2 == subsequentCharacter) | |||||
return width + kerningPairs.getReference(i).kerningAmount; | |||||
return width; | |||||
} | |||||
const juce_wchar character; | |||||
const Path path; | |||||
float width; | |||||
Array <KerningPair> kerningPairs; | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlyphInfo) | |||||
}; | |||||
//============================================================================== | |||||
namespace CustomTypefaceHelpers | |||||
{ | |||||
static juce_wchar readChar (InputStream& in) | |||||
{ | |||||
uint32 n = (uint32) (uint16) in.readShort(); | |||||
if (n >= 0xd800 && n <= 0xdfff) | |||||
{ | |||||
const uint32 nextWord = (uint32) (uint16) in.readShort(); | |||||
jassert (nextWord >= 0xdc00); // illegal unicode character! | |||||
n = 0x10000 + (((n - 0xd800) << 10) | (nextWord - 0xdc00)); | |||||
} | |||||
return (juce_wchar) n; | |||||
} | |||||
static void writeChar (OutputStream& out, juce_wchar charToWrite) | |||||
{ | |||||
if (charToWrite >= 0x10000) | |||||
{ | |||||
charToWrite -= 0x10000; | |||||
out.writeShort ((short) (uint16) (0xd800 + (charToWrite >> 10))); | |||||
out.writeShort ((short) (uint16) (0xdc00 + (charToWrite & 0x3ff))); | |||||
} | |||||
else | |||||
{ | |||||
out.writeShort ((short) (uint16) charToWrite); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
CustomTypeface::CustomTypeface() | |||||
: Typeface (String(), String()) | |||||
{ | |||||
clear(); | |||||
} | |||||
CustomTypeface::CustomTypeface (InputStream& serialisedTypefaceStream) | |||||
: Typeface (String(), String()) | |||||
{ | |||||
clear(); | |||||
GZIPDecompressorInputStream gzin (serialisedTypefaceStream); | |||||
BufferedInputStream in (gzin, 32768); | |||||
name = in.readString(); | |||||
const bool isBold = in.readBool(); | |||||
const bool isItalic = in.readBool(); | |||||
style = FontStyleHelpers::getStyleName (isBold, isItalic); | |||||
ascent = in.readFloat(); | |||||
defaultCharacter = CustomTypefaceHelpers::readChar (in); | |||||
int numChars = in.readInt(); | |||||
for (int i = 0; i < numChars; ++i) | |||||
{ | |||||
const juce_wchar c = CustomTypefaceHelpers::readChar (in); | |||||
const float width = in.readFloat(); | |||||
Path p; | |||||
p.loadPathFromStream (in); | |||||
addGlyph (c, p, width); | |||||
} | |||||
const int numKerningPairs = in.readInt(); | |||||
for (int i = 0; i < numKerningPairs; ++i) | |||||
{ | |||||
const juce_wchar char1 = CustomTypefaceHelpers::readChar (in); | |||||
const juce_wchar char2 = CustomTypefaceHelpers::readChar (in); | |||||
addKerningPair (char1, char2, in.readFloat()); | |||||
} | |||||
} | |||||
CustomTypeface::~CustomTypeface() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void CustomTypeface::clear() | |||||
{ | |||||
defaultCharacter = 0; | |||||
ascent = 1.0f; | |||||
style = "Regular"; | |||||
zeromem (lookupTable, sizeof (lookupTable)); | |||||
glyphs.clear(); | |||||
} | |||||
void CustomTypeface::setCharacteristics (const String& newName, const float newAscent, const bool isBold, | |||||
const bool isItalic, const juce_wchar newDefaultCharacter) noexcept | |||||
{ | |||||
name = newName; | |||||
defaultCharacter = newDefaultCharacter; | |||||
ascent = newAscent; | |||||
style = FontStyleHelpers::getStyleName (isBold, isItalic); | |||||
} | |||||
void CustomTypeface::setCharacteristics (const String& newName, const String& newStyle, const float newAscent, | |||||
const juce_wchar newDefaultCharacter) noexcept | |||||
{ | |||||
name = newName; | |||||
style = newStyle; | |||||
defaultCharacter = newDefaultCharacter; | |||||
ascent = newAscent; | |||||
} | |||||
void CustomTypeface::addGlyph (const juce_wchar character, const Path& path, const float width) noexcept | |||||
{ | |||||
// Check that you're not trying to add the same character twice.. | |||||
jassert (findGlyph (character, false) == nullptr); | |||||
if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable))) | |||||
lookupTable [character] = (short) glyphs.size(); | |||||
glyphs.add (new GlyphInfo (character, path, width)); | |||||
} | |||||
void CustomTypeface::addKerningPair (const juce_wchar char1, const juce_wchar char2, const float extraAmount) noexcept | |||||
{ | |||||
if (extraAmount != 0.0f) | |||||
{ | |||||
if (auto* g = findGlyph (char1, true)) | |||||
g->addKerningPair (char2, extraAmount); | |||||
else | |||||
jassertfalse; // can only add kerning pairs for characters that exist! | |||||
} | |||||
} | |||||
CustomTypeface::GlyphInfo* CustomTypeface::findGlyph (const juce_wchar character, const bool loadIfNeeded) noexcept | |||||
{ | |||||
if (isPositiveAndBelow ((int) character, numElementsInArray (lookupTable)) && lookupTable [character] > 0) | |||||
return glyphs [(int) lookupTable [(int) character]]; | |||||
for (int i = 0; i < glyphs.size(); ++i) | |||||
{ | |||||
GlyphInfo* const g = glyphs.getUnchecked(i); | |||||
if (g->character == character) | |||||
return g; | |||||
} | |||||
if (loadIfNeeded && loadGlyphIfPossible (character)) | |||||
return findGlyph (character, false); | |||||
return nullptr; | |||||
} | |||||
bool CustomTypeface::loadGlyphIfPossible (const juce_wchar /*characterNeeded*/) | |||||
{ | |||||
return false; | |||||
} | |||||
void CustomTypeface::addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept | |||||
{ | |||||
setCharacteristics (name, style, typefaceToCopy.getAscent(), defaultCharacter); | |||||
for (int i = 0; i < numCharacters; ++i) | |||||
{ | |||||
const juce_wchar c = (juce_wchar) (characterStartIndex + static_cast<juce_wchar> (i)); | |||||
Array <int> glyphIndexes; | |||||
Array <float> offsets; | |||||
typefaceToCopy.getGlyphPositions (String::charToString (c), glyphIndexes, offsets); | |||||
const int glyphIndex = glyphIndexes.getFirst(); | |||||
if (glyphIndex >= 0 && glyphIndexes.size() > 0) | |||||
{ | |||||
const float glyphWidth = offsets[1]; | |||||
Path p; | |||||
typefaceToCopy.getOutlineForGlyph (glyphIndex, p); | |||||
addGlyph (c, p, glyphWidth); | |||||
for (int j = glyphs.size() - 1; --j >= 0;) | |||||
{ | |||||
const juce_wchar char2 = glyphs.getUnchecked (j)->character; | |||||
glyphIndexes.clearQuick(); | |||||
offsets.clearQuick(); | |||||
typefaceToCopy.getGlyphPositions (String::charToString (c) + String::charToString (char2), glyphIndexes, offsets); | |||||
if (offsets.size() > 1) | |||||
addKerningPair (c, char2, offsets[1] - glyphWidth); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
bool CustomTypeface::writeToStream (OutputStream& outputStream) | |||||
{ | |||||
GZIPCompressorOutputStream out (&outputStream); | |||||
out.writeString (name); | |||||
out.writeBool (FontStyleHelpers::isBold (style)); | |||||
out.writeBool (FontStyleHelpers::isItalic (style)); | |||||
out.writeFloat (ascent); | |||||
CustomTypefaceHelpers::writeChar (out, defaultCharacter); | |||||
out.writeInt (glyphs.size()); | |||||
int numKerningPairs = 0; | |||||
for (int i = 0; i < glyphs.size(); ++i) | |||||
{ | |||||
const GlyphInfo* const g = glyphs.getUnchecked (i); | |||||
CustomTypefaceHelpers::writeChar (out, g->character); | |||||
out.writeFloat (g->width); | |||||
g->path.writePathToStream (out); | |||||
numKerningPairs += g->kerningPairs.size(); | |||||
} | |||||
out.writeInt (numKerningPairs); | |||||
for (int i = 0; i < glyphs.size(); ++i) | |||||
{ | |||||
const GlyphInfo* const g = glyphs.getUnchecked (i); | |||||
for (int j = 0; j < g->kerningPairs.size(); ++j) | |||||
{ | |||||
const GlyphInfo::KerningPair& p = g->kerningPairs.getReference (j); | |||||
CustomTypefaceHelpers::writeChar (out, g->character); | |||||
CustomTypefaceHelpers::writeChar (out, p.character2); | |||||
out.writeFloat (p.kerningAmount); | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
//============================================================================== | |||||
float CustomTypeface::getAscent() const { return ascent; } | |||||
float CustomTypeface::getDescent() const { return 1.0f - ascent; } | |||||
float CustomTypeface::getHeightToPointsFactor() const { return ascent; } | |||||
float CustomTypeface::getStringWidth (const String& text) | |||||
{ | |||||
float x = 0; | |||||
for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) | |||||
{ | |||||
const juce_wchar c = t.getAndAdvance(); | |||||
if (const GlyphInfo* const glyph = findGlyph (c, true)) | |||||
{ | |||||
x += glyph->getHorizontalSpacing (*t); | |||||
} | |||||
else | |||||
{ | |||||
const Typeface::Ptr fallbackTypeface (Typeface::getFallbackTypeface()); | |||||
if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||||
x += fallbackTypeface->getStringWidth (String::charToString (c)); | |||||
} | |||||
} | |||||
return x; | |||||
} | |||||
void CustomTypeface::getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array<float>& xOffsets) | |||||
{ | |||||
xOffsets.add (0); | |||||
float x = 0; | |||||
for (String::CharPointerType t (text.getCharPointer()); ! t.isEmpty();) | |||||
{ | |||||
float width = 0.0f; | |||||
int glyphChar = 0; | |||||
const juce_wchar c = t.getAndAdvance(); | |||||
if (const GlyphInfo* const glyph = findGlyph (c, true)) | |||||
{ | |||||
width = glyph->getHorizontalSpacing (*t); | |||||
glyphChar = (int) glyph->character; | |||||
} | |||||
else | |||||
{ | |||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||||
if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||||
{ | |||||
Array <int> subGlyphs; | |||||
Array <float> subOffsets; | |||||
fallbackTypeface->getGlyphPositions (String::charToString (c), subGlyphs, subOffsets); | |||||
if (subGlyphs.size() > 0) | |||||
{ | |||||
glyphChar = subGlyphs.getFirst(); | |||||
width = subOffsets[1]; | |||||
} | |||||
} | |||||
} | |||||
x += width; | |||||
resultGlyphs.add (glyphChar); | |||||
xOffsets.add (x); | |||||
} | |||||
} | |||||
bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path) | |||||
{ | |||||
if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) | |||||
{ | |||||
path = glyph->path; | |||||
return true; | |||||
} | |||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||||
if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||||
return fallbackTypeface->getOutlineForGlyph (glyphNumber, path); | |||||
return false; | |||||
} | |||||
EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) | |||||
{ | |||||
if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true)) | |||||
{ | |||||
if (! glyph->path.isEmpty()) | |||||
return new EdgeTable (glyph->path.getBoundsTransformed (transform) | |||||
.getSmallestIntegerContainer().expanded (1, 0), | |||||
glyph->path, transform); | |||||
} | |||||
else | |||||
{ | |||||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface()); | |||||
if (fallbackTypeface != nullptr && fallbackTypeface != this) | |||||
return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight); | |||||
} | |||||
return nullptr; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,165 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A typeface that can be populated with custom glyphs. | |||||
You can create a CustomTypeface if you need one that contains your own glyphs, | |||||
or if you need to load a typeface from a Juce-formatted binary stream. | |||||
If you want to create a copy of a native face, you can use addGlyphsFromOtherTypeface() | |||||
to copy glyphs into this face. | |||||
NOTE! For most people this class is almost certainly NOT the right tool to use! | |||||
If what you want to do is to embed a font into your exe, then your best plan is | |||||
probably to embed your TTF/OTF font file into your binary using the Projucer, | |||||
and then call Typeface::createSystemTypefaceFor() to load it from memory. | |||||
@see Typeface, Font | |||||
*/ | |||||
class JUCE_API CustomTypeface : public Typeface | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a new, empty typeface. */ | |||||
CustomTypeface(); | |||||
/** Loads a typeface from a previously saved stream. | |||||
The stream must have been created by writeToStream(). | |||||
NOTE! Since this class was written, support was added for loading real font files from | |||||
memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||||
is more appropriate than using this class to store it in a proprietary format. | |||||
@see writeToStream | |||||
*/ | |||||
explicit CustomTypeface (InputStream& serialisedTypefaceStream); | |||||
/** Destructor. */ | |||||
~CustomTypeface(); | |||||
//============================================================================== | |||||
/** Resets this typeface, deleting all its glyphs and settings. */ | |||||
void clear(); | |||||
/** Sets the vital statistics for the typeface. | |||||
@param fontFamily the typeface's font family | |||||
@param ascent the ascent - this is normalised to a height of 1.0 and this is | |||||
the value that will be returned by Typeface::getAscent(). The | |||||
descent is assumed to be (1.0 - ascent) | |||||
@param isBold should be true if the typeface is bold | |||||
@param isItalic should be true if the typeface is italic | |||||
@param defaultCharacter the character to be used as a replacement if there's | |||||
no glyph available for the character that's being drawn | |||||
*/ | |||||
void setCharacteristics (const String& fontFamily, float ascent, | |||||
bool isBold, bool isItalic, | |||||
juce_wchar defaultCharacter) noexcept; | |||||
/** Sets the vital statistics for the typeface. | |||||
@param fontFamily the typeface's font family | |||||
@param fontStyle the typeface's font style | |||||
@param ascent the ascent - this is normalised to a height of 1.0 and this is | |||||
the value that will be returned by Typeface::getAscent(). The | |||||
descent is assumed to be (1.0 - ascent) | |||||
@param defaultCharacter the character to be used as a replacement if there's | |||||
no glyph available for the character that's being drawn | |||||
*/ | |||||
void setCharacteristics (const String& fontFamily, const String& fontStyle, | |||||
float ascent, juce_wchar defaultCharacter) noexcept; | |||||
/** Adds a glyph to the typeface. | |||||
The path that is passed in is normalised so that the font height is 1.0, and its | |||||
origin is the anchor point of the character on its baseline. | |||||
The width is the nominal width of the character, and any extra kerning values that | |||||
are specified will be added to this width. | |||||
*/ | |||||
void addGlyph (juce_wchar character, const Path& path, float width) noexcept; | |||||
/** Specifies an extra kerning amount to be used between a pair of characters. | |||||
The amount will be added to the nominal width of the first character when laying out a string. | |||||
*/ | |||||
void addKerningPair (juce_wchar char1, juce_wchar char2, float extraAmount) noexcept; | |||||
/** Adds a range of glyphs from another typeface. | |||||
This will attempt to pull in the paths and kerning information from another typeface and | |||||
add it to this one. | |||||
*/ | |||||
void addGlyphsFromOtherTypeface (Typeface& typefaceToCopy, juce_wchar characterStartIndex, int numCharacters) noexcept; | |||||
/** Saves this typeface as a Juce-formatted font file. | |||||
A CustomTypeface can be created to reload the data that is written - see the CustomTypeface | |||||
constructor. | |||||
NOTE! Since this class was written, support was added for loading real font files from | |||||
memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||||
is more appropriate than using this class to store it in a proprietary format. | |||||
*/ | |||||
bool writeToStream (OutputStream& outputStream); | |||||
//============================================================================== | |||||
// The following methods implement the basic Typeface behaviour. | |||||
float getAscent() const override; | |||||
float getDescent() const override; | |||||
float getHeightToPointsFactor() const override; | |||||
float getStringWidth (const String&) override; | |||||
void getGlyphPositions (const String&, Array <int>& glyphs, Array<float>& xOffsets) override; | |||||
bool getOutlineForGlyph (int glyphNumber, Path&) override; | |||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override; | |||||
protected: | |||||
//============================================================================== | |||||
juce_wchar defaultCharacter; | |||||
float ascent; | |||||
//============================================================================== | |||||
/** If a subclass overrides this, it can load glyphs into the font on-demand. | |||||
When methods such as getGlyphPositions() or getOutlineForGlyph() are asked for a | |||||
particular character and there's no corresponding glyph, they'll call this | |||||
method so that a subclass can try to add that glyph, returning true if it | |||||
manages to do so. | |||||
*/ | |||||
virtual bool loadGlyphIfPossible (juce_wchar characterNeeded); | |||||
private: | |||||
//============================================================================== | |||||
class GlyphInfo; | |||||
friend struct ContainerDeletePolicy<GlyphInfo>; | |||||
OwnedArray<GlyphInfo> glyphs; | |||||
short lookupTable [128]; | |||||
GlyphInfo* findGlyph (const juce_wchar character, bool loadIfNeeded) noexcept; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CustomTypeface) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,722 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
namespace FontValues | |||||
{ | |||||
static float limitFontHeight (const float height) noexcept | |||||
{ | |||||
return jlimit (0.1f, 10000.0f, height); | |||||
} | |||||
const float defaultFontHeight = 14.0f; | |||||
float minimumHorizontalScale = 0.7f; | |||||
String fallbackFont; | |||||
String fallbackFontStyle; | |||||
} | |||||
typedef Typeface::Ptr (*GetTypefaceForFont) (const Font&); | |||||
GetTypefaceForFont juce_getTypefaceForFont = nullptr; | |||||
float Font::getDefaultMinimumHorizontalScaleFactor() noexcept { return FontValues::minimumHorizontalScale; } | |||||
void Font::setDefaultMinimumHorizontalScaleFactor (float newValue) noexcept { FontValues::minimumHorizontalScale = newValue; } | |||||
//============================================================================== | |||||
class TypefaceCache : private DeletedAtShutdown | |||||
{ | |||||
public: | |||||
TypefaceCache() | |||||
{ | |||||
setSize (10); | |||||
} | |||||
~TypefaceCache() | |||||
{ | |||||
clearSingletonInstance(); | |||||
} | |||||
juce_DeclareSingleton (TypefaceCache, false) | |||||
void setSize (const int numToCache) | |||||
{ | |||||
const ScopedWriteLock sl (lock); | |||||
faces.clear(); | |||||
faces.insertMultiple (-1, CachedFace(), numToCache); | |||||
} | |||||
void clear() | |||||
{ | |||||
const ScopedWriteLock sl (lock); | |||||
setSize (faces.size()); | |||||
defaultFace = nullptr; | |||||
} | |||||
Typeface::Ptr findTypefaceFor (const Font& font) | |||||
{ | |||||
const ScopedReadLock slr (lock); | |||||
auto faceName = font.getTypefaceName(); | |||||
auto faceStyle = font.getTypefaceStyle(); | |||||
jassert (faceName.isNotEmpty()); | |||||
for (int i = faces.size(); --i >= 0;) | |||||
{ | |||||
CachedFace& face = faces.getReference(i); | |||||
if (face.typefaceName == faceName | |||||
&& face.typefaceStyle == faceStyle | |||||
&& face.typeface != nullptr | |||||
&& face.typeface->isSuitableForFont (font)) | |||||
{ | |||||
face.lastUsageCount = ++counter; | |||||
return face.typeface; | |||||
} | |||||
} | |||||
const ScopedWriteLock slw (lock); | |||||
int replaceIndex = 0; | |||||
auto bestLastUsageCount = std::numeric_limits<size_t>::max(); | |||||
for (int i = faces.size(); --i >= 0;) | |||||
{ | |||||
auto lu = faces.getReference(i).lastUsageCount; | |||||
if (bestLastUsageCount > lu) | |||||
{ | |||||
bestLastUsageCount = lu; | |||||
replaceIndex = i; | |||||
} | |||||
} | |||||
auto& face = faces.getReference (replaceIndex); | |||||
face.typefaceName = faceName; | |||||
face.typefaceStyle = faceStyle; | |||||
face.lastUsageCount = ++counter; | |||||
if (juce_getTypefaceForFont == nullptr) | |||||
face.typeface = Font::getDefaultTypefaceForFont (font); | |||||
else | |||||
face.typeface = juce_getTypefaceForFont (font); | |||||
jassert (face.typeface != nullptr); // the look and feel must return a typeface! | |||||
if (defaultFace == nullptr && font == Font()) | |||||
defaultFace = face.typeface; | |||||
return face.typeface; | |||||
} | |||||
Typeface::Ptr defaultFace; | |||||
private: | |||||
struct CachedFace | |||||
{ | |||||
CachedFace() noexcept : lastUsageCount (0) {} | |||||
// Although it seems a bit wacky to store the name here, it's because it may be a | |||||
// placeholder rather than a real one, e.g. "<Sans-Serif>" vs the actual typeface name. | |||||
// Since the typeface itself doesn't know that it may have this alias, the name under | |||||
// which it was fetched needs to be stored separately. | |||||
String typefaceName, typefaceStyle; | |||||
size_t lastUsageCount; | |||||
Typeface::Ptr typeface; | |||||
}; | |||||
ReadWriteLock lock; | |||||
Array<CachedFace> faces; | |||||
size_t counter = 0; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TypefaceCache) | |||||
}; | |||||
juce_ImplementSingleton (TypefaceCache) | |||||
void Typeface::setTypefaceCacheSize (int numFontsToCache) | |||||
{ | |||||
TypefaceCache::getInstance()->setSize (numFontsToCache); | |||||
} | |||||
void (*clearOpenGLGlyphCache)() = nullptr; | |||||
void Typeface::clearTypefaceCache() | |||||
{ | |||||
TypefaceCache::getInstance()->clear(); | |||||
RenderingHelpers::SoftwareRendererSavedState::clearGlyphCache(); | |||||
if (clearOpenGLGlyphCache != nullptr) | |||||
clearOpenGLGlyphCache(); | |||||
} | |||||
//============================================================================== | |||||
class Font::SharedFontInternal : public ReferenceCountedObject | |||||
{ | |||||
public: | |||||
SharedFontInternal() noexcept | |||||
: typeface (TypefaceCache::getInstance()->defaultFace), | |||||
typefaceName (Font::getDefaultSansSerifFontName()), | |||||
typefaceStyle (Font::getDefaultStyle()), | |||||
height (FontValues::defaultFontHeight) | |||||
{ | |||||
} | |||||
SharedFontInternal (int styleFlags, float fontHeight) noexcept | |||||
: typefaceName (Font::getDefaultSansSerifFontName()), | |||||
typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), | |||||
height (fontHeight), | |||||
underline ((styleFlags & underlined) != 0) | |||||
{ | |||||
if (styleFlags == plain) | |||||
typeface = TypefaceCache::getInstance()->defaultFace; | |||||
} | |||||
SharedFontInternal (const String& name, int styleFlags, float fontHeight) noexcept | |||||
: typefaceName (name), | |||||
typefaceStyle (FontStyleHelpers::getStyleName (styleFlags)), | |||||
height (fontHeight), | |||||
underline ((styleFlags & underlined) != 0) | |||||
{ | |||||
if (styleFlags == plain && typefaceName.isEmpty()) | |||||
typeface = TypefaceCache::getInstance()->defaultFace; | |||||
} | |||||
SharedFontInternal (const String& name, const String& style, float fontHeight) noexcept | |||||
: typefaceName (name), typefaceStyle (style), height (fontHeight) | |||||
{ | |||||
if (typefaceName.isEmpty()) | |||||
typefaceName = Font::getDefaultSansSerifFontName(); | |||||
} | |||||
explicit SharedFontInternal (const Typeface::Ptr& face) noexcept | |||||
: typeface (face), | |||||
typefaceName (face->getName()), | |||||
typefaceStyle (face->getStyle()), | |||||
height (FontValues::defaultFontHeight) | |||||
{ | |||||
jassert (typefaceName.isNotEmpty()); | |||||
} | |||||
SharedFontInternal (const SharedFontInternal& other) noexcept | |||||
: ReferenceCountedObject(), | |||||
typeface (other.typeface), | |||||
typefaceName (other.typefaceName), | |||||
typefaceStyle (other.typefaceStyle), | |||||
height (other.height), | |||||
horizontalScale (other.horizontalScale), | |||||
kerning (other.kerning), | |||||
ascent (other.ascent), | |||||
underline (other.underline) | |||||
{ | |||||
} | |||||
bool operator== (const SharedFontInternal& other) const noexcept | |||||
{ | |||||
return height == other.height | |||||
&& underline == other.underline | |||||
&& horizontalScale == other.horizontalScale | |||||
&& kerning == other.kerning | |||||
&& typefaceName == other.typefaceName | |||||
&& typefaceStyle == other.typefaceStyle; | |||||
} | |||||
Typeface::Ptr typeface; | |||||
String typefaceName, typefaceStyle; | |||||
float height, horizontalScale = 1.0f, kerning = 0, ascent = 0; | |||||
bool underline = false; | |||||
}; | |||||
//============================================================================== | |||||
Font::Font() : font (new SharedFontInternal()) {} | |||||
Font::Font (const Typeface::Ptr& typeface) : font (new SharedFontInternal (typeface)) {} | |||||
Font::Font (const Font& other) noexcept : font (other.font) {} | |||||
Font::Font (float fontHeight, int styleFlags) | |||||
: font (new SharedFontInternal (styleFlags, FontValues::limitFontHeight (fontHeight))) | |||||
{ | |||||
} | |||||
Font::Font (const String& typefaceName, float fontHeight, int styleFlags) | |||||
: font (new SharedFontInternal (typefaceName, styleFlags, FontValues::limitFontHeight (fontHeight))) | |||||
{ | |||||
} | |||||
Font::Font (const String& typefaceName, const String& typefaceStyle, float fontHeight) | |||||
: font (new SharedFontInternal (typefaceName, typefaceStyle, FontValues::limitFontHeight (fontHeight))) | |||||
{ | |||||
} | |||||
Font& Font::operator= (const Font& other) noexcept | |||||
{ | |||||
font = other.font; | |||||
return *this; | |||||
} | |||||
Font::Font (Font&& other) noexcept | |||||
: font (static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font)) | |||||
{ | |||||
} | |||||
Font& Font::operator= (Font&& other) noexcept | |||||
{ | |||||
font = static_cast<ReferenceCountedObjectPtr<SharedFontInternal>&&> (other.font); | |||||
return *this; | |||||
} | |||||
Font::~Font() noexcept | |||||
{ | |||||
} | |||||
bool Font::operator== (const Font& other) const noexcept | |||||
{ | |||||
return font == other.font | |||||
|| *font == *other.font; | |||||
} | |||||
bool Font::operator!= (const Font& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
void Font::dupeInternalIfShared() | |||||
{ | |||||
if (font->getReferenceCount() > 1) | |||||
font = new SharedFontInternal (*font); | |||||
} | |||||
void Font::checkTypefaceSuitability() | |||||
{ | |||||
if (font->typeface != nullptr && ! font->typeface->isSuitableForFont (*this)) | |||||
font->typeface = nullptr; | |||||
} | |||||
//============================================================================== | |||||
struct FontPlaceholderNames | |||||
{ | |||||
String sans { "<Sans-Serif>" }, | |||||
serif { "<Serif>" }, | |||||
mono { "<Monospaced>" }, | |||||
regular { "<Regular>" }; | |||||
}; | |||||
const FontPlaceholderNames& getFontPlaceholderNames() | |||||
{ | |||||
static FontPlaceholderNames names; | |||||
return names; | |||||
} | |||||
#if JUCE_MSVC | |||||
// This is a workaround for the lack of thread-safety in MSVC's handling of function-local | |||||
// statics - if multiple threads all try to create the first Font object at the same time, | |||||
// it can cause a race-condition in creating these placeholder strings. | |||||
struct FontNamePreloader { FontNamePreloader() { getFontPlaceholderNames(); } }; | |||||
static FontNamePreloader fnp; | |||||
#endif | |||||
const String& Font::getDefaultSansSerifFontName() { return getFontPlaceholderNames().sans; } | |||||
const String& Font::getDefaultSerifFontName() { return getFontPlaceholderNames().serif; } | |||||
const String& Font::getDefaultMonospacedFontName() { return getFontPlaceholderNames().mono; } | |||||
const String& Font::getDefaultStyle() { return getFontPlaceholderNames().regular; } | |||||
const String& Font::getTypefaceName() const noexcept { return font->typefaceName; } | |||||
const String& Font::getTypefaceStyle() const noexcept { return font->typefaceStyle; } | |||||
void Font::setTypefaceName (const String& faceName) | |||||
{ | |||||
if (faceName != font->typefaceName) | |||||
{ | |||||
jassert (faceName.isNotEmpty()); | |||||
dupeInternalIfShared(); | |||||
font->typefaceName = faceName; | |||||
font->typeface = nullptr; | |||||
font->ascent = 0; | |||||
} | |||||
} | |||||
void Font::setTypefaceStyle (const String& typefaceStyle) | |||||
{ | |||||
if (typefaceStyle != font->typefaceStyle) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->typefaceStyle = typefaceStyle; | |||||
font->typeface = nullptr; | |||||
font->ascent = 0; | |||||
} | |||||
} | |||||
Font Font::withTypefaceStyle (const String& newStyle) const | |||||
{ | |||||
Font f (*this); | |||||
f.setTypefaceStyle (newStyle); | |||||
return f; | |||||
} | |||||
StringArray Font::getAvailableStyles() const | |||||
{ | |||||
return findAllTypefaceStyles (getTypeface()->getName()); | |||||
} | |||||
Typeface* Font::getTypeface() const | |||||
{ | |||||
if (font->typeface == nullptr) | |||||
{ | |||||
font->typeface = TypefaceCache::getInstance()->findTypefaceFor (*this); | |||||
jassert (font->typeface != nullptr); | |||||
} | |||||
return font->typeface; | |||||
} | |||||
//============================================================================== | |||||
const String& Font::getFallbackFontName() | |||||
{ | |||||
return FontValues::fallbackFont; | |||||
} | |||||
void Font::setFallbackFontName (const String& name) | |||||
{ | |||||
FontValues::fallbackFont = name; | |||||
#if JUCE_MAC || JUCE_IOS | |||||
jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. | |||||
#endif | |||||
} | |||||
const String& Font::getFallbackFontStyle() | |||||
{ | |||||
return FontValues::fallbackFontStyle; | |||||
} | |||||
void Font::setFallbackFontStyle (const String& style) | |||||
{ | |||||
FontValues::fallbackFontStyle = style; | |||||
#if JUCE_MAC || JUCE_IOS | |||||
jassertfalse; // Note that use of a fallback font isn't currently implemented in OSX.. | |||||
#endif | |||||
} | |||||
//============================================================================== | |||||
Font Font::withHeight (const float newHeight) const | |||||
{ | |||||
Font f (*this); | |||||
f.setHeight (newHeight); | |||||
return f; | |||||
} | |||||
float Font::getHeightToPointsFactor() const | |||||
{ | |||||
return getTypeface()->getHeightToPointsFactor(); | |||||
} | |||||
Font Font::withPointHeight (float heightInPoints) const | |||||
{ | |||||
Font f (*this); | |||||
f.setHeight (heightInPoints / getHeightToPointsFactor()); | |||||
return f; | |||||
} | |||||
void Font::setHeight (float newHeight) | |||||
{ | |||||
newHeight = FontValues::limitFontHeight (newHeight); | |||||
if (font->height != newHeight) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->height = newHeight; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
} | |||||
void Font::setHeightWithoutChangingWidth (float newHeight) | |||||
{ | |||||
newHeight = FontValues::limitFontHeight (newHeight); | |||||
if (font->height != newHeight) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->horizontalScale *= (font->height / newHeight); | |||||
font->height = newHeight; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
} | |||||
int Font::getStyleFlags() const noexcept | |||||
{ | |||||
int styleFlags = font->underline ? underlined : plain; | |||||
if (isBold()) styleFlags |= bold; | |||||
if (isItalic()) styleFlags |= italic; | |||||
return styleFlags; | |||||
} | |||||
Font Font::withStyle (const int newFlags) const | |||||
{ | |||||
Font f (*this); | |||||
f.setStyleFlags (newFlags); | |||||
return f; | |||||
} | |||||
void Font::setStyleFlags (const int newFlags) | |||||
{ | |||||
if (getStyleFlags() != newFlags) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->typeface = nullptr; | |||||
font->typefaceStyle = FontStyleHelpers::getStyleName (newFlags); | |||||
font->underline = (newFlags & underlined) != 0; | |||||
font->ascent = 0; | |||||
} | |||||
} | |||||
void Font::setSizeAndStyle (float newHeight, | |||||
const int newStyleFlags, | |||||
const float newHorizontalScale, | |||||
const float newKerningAmount) | |||||
{ | |||||
newHeight = FontValues::limitFontHeight (newHeight); | |||||
if (font->height != newHeight | |||||
|| font->horizontalScale != newHorizontalScale | |||||
|| font->kerning != newKerningAmount) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->height = newHeight; | |||||
font->horizontalScale = newHorizontalScale; | |||||
font->kerning = newKerningAmount; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
setStyleFlags (newStyleFlags); | |||||
} | |||||
void Font::setSizeAndStyle (float newHeight, | |||||
const String& newStyle, | |||||
const float newHorizontalScale, | |||||
const float newKerningAmount) | |||||
{ | |||||
newHeight = FontValues::limitFontHeight (newHeight); | |||||
if (font->height != newHeight | |||||
|| font->horizontalScale != newHorizontalScale | |||||
|| font->kerning != newKerningAmount) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->height = newHeight; | |||||
font->horizontalScale = newHorizontalScale; | |||||
font->kerning = newKerningAmount; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
setTypefaceStyle (newStyle); | |||||
} | |||||
Font Font::withHorizontalScale (const float newHorizontalScale) const | |||||
{ | |||||
Font f (*this); | |||||
f.setHorizontalScale (newHorizontalScale); | |||||
return f; | |||||
} | |||||
void Font::setHorizontalScale (const float scaleFactor) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->horizontalScale = scaleFactor; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
float Font::getHorizontalScale() const noexcept | |||||
{ | |||||
return font->horizontalScale; | |||||
} | |||||
float Font::getExtraKerningFactor() const noexcept | |||||
{ | |||||
return font->kerning; | |||||
} | |||||
Font Font::withExtraKerningFactor (const float extraKerning) const | |||||
{ | |||||
Font f (*this); | |||||
f.setExtraKerningFactor (extraKerning); | |||||
return f; | |||||
} | |||||
void Font::setExtraKerningFactor (const float extraKerning) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->kerning = extraKerning; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
Font Font::boldened() const { return withStyle (getStyleFlags() | bold); } | |||||
Font Font::italicised() const { return withStyle (getStyleFlags() | italic); } | |||||
bool Font::isBold() const noexcept { return FontStyleHelpers::isBold (font->typefaceStyle); } | |||||
bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->typefaceStyle); } | |||||
bool Font::isUnderlined() const noexcept { return font->underline; } | |||||
void Font::setBold (const bool shouldBeBold) | |||||
{ | |||||
auto flags = getStyleFlags(); | |||||
setStyleFlags (shouldBeBold ? (flags | bold) | |||||
: (flags & ~bold)); | |||||
} | |||||
void Font::setItalic (const bool shouldBeItalic) | |||||
{ | |||||
auto flags = getStyleFlags(); | |||||
setStyleFlags (shouldBeItalic ? (flags | italic) | |||||
: (flags & ~italic)); | |||||
} | |||||
void Font::setUnderline (const bool shouldBeUnderlined) | |||||
{ | |||||
dupeInternalIfShared(); | |||||
font->underline = shouldBeUnderlined; | |||||
checkTypefaceSuitability(); | |||||
} | |||||
float Font::getAscent() const | |||||
{ | |||||
if (font->ascent == 0.0f) | |||||
font->ascent = getTypeface()->getAscent(); | |||||
return font->height * font->ascent; | |||||
} | |||||
float Font::getHeight() const noexcept { return font->height; } | |||||
float Font::getDescent() const { return font->height - getAscent(); } | |||||
float Font::getHeightInPoints() const { return getHeight() * getHeightToPointsFactor(); } | |||||
float Font::getAscentInPoints() const { return getAscent() * getHeightToPointsFactor(); } | |||||
float Font::getDescentInPoints() const { return getDescent() * getHeightToPointsFactor(); } | |||||
int Font::getStringWidth (const String& text) const | |||||
{ | |||||
return (int) std::ceil (getStringWidthFloat (text)); | |||||
} | |||||
float Font::getStringWidthFloat (const String& text) const | |||||
{ | |||||
// This call isn't thread-safe when there's a message thread running | |||||
jassert (MessageManager::getInstanceWithoutCreating() == nullptr | |||||
|| MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||||
auto w = getTypeface()->getStringWidth (text); | |||||
if (font->kerning != 0.0f) | |||||
w += font->kerning * text.length(); | |||||
return w * font->height * font->horizontalScale; | |||||
} | |||||
void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const | |||||
{ | |||||
// This call isn't thread-safe when there's a message thread running | |||||
jassert (MessageManager::getInstanceWithoutCreating() == nullptr | |||||
|| MessageManager::getInstanceWithoutCreating()->currentThreadHasLockedMessageManager()); | |||||
getTypeface()->getGlyphPositions (text, glyphs, xOffsets); | |||||
if (auto num = xOffsets.size()) | |||||
{ | |||||
auto scale = font->height * font->horizontalScale; | |||||
auto* x = xOffsets.getRawDataPointer(); | |||||
if (font->kerning != 0.0f) | |||||
{ | |||||
for (int i = 0; i < num; ++i) | |||||
x[i] = (x[i] + i * font->kerning) * scale; | |||||
} | |||||
else | |||||
{ | |||||
for (int i = 0; i < num; ++i) | |||||
x[i] *= scale; | |||||
} | |||||
} | |||||
} | |||||
void Font::findFonts (Array<Font>& destArray) | |||||
{ | |||||
for (auto& name : findAllTypefaceNames()) | |||||
{ | |||||
auto styles = findAllTypefaceStyles (name); | |||||
String style ("Regular"); | |||||
if (! styles.contains (style, true)) | |||||
style = styles[0]; | |||||
destArray.add (Font (name, style, FontValues::defaultFontHeight)); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
String Font::toString() const | |||||
{ | |||||
String s; | |||||
if (getTypefaceName() != getDefaultSansSerifFontName()) | |||||
s << getTypefaceName() << "; "; | |||||
s << String (getHeight(), 1); | |||||
if (getTypefaceStyle() != getDefaultStyle()) | |||||
s << ' ' << getTypefaceStyle(); | |||||
return s; | |||||
} | |||||
Font Font::fromString (const String& fontDescription) | |||||
{ | |||||
const int separator = fontDescription.indexOfChar (';'); | |||||
String name; | |||||
if (separator > 0) | |||||
name = fontDescription.substring (0, separator).trim(); | |||||
if (name.isEmpty()) | |||||
name = getDefaultSansSerifFontName(); | |||||
String sizeAndStyle (fontDescription.substring (separator + 1).trimStart()); | |||||
float height = sizeAndStyle.getFloatValue(); | |||||
if (height <= 0) | |||||
height = 10.0f; | |||||
const String style (sizeAndStyle.fromFirstOccurrenceOf (" ", false, false)); | |||||
return Font (name, style, height); | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,478 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a particular font, including its size, style, etc. | |||||
Apart from the typeface to be used, a Font object also dictates whether | |||||
the font is bold, italic, underlined, how big it is, and its kerning and | |||||
horizontal scale factor. | |||||
@see Typeface | |||||
*/ | |||||
class JUCE_API Font | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** A combination of these values is used by the constructor to specify the | |||||
style of font to use. | |||||
*/ | |||||
enum FontStyleFlags | |||||
{ | |||||
plain = 0, /**< indicates a plain, non-bold, non-italic version of the font. @see setStyleFlags */ | |||||
bold = 1, /**< boldens the font. @see setStyleFlags */ | |||||
italic = 2, /**< finds an italic version of the font. @see setStyleFlags */ | |||||
underlined = 4 /**< underlines the font. @see setStyleFlags */ | |||||
}; | |||||
//============================================================================== | |||||
/** Creates a sans-serif font in a given size. | |||||
@param fontHeight the height in pixels (can be fractional) | |||||
@param styleFlags the style to use - this can be a combination of the | |||||
Font::bold, Font::italic and Font::underlined, or | |||||
just Font::plain for the normal style. | |||||
@see FontStyleFlags, getDefaultSansSerifFontName | |||||
*/ | |||||
Font (float fontHeight, int styleFlags = plain); | |||||
/** Creates a font with a given typeface and parameters. | |||||
@param typefaceName the font family of the typeface to use | |||||
@param fontHeight the height in pixels (can be fractional) | |||||
@param styleFlags the style to use - this can be a combination of the | |||||
Font::bold, Font::italic and Font::underlined, or | |||||
just Font::plain for the normal style. | |||||
@see FontStyleFlags, getDefaultSansSerifFontName | |||||
*/ | |||||
Font (const String& typefaceName, float fontHeight, int styleFlags); | |||||
/** Creates a font with a given typeface and parameters. | |||||
@param typefaceName the font family of the typeface to use | |||||
@param typefaceStyle the font style of the typeface to use | |||||
@param fontHeight the height in pixels (can be fractional) | |||||
*/ | |||||
Font (const String& typefaceName, const String& typefaceStyle, float fontHeight); | |||||
/** Creates a copy of another Font object. */ | |||||
Font (const Font& other) noexcept; | |||||
/** Creates a font for a typeface. */ | |||||
Font (const Typeface::Ptr& typeface); | |||||
/** Creates a basic sans-serif font at a default height. | |||||
You should use one of the other constructors for creating a font that you're planning | |||||
on drawing with - this constructor is here to help initialise objects before changing | |||||
the font's settings later. | |||||
*/ | |||||
Font(); | |||||
/** Move constructor */ | |||||
Font (Font&& other) noexcept; | |||||
/** Move assignment operator */ | |||||
Font& operator= (Font&& other) noexcept; | |||||
/** Copies this font from another one. */ | |||||
Font& operator= (const Font& other) noexcept; | |||||
bool operator== (const Font& other) const noexcept; | |||||
bool operator!= (const Font& other) const noexcept; | |||||
/** Destructor. */ | |||||
~Font() noexcept; | |||||
//============================================================================== | |||||
/** Changes the font family of the typeface. | |||||
e.g. "Arial", "Courier", etc. | |||||
This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), | |||||
or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, | |||||
but are generic font family names that are used to represent the various default fonts. | |||||
If you need to know the exact typeface font family being used, you can call | |||||
Font::getTypeface()->getName(), which will give you the platform-specific font family. | |||||
If a suitable font isn't found on the machine, it'll just use a default instead. | |||||
*/ | |||||
void setTypefaceName (const String& faceName); | |||||
/** Returns the font family of the typeface that this font uses. | |||||
e.g. "Arial", "Courier", etc. | |||||
This may also be set to Font::getDefaultSansSerifFontName(), Font::getDefaultSerifFontName(), | |||||
or Font::getDefaultMonospacedFontName(), which are not actual platform-specific font family names, | |||||
but are generic font familiy names that are used to represent the various default fonts. | |||||
If you need to know the exact typeface font family being used, you can call | |||||
Font::getTypeface()->getName(), which will give you the platform-specific font family. | |||||
*/ | |||||
const String& getTypefaceName() const noexcept; | |||||
//============================================================================== | |||||
/** Returns the font style of the typeface that this font uses. | |||||
@see withTypefaceStyle, getAvailableStyles() | |||||
*/ | |||||
const String& getTypefaceStyle() const noexcept; | |||||
/** Changes the font style of the typeface. | |||||
@see getAvailableStyles() | |||||
*/ | |||||
void setTypefaceStyle (const String& newStyle); | |||||
/** Returns a copy of this font with a new typeface style. | |||||
@see getAvailableStyles() | |||||
*/ | |||||
Font withTypefaceStyle (const String& newStyle) const; | |||||
/** Returns a list of the styles that this font can use. */ | |||||
StringArray getAvailableStyles() const; | |||||
//============================================================================== | |||||
/** Returns a typeface font family that represents the default sans-serif font. | |||||
This is also the typeface that will be used when a font is created without | |||||
specifying any typeface details. | |||||
Note that this method just returns a generic placeholder string that means "the default | |||||
sans-serif font" - it's not the actual font family of this font. | |||||
@see setTypefaceName, getDefaultSerifFontName, getDefaultMonospacedFontName | |||||
*/ | |||||
static const String& getDefaultSansSerifFontName(); | |||||
/** Returns a typeface font family that represents the default serif font. | |||||
Note that this method just returns a generic placeholder string that means "the default | |||||
serif font" - it's not the actual font family of this font. | |||||
@see setTypefaceName, getDefaultSansSerifFontName, getDefaultMonospacedFontName | |||||
*/ | |||||
static const String& getDefaultSerifFontName(); | |||||
/** Returns a typeface font family that represents the default monospaced font. | |||||
Note that this method just returns a generic placeholder string that means "the default | |||||
monospaced font" - it's not the actual font family of this font. | |||||
@see setTypefaceName, getDefaultSansSerifFontName, getDefaultSerifFontName | |||||
*/ | |||||
static const String& getDefaultMonospacedFontName(); | |||||
/** Returns a font style name that represents the default style. | |||||
Note that this method just returns a generic placeholder string that means "the default | |||||
font style" - it's not the actual name of the font style of any particular font. | |||||
@see setTypefaceStyle | |||||
*/ | |||||
static const String& getDefaultStyle(); | |||||
/** Returns the default system typeface for the given font. */ | |||||
static Typeface::Ptr getDefaultTypefaceForFont (const Font& font); | |||||
//============================================================================== | |||||
/** Returns a copy of this font with a new height. */ | |||||
Font withHeight (float height) const; | |||||
/** Returns a copy of this font with a new height, specified in points. */ | |||||
Font withPointHeight (float heightInPoints) const; | |||||
/** Changes the font's height. | |||||
@see getHeight, withHeight, setHeightWithoutChangingWidth | |||||
*/ | |||||
void setHeight (float newHeight); | |||||
/** Changes the font's height without changing its width. | |||||
This alters the horizontal scale to compensate for the change in height. | |||||
*/ | |||||
void setHeightWithoutChangingWidth (float newHeight); | |||||
/** Returns the total height of this font, in pixels. | |||||
This is the maximum height, from the top of the ascent to the bottom of the | |||||
descenders. | |||||
@see withHeight, setHeightWithoutChangingWidth, getAscent | |||||
*/ | |||||
float getHeight() const noexcept; | |||||
/** Returns the total height of this font, in points. | |||||
This is the maximum height, from the top of the ascent to the bottom of the | |||||
descenders. | |||||
@see withPointHeight, getHeight | |||||
*/ | |||||
float getHeightInPoints() const; | |||||
/** Returns the height of the font above its baseline, in pixels. | |||||
This is the maximum height from the baseline to the top. | |||||
@see getHeight, getDescent | |||||
*/ | |||||
float getAscent() const; | |||||
/** Returns the height of the font above its baseline, in points. | |||||
This is the maximum height from the baseline to the top. | |||||
@see getHeight, getDescent | |||||
*/ | |||||
float getAscentInPoints() const; | |||||
/** Returns the amount that the font descends below its baseline, in pixels. | |||||
This is calculated as (getHeight() - getAscent()). | |||||
@see getAscent, getHeight | |||||
*/ | |||||
float getDescent() const; | |||||
/** Returns the amount that the font descends below its baseline, in points. | |||||
This is calculated as (getHeight() - getAscent()). | |||||
@see getAscent, getHeight | |||||
*/ | |||||
float getDescentInPoints() const; | |||||
//============================================================================== | |||||
/** Returns the font's style flags. | |||||
This will return a bitwise-or'ed combination of values from the FontStyleFlags | |||||
enum, to describe whether the font is bold, italic, etc. | |||||
@see FontStyleFlags, withStyle | |||||
*/ | |||||
int getStyleFlags() const noexcept; | |||||
/** Returns a copy of this font with the given set of style flags. | |||||
@param styleFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. | |||||
@see FontStyleFlags, getStyleFlags | |||||
*/ | |||||
Font withStyle (int styleFlags) const; | |||||
/** Changes the font's style. | |||||
@param newFlags a bitwise-or'ed combination of values from the FontStyleFlags enum. | |||||
@see FontStyleFlags, withStyle | |||||
*/ | |||||
void setStyleFlags (int newFlags); | |||||
//============================================================================== | |||||
/** Makes the font bold or non-bold. */ | |||||
void setBold (bool shouldBeBold); | |||||
/** Returns a copy of this font with the bold attribute set. | |||||
If the font does not have a bold version, this will return the default font. | |||||
*/ | |||||
Font boldened() const; | |||||
/** Returns true if the font is bold. */ | |||||
bool isBold() const noexcept; | |||||
/** Makes the font italic or non-italic. */ | |||||
void setItalic (bool shouldBeItalic); | |||||
/** Returns a copy of this font with the italic attribute set. */ | |||||
Font italicised() const; | |||||
/** Returns true if the font is italic. */ | |||||
bool isItalic() const noexcept; | |||||
/** Makes the font underlined or non-underlined. */ | |||||
void setUnderline (bool shouldBeUnderlined); | |||||
/** Returns true if the font is underlined. */ | |||||
bool isUnderlined() const noexcept; | |||||
//============================================================================== | |||||
/** Returns the font's horizontal scale. | |||||
A value of 1.0 is the normal scale, less than this will be narrower, greater | |||||
than 1.0 will be stretched out. | |||||
@see withHorizontalScale | |||||
*/ | |||||
float getHorizontalScale() const noexcept; | |||||
/** Returns a copy of this font with a new horizontal scale. | |||||
@param scaleFactor a value of 1.0 is the normal scale, less than this will be | |||||
narrower, greater than 1.0 will be stretched out. | |||||
@see getHorizontalScale | |||||
*/ | |||||
Font withHorizontalScale (float scaleFactor) const; | |||||
/** Changes the font's horizontal scale factor. | |||||
@param scaleFactor a value of 1.0 is the normal scale, less than this will be | |||||
narrower, greater than 1.0 will be stretched out. | |||||
*/ | |||||
void setHorizontalScale (float scaleFactor); | |||||
/** Returns the minimum horizontal scale to which fonts may be squashed when trying to | |||||
create a layout. | |||||
@see setDefaultMinimumHorizontalScaleFactor | |||||
*/ | |||||
static float getDefaultMinimumHorizontalScaleFactor() noexcept; | |||||
/** Sets the minimum horizontal scale to which fonts may be squashed when trying to | |||||
create a text layout. | |||||
@see getDefaultMinimumHorizontalScaleFactor | |||||
*/ | |||||
static void setDefaultMinimumHorizontalScaleFactor (float newMinimumScaleFactor) noexcept; | |||||
/** Returns the font's kerning. | |||||
This is the extra space added between adjacent characters, as a proportion | |||||
of the font's height. | |||||
A value of zero is normal spacing, positive values will spread the letters | |||||
out more, and negative values make them closer together. | |||||
*/ | |||||
float getExtraKerningFactor() const noexcept; | |||||
/** Returns a copy of this font with a new kerning factor. | |||||
@param extraKerning a multiple of the font's height that will be added | |||||
to space between the characters. So a value of zero is | |||||
normal spacing, positive values spread the letters out, | |||||
negative values make them closer together. | |||||
*/ | |||||
Font withExtraKerningFactor (float extraKerning) const; | |||||
/** Changes the font's kerning. | |||||
@param extraKerning a multiple of the font's height that will be added | |||||
to space between the characters. So a value of zero is | |||||
normal spacing, positive values spread the letters out, | |||||
negative values make them closer together. | |||||
*/ | |||||
void setExtraKerningFactor (float extraKerning); | |||||
//============================================================================== | |||||
/** Changes all the font's characteristics with one call. */ | |||||
void setSizeAndStyle (float newHeight, | |||||
int newStyleFlags, | |||||
float newHorizontalScale, | |||||
float newKerningAmount); | |||||
/** Changes all the font's characteristics with one call. */ | |||||
void setSizeAndStyle (float newHeight, | |||||
const String& newStyle, | |||||
float newHorizontalScale, | |||||
float newKerningAmount); | |||||
//============================================================================== | |||||
/** Returns the total width of a string as it would be drawn using this font. | |||||
For a more accurate floating-point result, use getStringWidthFloat(). | |||||
*/ | |||||
int getStringWidth (const String& text) const; | |||||
/** Returns the total width of a string as it would be drawn using this font. | |||||
@see getStringWidth | |||||
*/ | |||||
float getStringWidthFloat (const String& text) const; | |||||
/** Returns the series of glyph numbers and their x offsets needed to represent a string. | |||||
An extra x offset is added at the end of the run, to indicate where the right hand | |||||
edge of the last character is. | |||||
*/ | |||||
void getGlyphPositions (const String& text, Array <int>& glyphs, Array <float>& xOffsets) const; | |||||
//============================================================================== | |||||
/** Returns the typeface used by this font. | |||||
Note that the object returned may go out of scope if this font is deleted | |||||
or has its style changed. | |||||
*/ | |||||
Typeface* getTypeface() const; | |||||
/** Creates an array of Font objects to represent all the fonts on the system. | |||||
If you just need the font family names of the typefaces, you can also use | |||||
findAllTypefaceNames() instead. | |||||
@param results the array to which new Font objects will be added. | |||||
*/ | |||||
static void findFonts (Array<Font>& results); | |||||
/** Returns a list of all the available typeface font families. | |||||
The names returned can be passed into setTypefaceName(). | |||||
You can use this instead of findFonts() if you only need their font family names, | |||||
and not font objects. | |||||
*/ | |||||
static StringArray findAllTypefaceNames(); | |||||
/** Returns a list of all the available typeface font styles. | |||||
The names returned can be passed into setTypefaceStyle(). | |||||
You can use this instead of findFonts() if you only need their styles, and not | |||||
font objects. | |||||
*/ | |||||
static StringArray findAllTypefaceStyles (const String& family); | |||||
//============================================================================== | |||||
/** Returns the font family of the typeface to be used for rendering glyphs that aren't | |||||
found in the requested typeface. | |||||
*/ | |||||
static const String& getFallbackFontName(); | |||||
/** Sets the (platform-specific) font family of the typeface to use to find glyphs that | |||||
aren't available in whatever font you're trying to use. | |||||
*/ | |||||
static void setFallbackFontName (const String& name); | |||||
/** Returns the font style of the typeface to be used for rendering glyphs that aren't | |||||
found in the requested typeface. | |||||
*/ | |||||
static const String& getFallbackFontStyle(); | |||||
/** Sets the (platform-specific) font style of the typeface to use to find glyphs that | |||||
aren't available in whatever font you're trying to use. | |||||
*/ | |||||
static void setFallbackFontStyle (const String& style); | |||||
//============================================================================== | |||||
/** Creates a string to describe this font. | |||||
The string will contain information to describe the font's typeface, size, and | |||||
style. To recreate the font from this string, use fromString(). | |||||
*/ | |||||
String toString() const; | |||||
/** Recreates a font from its stringified encoding. | |||||
This method takes a string that was created by toString(), and recreates the | |||||
original font. | |||||
*/ | |||||
static Font fromString (const String& fontDescription); | |||||
private: | |||||
//============================================================================== | |||||
class SharedFontInternal; | |||||
ReferenceCountedObjectPtr<SharedFontInternal> font; | |||||
void dupeInternalIfShared(); | |||||
void checkTypefaceSuitability(); | |||||
float getHeightToPointsFactor() const; | |||||
JUCE_LEAK_DETECTOR (Font) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,820 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
PositionedGlyph::PositionedGlyph() noexcept | |||||
: character (0), glyph (0), x (0), y (0), w (0), whitespace (false) | |||||
{ | |||||
} | |||||
PositionedGlyph::PositionedGlyph (const Font& font_, const juce_wchar character_, const int glyph_, | |||||
const float x_, const float y_, const float w_, const bool whitespace_) | |||||
: font (font_), character (character_), glyph (glyph_), | |||||
x (x_), y (y_), w (w_), whitespace (whitespace_) | |||||
{ | |||||
} | |||||
PositionedGlyph::PositionedGlyph (const PositionedGlyph& other) | |||||
: font (other.font), character (other.character), glyph (other.glyph), | |||||
x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) | |||||
{ | |||||
} | |||||
PositionedGlyph::PositionedGlyph (PositionedGlyph&& other) noexcept | |||||
: font (static_cast<Font&&> (other.font)), | |||||
character (other.character), glyph (other.glyph), | |||||
x (other.x), y (other.y), w (other.w), whitespace (other.whitespace) | |||||
{ | |||||
} | |||||
PositionedGlyph& PositionedGlyph::operator= (PositionedGlyph&& other) noexcept | |||||
{ | |||||
font = static_cast<Font&&> (other.font); | |||||
character = other.character; | |||||
glyph = other.glyph; | |||||
x = other.x; | |||||
y = other.y; | |||||
w = other.w; | |||||
whitespace = other.whitespace; | |||||
return *this; | |||||
} | |||||
PositionedGlyph::~PositionedGlyph() {} | |||||
PositionedGlyph& PositionedGlyph::operator= (const PositionedGlyph& other) | |||||
{ | |||||
font = other.font; | |||||
character = other.character; | |||||
glyph = other.glyph; | |||||
x = other.x; | |||||
y = other.y; | |||||
w = other.w; | |||||
whitespace = other.whitespace; | |||||
return *this; | |||||
} | |||||
static inline void drawGlyphWithFont (Graphics& g, int glyph, const Font& font, const AffineTransform& t) | |||||
{ | |||||
LowLevelGraphicsContext& context = g.getInternalContext(); | |||||
context.setFont (font); | |||||
context.drawGlyph (glyph, t); | |||||
} | |||||
void PositionedGlyph::draw (Graphics& g) const | |||||
{ | |||||
if (! isWhitespace()) | |||||
drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y)); | |||||
} | |||||
void PositionedGlyph::draw (Graphics& g, const AffineTransform& transform) const | |||||
{ | |||||
if (! isWhitespace()) | |||||
drawGlyphWithFont (g, glyph, font, AffineTransform::translation (x, y).followedBy (transform)); | |||||
} | |||||
void PositionedGlyph::createPath (Path& path) const | |||||
{ | |||||
if (! isWhitespace()) | |||||
{ | |||||
if (auto* t = font.getTypeface()) | |||||
{ | |||||
Path p; | |||||
t->getOutlineForGlyph (glyph, p); | |||||
path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()) | |||||
.translated (x, y)); | |||||
} | |||||
} | |||||
} | |||||
bool PositionedGlyph::hitTest (float px, float py) const | |||||
{ | |||||
if (getBounds().contains (px, py) && ! isWhitespace()) | |||||
{ | |||||
if (auto* t = font.getTypeface()) | |||||
{ | |||||
Path p; | |||||
t->getOutlineForGlyph (glyph, p); | |||||
AffineTransform::translation (-x, -y) | |||||
.scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight()) | |||||
.transformPoint (px, py); | |||||
return p.contains (px, py); | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void PositionedGlyph::moveBy (const float deltaX, | |||||
const float deltaY) | |||||
{ | |||||
x += deltaX; | |||||
y += deltaY; | |||||
} | |||||
//============================================================================== | |||||
GlyphArrangement::GlyphArrangement() | |||||
{ | |||||
glyphs.ensureStorageAllocated (128); | |||||
} | |||||
GlyphArrangement::GlyphArrangement (const GlyphArrangement& other) | |||||
: glyphs (other.glyphs) | |||||
{ | |||||
} | |||||
GlyphArrangement& GlyphArrangement::operator= (const GlyphArrangement& other) | |||||
{ | |||||
glyphs = other.glyphs; | |||||
return *this; | |||||
} | |||||
GlyphArrangement::~GlyphArrangement() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void GlyphArrangement::clear() | |||||
{ | |||||
glyphs.clear(); | |||||
} | |||||
PositionedGlyph& GlyphArrangement::getGlyph (const int index) const noexcept | |||||
{ | |||||
return glyphs.getReference (index); | |||||
} | |||||
//============================================================================== | |||||
void GlyphArrangement::addGlyphArrangement (const GlyphArrangement& other) | |||||
{ | |||||
glyphs.addArray (other.glyphs); | |||||
} | |||||
void GlyphArrangement::addGlyph (const PositionedGlyph& glyph) | |||||
{ | |||||
glyphs.add (glyph); | |||||
} | |||||
void GlyphArrangement::removeRangeOfGlyphs (int startIndex, const int num) | |||||
{ | |||||
glyphs.removeRange (startIndex, num < 0 ? glyphs.size() : num); | |||||
} | |||||
//============================================================================== | |||||
void GlyphArrangement::addLineOfText (const Font& font, | |||||
const String& text, | |||||
const float xOffset, | |||||
const float yOffset) | |||||
{ | |||||
addCurtailedLineOfText (font, text, xOffset, yOffset, 1.0e10f, false); | |||||
} | |||||
void GlyphArrangement::addCurtailedLineOfText (const Font& font, | |||||
const String& text, | |||||
const float xOffset, | |||||
const float yOffset, | |||||
const float maxWidthPixels, | |||||
const bool useEllipsis) | |||||
{ | |||||
if (text.isNotEmpty()) | |||||
{ | |||||
Array<int> newGlyphs; | |||||
Array<float> xOffsets; | |||||
font.getGlyphPositions (text, newGlyphs, xOffsets); | |||||
const int textLen = newGlyphs.size(); | |||||
glyphs.ensureStorageAllocated (glyphs.size() + textLen); | |||||
auto t = text.getCharPointer(); | |||||
for (int i = 0; i < textLen; ++i) | |||||
{ | |||||
const float nextX = xOffsets.getUnchecked (i + 1); | |||||
if (nextX > maxWidthPixels + 1.0f) | |||||
{ | |||||
// curtail the string if it's too wide.. | |||||
if (useEllipsis && textLen > 3 && glyphs.size() >= 3) | |||||
insertEllipsis (font, xOffset + maxWidthPixels, 0, glyphs.size()); | |||||
break; | |||||
} | |||||
const float thisX = xOffsets.getUnchecked (i); | |||||
const bool isWhitespace = t.isWhitespace(); | |||||
glyphs.add (PositionedGlyph (font, t.getAndAdvance(), | |||||
newGlyphs.getUnchecked(i), | |||||
xOffset + thisX, yOffset, | |||||
nextX - thisX, isWhitespace)); | |||||
} | |||||
} | |||||
} | |||||
int GlyphArrangement::insertEllipsis (const Font& font, const float maxXPos, | |||||
const int startIndex, int endIndex) | |||||
{ | |||||
int numDeleted = 0; | |||||
if (glyphs.size() > 0) | |||||
{ | |||||
Array<int> dotGlyphs; | |||||
Array<float> dotXs; | |||||
font.getGlyphPositions ("..", dotGlyphs, dotXs); | |||||
const float dx = dotXs[1]; | |||||
float xOffset = 0.0f, yOffset = 0.0f; | |||||
while (endIndex > startIndex) | |||||
{ | |||||
auto& pg = glyphs.getReference (--endIndex); | |||||
xOffset = pg.x; | |||||
yOffset = pg.y; | |||||
glyphs.remove (endIndex); | |||||
++numDeleted; | |||||
if (xOffset + dx * 3 <= maxXPos) | |||||
break; | |||||
} | |||||
for (int i = 3; --i >= 0;) | |||||
{ | |||||
glyphs.insert (endIndex++, PositionedGlyph (font, '.', dotGlyphs.getFirst(), | |||||
xOffset, yOffset, dx, false)); | |||||
--numDeleted; | |||||
xOffset += dx; | |||||
if (xOffset > maxXPos) | |||||
break; | |||||
} | |||||
} | |||||
return numDeleted; | |||||
} | |||||
void GlyphArrangement::addJustifiedText (const Font& font, | |||||
const String& text, | |||||
float x, float y, | |||||
const float maxLineWidth, | |||||
Justification horizontalLayout) | |||||
{ | |||||
int lineStartIndex = glyphs.size(); | |||||
addLineOfText (font, text, x, y); | |||||
const float originalY = y; | |||||
while (lineStartIndex < glyphs.size()) | |||||
{ | |||||
int i = lineStartIndex; | |||||
if (glyphs.getReference(i).getCharacter() != '\n' | |||||
&& glyphs.getReference(i).getCharacter() != '\r') | |||||
++i; | |||||
const float lineMaxX = glyphs.getReference (lineStartIndex).getLeft() + maxLineWidth; | |||||
int lastWordBreakIndex = -1; | |||||
while (i < glyphs.size()) | |||||
{ | |||||
auto& pg = glyphs.getReference (i); | |||||
const juce_wchar c = pg.getCharacter(); | |||||
if (c == '\r' || c == '\n') | |||||
{ | |||||
++i; | |||||
if (c == '\r' && i < glyphs.size() | |||||
&& glyphs.getReference(i).getCharacter() == '\n') | |||||
++i; | |||||
break; | |||||
} | |||||
if (pg.isWhitespace()) | |||||
{ | |||||
lastWordBreakIndex = i + 1; | |||||
} | |||||
else if (pg.getRight() - 0.0001f >= lineMaxX) | |||||
{ | |||||
if (lastWordBreakIndex >= 0) | |||||
i = lastWordBreakIndex; | |||||
break; | |||||
} | |||||
++i; | |||||
} | |||||
const float currentLineStartX = glyphs.getReference (lineStartIndex).getLeft(); | |||||
float currentLineEndX = currentLineStartX; | |||||
for (int j = i; --j >= lineStartIndex;) | |||||
{ | |||||
if (! glyphs.getReference (j).isWhitespace()) | |||||
{ | |||||
currentLineEndX = glyphs.getReference (j).getRight(); | |||||
break; | |||||
} | |||||
} | |||||
float deltaX = 0.0f; | |||||
if (horizontalLayout.testFlags (Justification::horizontallyJustified)) | |||||
spreadOutLine (lineStartIndex, i - lineStartIndex, maxLineWidth); | |||||
else if (horizontalLayout.testFlags (Justification::horizontallyCentred)) | |||||
deltaX = (maxLineWidth - (currentLineEndX - currentLineStartX)) * 0.5f; | |||||
else if (horizontalLayout.testFlags (Justification::right)) | |||||
deltaX = maxLineWidth - (currentLineEndX - currentLineStartX); | |||||
moveRangeOfGlyphs (lineStartIndex, i - lineStartIndex, | |||||
x + deltaX - currentLineStartX, y - originalY); | |||||
lineStartIndex = i; | |||||
y += font.getHeight(); | |||||
} | |||||
} | |||||
void GlyphArrangement::addFittedText (const Font& f, | |||||
const String& text, | |||||
const float x, const float y, | |||||
const float width, const float height, | |||||
Justification layout, | |||||
int maximumLines, | |||||
float minimumHorizontalScale) | |||||
{ | |||||
if (minimumHorizontalScale == 0.0f) | |||||
minimumHorizontalScale = Font::getDefaultMinimumHorizontalScaleFactor(); | |||||
// doesn't make much sense if this is outside a sensible range of 0.5 to 1.0 | |||||
jassert (minimumHorizontalScale > 0 && minimumHorizontalScale <= 1.0f); | |||||
if (text.containsAnyOf ("\r\n")) | |||||
{ | |||||
addLinesWithLineBreaks (text, f, x, y, width, height, layout); | |||||
} | |||||
else | |||||
{ | |||||
const int startIndex = glyphs.size(); | |||||
const String trimmed (text.trim()); | |||||
addLineOfText (f, trimmed, x, y); | |||||
const int numGlyphs = glyphs.size() - startIndex; | |||||
if (numGlyphs > 0) | |||||
{ | |||||
const float lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() | |||||
- glyphs.getReference (startIndex).getLeft(); | |||||
if (lineWidth > 0) | |||||
{ | |||||
if (lineWidth * minimumHorizontalScale < width) | |||||
{ | |||||
if (lineWidth > width) | |||||
stretchRangeOfGlyphs (startIndex, numGlyphs, width / lineWidth); | |||||
justifyGlyphs (startIndex, numGlyphs, x, y, width, height, layout); | |||||
} | |||||
else if (maximumLines <= 1) | |||||
{ | |||||
fitLineIntoSpace (startIndex, numGlyphs, x, y, width, height, | |||||
f, layout, minimumHorizontalScale); | |||||
} | |||||
else | |||||
{ | |||||
splitLines (trimmed, f, startIndex, x, y, width, height, | |||||
maximumLines, lineWidth, layout, minimumHorizontalScale); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void GlyphArrangement::moveRangeOfGlyphs (int startIndex, int num, const float dx, const float dy) | |||||
{ | |||||
jassert (startIndex >= 0); | |||||
if (dx != 0.0f || dy != 0.0f) | |||||
{ | |||||
if (num < 0 || startIndex + num > glyphs.size()) | |||||
num = glyphs.size() - startIndex; | |||||
while (--num >= 0) | |||||
glyphs.getReference (startIndex++).moveBy (dx, dy); | |||||
} | |||||
} | |||||
void GlyphArrangement::addLinesWithLineBreaks (const String& text, const Font& f, | |||||
float x, float y, float width, float height, Justification layout) | |||||
{ | |||||
GlyphArrangement ga; | |||||
ga.addJustifiedText (f, text, x, y, width, layout); | |||||
auto bb = ga.getBoundingBox (0, -1, false); | |||||
float dy = y - bb.getY(); | |||||
if (layout.testFlags (Justification::verticallyCentred)) dy += (height - bb.getHeight()) * 0.5f; | |||||
else if (layout.testFlags (Justification::bottom)) dy += (height - bb.getHeight()); | |||||
ga.moveRangeOfGlyphs (0, -1, 0.0f, dy); | |||||
glyphs.addArray (ga.glyphs); | |||||
} | |||||
int GlyphArrangement::fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font& font, | |||||
Justification justification, float minimumHorizontalScale) | |||||
{ | |||||
int numDeleted = 0; | |||||
const float lineStartX = glyphs.getReference (start).getLeft(); | |||||
float lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX; | |||||
if (lineWidth > w) | |||||
{ | |||||
if (minimumHorizontalScale < 1.0f) | |||||
{ | |||||
stretchRangeOfGlyphs (start, numGlyphs, jmax (minimumHorizontalScale, w / lineWidth)); | |||||
lineWidth = glyphs.getReference (start + numGlyphs - 1).getRight() - lineStartX - 0.5f; | |||||
} | |||||
if (lineWidth > w) | |||||
{ | |||||
numDeleted = insertEllipsis (font, lineStartX + w, start, start + numGlyphs); | |||||
numGlyphs -= numDeleted; | |||||
} | |||||
} | |||||
justifyGlyphs (start, numGlyphs, x, y, w, h, justification); | |||||
return numDeleted; | |||||
} | |||||
void GlyphArrangement::stretchRangeOfGlyphs (int startIndex, int num, | |||||
const float horizontalScaleFactor) | |||||
{ | |||||
jassert (startIndex >= 0); | |||||
if (num < 0 || startIndex + num > glyphs.size()) | |||||
num = glyphs.size() - startIndex; | |||||
if (num > 0) | |||||
{ | |||||
const float xAnchor = glyphs.getReference (startIndex).getLeft(); | |||||
while (--num >= 0) | |||||
{ | |||||
auto& pg = glyphs.getReference (startIndex++); | |||||
pg.x = xAnchor + (pg.x - xAnchor) * horizontalScaleFactor; | |||||
pg.font.setHorizontalScale (pg.font.getHorizontalScale() * horizontalScaleFactor); | |||||
pg.w *= horizontalScaleFactor; | |||||
} | |||||
} | |||||
} | |||||
Rectangle<float> GlyphArrangement::getBoundingBox (int startIndex, int num, const bool includeWhitespace) const | |||||
{ | |||||
jassert (startIndex >= 0); | |||||
if (num < 0 || startIndex + num > glyphs.size()) | |||||
num = glyphs.size() - startIndex; | |||||
Rectangle<float> result; | |||||
while (--num >= 0) | |||||
{ | |||||
auto& pg = glyphs.getReference (startIndex++); | |||||
if (includeWhitespace || ! pg.isWhitespace()) | |||||
result = result.getUnion (pg.getBounds()); | |||||
} | |||||
return result; | |||||
} | |||||
void GlyphArrangement::justifyGlyphs (const int startIndex, const int num, | |||||
const float x, const float y, const float width, const float height, | |||||
Justification justification) | |||||
{ | |||||
jassert (num >= 0 && startIndex >= 0); | |||||
if (glyphs.size() > 0 && num > 0) | |||||
{ | |||||
auto bb = getBoundingBox (startIndex, num, ! justification.testFlags (Justification::horizontallyJustified | |||||
| Justification::horizontallyCentred)); | |||||
float deltaX = 0.0f, deltaY = 0.0f; | |||||
if (justification.testFlags (Justification::horizontallyJustified)) deltaX = x - bb.getX(); | |||||
else if (justification.testFlags (Justification::horizontallyCentred)) deltaX = x + (width - bb.getWidth()) * 0.5f - bb.getX(); | |||||
else if (justification.testFlags (Justification::right)) deltaX = x + width - bb.getRight(); | |||||
else deltaX = x - bb.getX(); | |||||
if (justification.testFlags (Justification::top)) deltaY = y - bb.getY(); | |||||
else if (justification.testFlags (Justification::bottom)) deltaY = y + height - bb.getBottom(); | |||||
else deltaY = y + (height - bb.getHeight()) * 0.5f - bb.getY(); | |||||
moveRangeOfGlyphs (startIndex, num, deltaX, deltaY); | |||||
if (justification.testFlags (Justification::horizontallyJustified)) | |||||
{ | |||||
int lineStart = 0; | |||||
float baseY = glyphs.getReference (startIndex).getBaselineY(); | |||||
int i; | |||||
for (i = 0; i < num; ++i) | |||||
{ | |||||
const float glyphY = glyphs.getReference (startIndex + i).getBaselineY(); | |||||
if (glyphY != baseY) | |||||
{ | |||||
spreadOutLine (startIndex + lineStart, i - lineStart, width); | |||||
lineStart = i; | |||||
baseY = glyphY; | |||||
} | |||||
} | |||||
if (i > lineStart) | |||||
spreadOutLine (startIndex + lineStart, i - lineStart, width); | |||||
} | |||||
} | |||||
} | |||||
void GlyphArrangement::spreadOutLine (const int start, const int num, const float targetWidth) | |||||
{ | |||||
if (start + num < glyphs.size() | |||||
&& glyphs.getReference (start + num - 1).getCharacter() != '\r' | |||||
&& glyphs.getReference (start + num - 1).getCharacter() != '\n') | |||||
{ | |||||
int numSpaces = 0; | |||||
int spacesAtEnd = 0; | |||||
for (int i = 0; i < num; ++i) | |||||
{ | |||||
if (glyphs.getReference (start + i).isWhitespace()) | |||||
{ | |||||
++spacesAtEnd; | |||||
++numSpaces; | |||||
} | |||||
else | |||||
{ | |||||
spacesAtEnd = 0; | |||||
} | |||||
} | |||||
numSpaces -= spacesAtEnd; | |||||
if (numSpaces > 0) | |||||
{ | |||||
const float startX = glyphs.getReference (start).getLeft(); | |||||
const float endX = glyphs.getReference (start + num - 1 - spacesAtEnd).getRight(); | |||||
const float extraPaddingBetweenWords = (targetWidth - (endX - startX)) / (float) numSpaces; | |||||
float deltaX = 0.0f; | |||||
for (int i = 0; i < num; ++i) | |||||
{ | |||||
glyphs.getReference (start + i).moveBy (deltaX, 0.0f); | |||||
if (glyphs.getReference (start + i).isWhitespace()) | |||||
deltaX += extraPaddingBetweenWords; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void GlyphArrangement::splitLines (const String& text, Font font, int startIndex, | |||||
float x, float y, float width, float height, int maximumLines, | |||||
float lineWidth, Justification layout, float minimumHorizontalScale) | |||||
{ | |||||
const int length = text.length(); | |||||
const int originalStartIndex = startIndex; | |||||
int numLines = 1; | |||||
if (length <= 12 && ! text.containsAnyOf (" -\t\r\n")) | |||||
maximumLines = 1; | |||||
maximumLines = jmin (maximumLines, length); | |||||
while (numLines < maximumLines) | |||||
{ | |||||
++numLines; | |||||
const float newFontHeight = height / (float) numLines; | |||||
if (newFontHeight < font.getHeight()) | |||||
{ | |||||
font.setHeight (jmax (8.0f, newFontHeight)); | |||||
removeRangeOfGlyphs (startIndex, -1); | |||||
addLineOfText (font, text, x, y); | |||||
lineWidth = glyphs.getReference (glyphs.size() - 1).getRight() | |||||
- glyphs.getReference (startIndex).getLeft(); | |||||
} | |||||
// Try to estimate the point at which there are enough lines to fit the text, | |||||
// allowing for unevenness in the lengths due to differently sized words. | |||||
const float lineLengthUnevennessAllowance = 80.0f; | |||||
if (numLines > (lineWidth + lineLengthUnevennessAllowance) / width || newFontHeight < 8.0f) | |||||
break; | |||||
} | |||||
if (numLines < 1) | |||||
numLines = 1; | |||||
float lineY = y; | |||||
float widthPerLine = lineWidth / numLines; | |||||
while (lineY < y + height) | |||||
{ | |||||
int i = startIndex; | |||||
const float lineStartX = glyphs.getReference (startIndex).getLeft(); | |||||
const float lineBottomY = lineY + font.getHeight(); | |||||
if (lineBottomY >= y + height) | |||||
{ | |||||
widthPerLine = width; | |||||
i = glyphs.size(); | |||||
} | |||||
else | |||||
{ | |||||
while (i < glyphs.size()) | |||||
{ | |||||
lineWidth = (glyphs.getReference (i).getRight() - lineStartX); | |||||
if (lineWidth > widthPerLine) | |||||
{ | |||||
// got to a point where the line's too long, so skip forward to find a | |||||
// good place to break it.. | |||||
const int searchStartIndex = i; | |||||
while (i < glyphs.size()) | |||||
{ | |||||
if ((glyphs.getReference (i).getRight() - lineStartX) * minimumHorizontalScale < width) | |||||
{ | |||||
if (glyphs.getReference (i).isWhitespace() | |||||
|| glyphs.getReference (i).getCharacter() == '-') | |||||
{ | |||||
++i; | |||||
break; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// can't find a suitable break, so try looking backwards.. | |||||
i = searchStartIndex; | |||||
for (int back = 1; back < jmin (7, i - startIndex - 1); ++back) | |||||
{ | |||||
if (glyphs.getReference (i - back).isWhitespace() | |||||
|| glyphs.getReference (i - back).getCharacter() == '-') | |||||
{ | |||||
i -= back - 1; | |||||
break; | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
++i; | |||||
} | |||||
break; | |||||
} | |||||
++i; | |||||
} | |||||
int wsStart = i; | |||||
while (wsStart > 0 && glyphs.getReference (wsStart - 1).isWhitespace()) | |||||
--wsStart; | |||||
int wsEnd = i; | |||||
while (wsEnd < glyphs.size() && glyphs.getReference (wsEnd).isWhitespace()) | |||||
++wsEnd; | |||||
removeRangeOfGlyphs (wsStart, wsEnd - wsStart); | |||||
i = jmax (wsStart, startIndex + 1); | |||||
} | |||||
i -= fitLineIntoSpace (startIndex, i - startIndex, | |||||
x, lineY, width, font.getHeight(), font, | |||||
layout.getOnlyHorizontalFlags() | Justification::verticallyCentred, | |||||
minimumHorizontalScale); | |||||
startIndex = i; | |||||
lineY = lineBottomY; | |||||
if (startIndex >= glyphs.size()) | |||||
break; | |||||
} | |||||
justifyGlyphs (originalStartIndex, glyphs.size() - originalStartIndex, | |||||
x, y, width, height, layout.getFlags() & ~Justification::horizontallyJustified); | |||||
} | |||||
//============================================================================== | |||||
void GlyphArrangement::drawGlyphUnderline (const Graphics& g, const PositionedGlyph& pg, | |||||
const int i, const AffineTransform& transform) const | |||||
{ | |||||
const float lineThickness = (pg.font.getDescent()) * 0.3f; | |||||
float nextX = pg.x + pg.w; | |||||
if (i < glyphs.size() - 1 && glyphs.getReference (i + 1).y == pg.y) | |||||
nextX = glyphs.getReference (i + 1).x; | |||||
Path p; | |||||
p.addRectangle (pg.x, pg.y + lineThickness * 2.0f, nextX - pg.x, lineThickness); | |||||
g.fillPath (p, transform); | |||||
} | |||||
void GlyphArrangement::draw (const Graphics& g) const | |||||
{ | |||||
draw (g, AffineTransform()); | |||||
} | |||||
void GlyphArrangement::draw (const Graphics& g, const AffineTransform& transform) const | |||||
{ | |||||
auto& context = g.getInternalContext(); | |||||
Font lastFont (context.getFont()); | |||||
bool needToRestore = false; | |||||
for (int i = 0; i < glyphs.size(); ++i) | |||||
{ | |||||
auto& pg = glyphs.getReference(i); | |||||
if (pg.font.isUnderlined()) | |||||
drawGlyphUnderline (g, pg, i, transform); | |||||
if (! pg.isWhitespace()) | |||||
{ | |||||
if (lastFont != pg.font) | |||||
{ | |||||
lastFont = pg.font; | |||||
if (! needToRestore) | |||||
{ | |||||
needToRestore = true; | |||||
context.saveState(); | |||||
} | |||||
context.setFont (lastFont); | |||||
} | |||||
context.drawGlyph (pg.glyph, AffineTransform::translation (pg.x, pg.y).followedBy (transform)); | |||||
} | |||||
} | |||||
if (needToRestore) | |||||
context.restoreState(); | |||||
} | |||||
void GlyphArrangement::createPath (Path& path) const | |||||
{ | |||||
for (auto& g : glyphs) | |||||
g.createPath (path); | |||||
} | |||||
int GlyphArrangement::findGlyphIndexAt (const float x, const float y) const | |||||
{ | |||||
for (int i = 0; i < glyphs.size(); ++i) | |||||
if (glyphs.getReference (i).hitTest (x, y)) | |||||
return i; | |||||
return -1; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,327 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A glyph from a particular font, with a particular size, style, | |||||
typeface and position. | |||||
You should rarely need to use this class directly - for most purposes, the | |||||
GlyphArrangement class will do what you need for text layout. | |||||
@see GlyphArrangement, Font | |||||
*/ | |||||
class JUCE_API PositionedGlyph | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
PositionedGlyph() noexcept; | |||||
PositionedGlyph (const Font& font, juce_wchar character, int glyphNumber, | |||||
float anchorX, float baselineY, float width, bool isWhitespace); | |||||
PositionedGlyph (const PositionedGlyph&); | |||||
PositionedGlyph& operator= (const PositionedGlyph&); | |||||
/** Move constructor */ | |||||
PositionedGlyph (PositionedGlyph&&) noexcept; | |||||
/** Move assignment operator */ | |||||
PositionedGlyph& operator= (PositionedGlyph&&) noexcept; | |||||
~PositionedGlyph(); | |||||
/** Returns the character the glyph represents. */ | |||||
juce_wchar getCharacter() const noexcept { return character; } | |||||
/** Checks whether the glyph is actually empty. */ | |||||
bool isWhitespace() const noexcept { return whitespace; } | |||||
/** Returns the position of the glyph's left-hand edge. */ | |||||
float getLeft() const noexcept { return x; } | |||||
/** Returns the position of the glyph's right-hand edge. */ | |||||
float getRight() const noexcept { return x + w; } | |||||
/** Returns the y position of the glyph's baseline. */ | |||||
float getBaselineY() const noexcept { return y; } | |||||
/** Returns the y position of the top of the glyph. */ | |||||
float getTop() const { return y - font.getAscent(); } | |||||
/** Returns the y position of the bottom of the glyph. */ | |||||
float getBottom() const { return y + font.getDescent(); } | |||||
/** Returns the bounds of the glyph. */ | |||||
Rectangle<float> getBounds() const { return Rectangle<float> (x, getTop(), w, font.getHeight()); } | |||||
//============================================================================== | |||||
/** Shifts the glyph's position by a relative amount. */ | |||||
void moveBy (float deltaX, float deltaY); | |||||
//============================================================================== | |||||
/** Draws the glyph into a graphics context. | |||||
(Note that this may change the context's currently selected font). | |||||
*/ | |||||
void draw (Graphics& g) const; | |||||
/** Draws the glyph into a graphics context, with an extra transform applied to it. | |||||
(Note that this may change the context's currently selected font). | |||||
*/ | |||||
void draw (Graphics& g, const AffineTransform& transform) const; | |||||
/** Returns the path for this glyph. | |||||
@param path the glyph's outline will be appended to this path | |||||
*/ | |||||
void createPath (Path& path) const; | |||||
/** Checks to see if a point lies within this glyph. */ | |||||
bool hitTest (float x, float y) const; | |||||
private: | |||||
//============================================================================== | |||||
friend class GlyphArrangement; | |||||
Font font; | |||||
juce_wchar character; | |||||
int glyph; | |||||
float x, y, w; | |||||
bool whitespace; | |||||
JUCE_LEAK_DETECTOR (PositionedGlyph) | |||||
}; | |||||
//============================================================================== | |||||
/** | |||||
A set of glyphs, each with a position. | |||||
You can create a GlyphArrangement, text to it and then draw it onto a | |||||
graphics context. It's used internally by the text methods in the | |||||
Graphics class, but can be used directly if more control is needed. | |||||
@see Font, PositionedGlyph | |||||
*/ | |||||
class JUCE_API GlyphArrangement | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty arrangement. */ | |||||
GlyphArrangement(); | |||||
/** Takes a copy of another arrangement. */ | |||||
GlyphArrangement (const GlyphArrangement&); | |||||
/** Copies another arrangement onto this one. | |||||
To add another arrangement without clearing this one, use addGlyphArrangement(). | |||||
*/ | |||||
GlyphArrangement& operator= (const GlyphArrangement&); | |||||
/** Destructor. */ | |||||
~GlyphArrangement(); | |||||
//============================================================================== | |||||
/** Returns the total number of glyphs in the arrangement. */ | |||||
int getNumGlyphs() const noexcept { return glyphs.size(); } | |||||
/** Returns one of the glyphs from the arrangement. | |||||
@param index the glyph's index, from 0 to (getNumGlyphs() - 1). Be | |||||
careful not to pass an out-of-range index here, as it | |||||
doesn't do any bounds-checking. | |||||
*/ | |||||
PositionedGlyph& getGlyph (int index) const noexcept; | |||||
//============================================================================== | |||||
/** Clears all text from the arrangement and resets it. */ | |||||
void clear(); | |||||
/** Appends a line of text to the arrangement. | |||||
This will add the text as a single line, where x is the left-hand edge of the | |||||
first character, and y is the position for the text's baseline. | |||||
If the text contains new-lines or carriage-returns, this will ignore them - use | |||||
addJustifiedText() to add multi-line arrangements. | |||||
*/ | |||||
void addLineOfText (const Font& font, | |||||
const String& text, | |||||
float x, float y); | |||||
/** Adds a line of text, truncating it if it's wider than a specified size. | |||||
This is the same as addLineOfText(), but if the line's width exceeds the value | |||||
specified in maxWidthPixels, it will be truncated using either ellipsis (i.e. dots: "..."), | |||||
if useEllipsis is true, or if this is false, it will just drop any subsequent characters. | |||||
*/ | |||||
void addCurtailedLineOfText (const Font& font, | |||||
const String& text, | |||||
float x, float y, | |||||
float maxWidthPixels, | |||||
bool useEllipsis); | |||||
/** Adds some multi-line text, breaking lines at word-boundaries if they are too wide. | |||||
This will add text to the arrangement, breaking it into new lines either where there | |||||
is a new-line or carriage-return character in the text, or where a line's width | |||||
exceeds the value set in maxLineWidth. | |||||
Each line that is added will be laid out using the flags set in horizontalLayout, so | |||||
the lines can be left- or right-justified, or centred horizontally in the space | |||||
between x and (x + maxLineWidth). | |||||
The y coordinate is the position of the baseline of the first line of text - subsequent | |||||
lines will be placed below it, separated by a distance of font.getHeight(). | |||||
*/ | |||||
void addJustifiedText (const Font& font, | |||||
const String& text, | |||||
float x, float y, | |||||
float maxLineWidth, | |||||
Justification horizontalLayout); | |||||
/** Tries to fit some text within a given space. | |||||
This does its best to make the given text readable within the specified rectangle, | |||||
so it useful for labelling things. | |||||
If the text is too big, it'll be squashed horizontally or broken over multiple lines | |||||
if the maximumLinesToUse value allows this. If the text just won't fit into the space, | |||||
it'll cram as much as possible in there, and put some ellipsis at the end to show that | |||||
it's been truncated. | |||||
A Justification parameter lets you specify how the text is laid out within the rectangle, | |||||
both horizontally and vertically. | |||||
The minimumHorizontalScale parameter specifies how much the text can be squashed horizontally | |||||
to try to squeeze it into the space. If you don't want any horizontal scaling to occur, you | |||||
can set this value to 1.0f. Pass 0 if you want it to use the default value. | |||||
@see Graphics::drawFittedText | |||||
*/ | |||||
void addFittedText (const Font& font, | |||||
const String& text, | |||||
float x, float y, float width, float height, | |||||
Justification layout, | |||||
int maximumLinesToUse, | |||||
float minimumHorizontalScale = 0.0f); | |||||
/** Appends another glyph arrangement to this one. */ | |||||
void addGlyphArrangement (const GlyphArrangement&); | |||||
/** Appends a custom glyph to the arrangement. */ | |||||
void addGlyph (const PositionedGlyph&); | |||||
//============================================================================== | |||||
/** Draws this glyph arrangement to a graphics context. | |||||
This uses cached bitmaps so is much faster than the draw (Graphics&, const AffineTransform&) | |||||
method, which renders the glyphs as filled vectors. | |||||
*/ | |||||
void draw (const Graphics&) const; | |||||
/** Draws this glyph arrangement to a graphics context. | |||||
This renders the paths as filled vectors, so is far slower than the draw (Graphics&) | |||||
method for non-transformed arrangements. | |||||
*/ | |||||
void draw (const Graphics&, const AffineTransform&) const; | |||||
/** Converts the set of glyphs into a path. | |||||
@param path the glyphs' outlines will be appended to this path | |||||
*/ | |||||
void createPath (Path& path) const; | |||||
/** Looks for a glyph that contains the given coordinate. | |||||
@returns the index of the glyph, or -1 if none were found. | |||||
*/ | |||||
int findGlyphIndexAt (float x, float y) const; | |||||
//============================================================================== | |||||
/** Finds the smallest rectangle that will enclose a subset of the glyphs. | |||||
@param startIndex the first glyph to test | |||||
@param numGlyphs the number of glyphs to include; if this is < 0, all glyphs after | |||||
startIndex will be included | |||||
@param includeWhitespace if true, the extent of any whitespace characters will also | |||||
be taken into account | |||||
*/ | |||||
Rectangle<float> getBoundingBox (int startIndex, int numGlyphs, bool includeWhitespace) const; | |||||
/** Shifts a set of glyphs by a given amount. | |||||
@param startIndex the first glyph to transform | |||||
@param numGlyphs the number of glyphs to move; if this is < 0, all glyphs after | |||||
startIndex will be used | |||||
@param deltaX the amount to add to their x-positions | |||||
@param deltaY the amount to add to their y-positions | |||||
*/ | |||||
void moveRangeOfGlyphs (int startIndex, int numGlyphs, | |||||
float deltaX, float deltaY); | |||||
/** Removes a set of glyphs from the arrangement. | |||||
@param startIndex the first glyph to remove | |||||
@param numGlyphs the number of glyphs to remove; if this is < 0, all glyphs after | |||||
startIndex will be deleted | |||||
*/ | |||||
void removeRangeOfGlyphs (int startIndex, int numGlyphs); | |||||
/** Expands or compresses a set of glyphs horizontally. | |||||
@param startIndex the first glyph to transform | |||||
@param numGlyphs the number of glyphs to stretch; if this is < 0, all glyphs after | |||||
startIndex will be used | |||||
@param horizontalScaleFactor how much to scale their horizontal width by | |||||
*/ | |||||
void stretchRangeOfGlyphs (int startIndex, int numGlyphs, | |||||
float horizontalScaleFactor); | |||||
/** Justifies a set of glyphs within a given space. | |||||
This moves the glyphs as a block so that the whole thing is located within the | |||||
given rectangle with the specified layout. | |||||
If the Justification::horizontallyJustified flag is specified, each line will | |||||
be stretched out to fill the specified width. | |||||
*/ | |||||
void justifyGlyphs (int startIndex, int numGlyphs, | |||||
float x, float y, float width, float height, | |||||
Justification justification); | |||||
private: | |||||
//============================================================================== | |||||
Array<PositionedGlyph> glyphs; | |||||
int insertEllipsis (const Font&, float maxXPos, int startIndex, int endIndex); | |||||
int fitLineIntoSpace (int start, int numGlyphs, float x, float y, float w, float h, const Font&, | |||||
Justification, float minimumHorizontalScale); | |||||
void spreadOutLine (int start, int numGlyphs, float targetWidth); | |||||
void splitLines (const String&, Font, int start, float x, float y, float w, float h, int maxLines, | |||||
float lineWidth, Justification, float minimumHorizontalScale); | |||||
void addLinesWithLineBreaks (const String&, const Font&, float x, float y, float width, float height, Justification); | |||||
void drawGlyphUnderline (const Graphics&, const PositionedGlyph&, int, const AffineTransform&) const; | |||||
JUCE_LEAK_DETECTOR (GlyphArrangement) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,592 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
TextLayout::Glyph::Glyph (const int glyph, Point<float> anch, float w) noexcept | |||||
: glyphCode (glyph), anchor (anch), width (w) | |||||
{ | |||||
} | |||||
TextLayout::Glyph::Glyph (const Glyph& other) noexcept | |||||
: glyphCode (other.glyphCode), anchor (other.anchor), width (other.width) | |||||
{ | |||||
} | |||||
TextLayout::Glyph& TextLayout::Glyph::operator= (const Glyph& other) noexcept | |||||
{ | |||||
glyphCode = other.glyphCode; | |||||
anchor = other.anchor; | |||||
width = other.width; | |||||
return *this; | |||||
} | |||||
TextLayout::Glyph::~Glyph() noexcept {} | |||||
//============================================================================== | |||||
TextLayout::Run::Run() noexcept | |||||
: colour (0xff000000) | |||||
{ | |||||
} | |||||
TextLayout::Run::Run (Range<int> range, int numGlyphsToPreallocate) | |||||
: colour (0xff000000), stringRange (range) | |||||
{ | |||||
glyphs.ensureStorageAllocated (numGlyphsToPreallocate); | |||||
} | |||||
TextLayout::Run::Run (const Run& other) | |||||
: font (other.font), | |||||
colour (other.colour), | |||||
glyphs (other.glyphs), | |||||
stringRange (other.stringRange) | |||||
{ | |||||
} | |||||
TextLayout::Run::~Run() noexcept {} | |||||
//============================================================================== | |||||
TextLayout::Line::Line() noexcept | |||||
: ascent (0.0f), descent (0.0f), leading (0.0f) | |||||
{ | |||||
} | |||||
TextLayout::Line::Line (Range<int> range, Point<float> o, float asc, float desc, | |||||
float lead, int numRunsToPreallocate) | |||||
: stringRange (range), lineOrigin (o), | |||||
ascent (asc), descent (desc), leading (lead) | |||||
{ | |||||
runs.ensureStorageAllocated (numRunsToPreallocate); | |||||
} | |||||
TextLayout::Line::Line (const Line& other) | |||||
: stringRange (other.stringRange), lineOrigin (other.lineOrigin), | |||||
ascent (other.ascent), descent (other.descent), leading (other.leading) | |||||
{ | |||||
runs.addCopiesOf (other.runs); | |||||
} | |||||
TextLayout::Line::~Line() noexcept | |||||
{ | |||||
} | |||||
Range<float> TextLayout::Line::getLineBoundsX() const noexcept | |||||
{ | |||||
Range<float> range; | |||||
bool isFirst = true; | |||||
for (auto* run : runs) | |||||
{ | |||||
for (auto& glyph : run->glyphs) | |||||
{ | |||||
Range<float> runRange (glyph.anchor.x, glyph.anchor.x + glyph.width); | |||||
if (isFirst) | |||||
{ | |||||
isFirst = false; | |||||
range = runRange; | |||||
} | |||||
else | |||||
{ | |||||
range = range.getUnionWith (runRange); | |||||
} | |||||
} | |||||
} | |||||
return range + lineOrigin.x; | |||||
} | |||||
Range<float> TextLayout::Line::getLineBoundsY() const noexcept | |||||
{ | |||||
return { lineOrigin.y - ascent, | |||||
lineOrigin.y + descent }; | |||||
} | |||||
Rectangle<float> TextLayout::Line::getLineBounds() const noexcept | |||||
{ | |||||
auto x = getLineBoundsX(); | |||||
auto y = getLineBoundsY(); | |||||
return { x.getStart(), y.getStart(), x.getLength(), y.getLength() }; | |||||
} | |||||
//============================================================================== | |||||
TextLayout::TextLayout() | |||||
: width (0), height (0), justification (Justification::topLeft) | |||||
{ | |||||
} | |||||
TextLayout::TextLayout (const TextLayout& other) | |||||
: width (other.width), height (other.height), | |||||
justification (other.justification) | |||||
{ | |||||
lines.addCopiesOf (other.lines); | |||||
} | |||||
TextLayout::TextLayout (TextLayout&& other) noexcept | |||||
: lines (static_cast<OwnedArray<Line>&&> (other.lines)), | |||||
width (other.width), height (other.height), | |||||
justification (other.justification) | |||||
{ | |||||
} | |||||
TextLayout& TextLayout::operator= (TextLayout&& other) noexcept | |||||
{ | |||||
lines = static_cast<OwnedArray<Line>&&> (other.lines); | |||||
width = other.width; | |||||
height = other.height; | |||||
justification = other.justification; | |||||
return *this; | |||||
} | |||||
TextLayout& TextLayout::operator= (const TextLayout& other) | |||||
{ | |||||
width = other.width; | |||||
height = other.height; | |||||
justification = other.justification; | |||||
lines.clear(); | |||||
lines.addCopiesOf (other.lines); | |||||
return *this; | |||||
} | |||||
TextLayout::~TextLayout() | |||||
{ | |||||
} | |||||
TextLayout::Line& TextLayout::getLine (const int index) const | |||||
{ | |||||
return *lines.getUnchecked (index); | |||||
} | |||||
void TextLayout::ensureStorageAllocated (int numLinesNeeded) | |||||
{ | |||||
lines.ensureStorageAllocated (numLinesNeeded); | |||||
} | |||||
void TextLayout::addLine (Line* line) | |||||
{ | |||||
lines.add (line); | |||||
} | |||||
void TextLayout::draw (Graphics& g, Rectangle<float> area) const | |||||
{ | |||||
auto origin = justification.appliedToRectangle (Rectangle<float> (width, getHeight()), area).getPosition(); | |||||
auto& context = g.getInternalContext(); | |||||
for (auto* line : lines) | |||||
{ | |||||
auto lineOrigin = origin + line->lineOrigin; | |||||
for (auto* run : line->runs) | |||||
{ | |||||
context.setFont (run->font); | |||||
context.setFill (run->colour); | |||||
for (auto& glyph : run->glyphs) | |||||
context.drawGlyph (glyph.glyphCode, AffineTransform::translation (lineOrigin.x + glyph.anchor.x, | |||||
lineOrigin.y + glyph.anchor.y)); | |||||
if (run->font.isUnderlined()) | |||||
{ | |||||
Range<float> runExtent; | |||||
for (auto& glyph : run->glyphs) | |||||
{ | |||||
Range<float> glyphRange (glyph.anchor.x, glyph.anchor.x + glyph.width); | |||||
runExtent = runExtent.isEmpty() ? glyphRange | |||||
: runExtent.getUnionWith (glyphRange); | |||||
} | |||||
auto lineThickness = run->font.getDescent() * 0.3f; | |||||
context.fillRect ({ runExtent.getStart() + lineOrigin.x, lineOrigin.y + lineThickness * 2.0f, | |||||
runExtent.getLength(), lineThickness }); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void TextLayout::createLayout (const AttributedString& text, float maxWidth) | |||||
{ | |||||
createLayout (text, maxWidth, 1.0e7f); | |||||
} | |||||
void TextLayout::createLayout (const AttributedString& text, float maxWidth, float maxHeight) | |||||
{ | |||||
lines.clear(); | |||||
width = maxWidth; | |||||
height = maxHeight; | |||||
justification = text.getJustification(); | |||||
if (! createNativeLayout (text)) | |||||
createStandardLayout (text); | |||||
recalculateSize(); | |||||
} | |||||
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth) | |||||
{ | |||||
createLayoutWithBalancedLineLengths (text, maxWidth, 1.0e7f); | |||||
} | |||||
void TextLayout::createLayoutWithBalancedLineLengths (const AttributedString& text, float maxWidth, float maxHeight) | |||||
{ | |||||
auto minimumWidth = maxWidth / 2.0f; | |||||
auto bestWidth = maxWidth; | |||||
float bestLineProportion = 0.0f; | |||||
while (maxWidth > minimumWidth) | |||||
{ | |||||
createLayout (text, maxWidth, maxHeight); | |||||
if (getNumLines() < 2) | |||||
return; | |||||
auto line1 = lines.getUnchecked (lines.size() - 1)->getLineBoundsX().getLength(); | |||||
auto line2 = lines.getUnchecked (lines.size() - 2)->getLineBoundsX().getLength(); | |||||
auto shortest = jmin (line1, line2); | |||||
auto longest = jmax (line1, line2); | |||||
auto prop = shortest > 0 ? longest / shortest : 1.0f; | |||||
if (prop > 0.9f && prop < 1.1f) | |||||
return; | |||||
if (prop > bestLineProportion) | |||||
{ | |||||
bestLineProportion = prop; | |||||
bestWidth = maxWidth; | |||||
} | |||||
maxWidth -= 10.0f; | |||||
} | |||||
if (bestWidth != maxWidth) | |||||
createLayout (text, bestWidth, maxHeight); | |||||
} | |||||
//============================================================================== | |||||
namespace TextLayoutHelpers | |||||
{ | |||||
struct Token | |||||
{ | |||||
Token (const String& t, const Font& f, Colour c, bool whitespace) | |||||
: text (t), font (f), colour (c), | |||||
area (font.getStringWidthFloat (t), f.getHeight()), | |||||
isWhitespace (whitespace), | |||||
isNewLine (t.containsChar ('\n') || t.containsChar ('\r')) | |||||
{} | |||||
const String text; | |||||
const Font font; | |||||
const Colour colour; | |||||
Rectangle<float> area; | |||||
int line; | |||||
float lineHeight; | |||||
const bool isWhitespace, isNewLine; | |||||
Token& operator= (const Token&) = delete; | |||||
}; | |||||
struct TokenList | |||||
{ | |||||
TokenList() noexcept {} | |||||
void createLayout (const AttributedString& text, TextLayout& layout) | |||||
{ | |||||
layout.ensureStorageAllocated (totalLines); | |||||
addTextRuns (text); | |||||
layoutRuns (layout.getWidth(), text.getLineSpacing(), text.getWordWrap()); | |||||
int charPosition = 0; | |||||
int lineStartPosition = 0; | |||||
int runStartPosition = 0; | |||||
ScopedPointer<TextLayout::Line> currentLine; | |||||
ScopedPointer<TextLayout::Run> currentRun; | |||||
bool needToSetLineOrigin = true; | |||||
for (int i = 0; i < tokens.size(); ++i) | |||||
{ | |||||
auto& t = *tokens.getUnchecked (i); | |||||
Array<int> newGlyphs; | |||||
Array<float> xOffsets; | |||||
t.font.getGlyphPositions (getTrimmedEndIfNotAllWhitespace (t.text), newGlyphs, xOffsets); | |||||
if (currentRun == nullptr) currentRun = new TextLayout::Run(); | |||||
if (currentLine == nullptr) currentLine = new TextLayout::Line(); | |||||
if (newGlyphs.size() > 0) | |||||
{ | |||||
currentRun->glyphs.ensureStorageAllocated (currentRun->glyphs.size() + newGlyphs.size()); | |||||
auto tokenOrigin = t.area.getPosition().translated (0, t.font.getAscent()); | |||||
if (needToSetLineOrigin) | |||||
{ | |||||
needToSetLineOrigin = false; | |||||
currentLine->lineOrigin = tokenOrigin; | |||||
} | |||||
auto glyphOffset = tokenOrigin - currentLine->lineOrigin; | |||||
for (int j = 0; j < newGlyphs.size(); ++j) | |||||
{ | |||||
auto x = xOffsets.getUnchecked (j); | |||||
currentRun->glyphs.add (TextLayout::Glyph (newGlyphs.getUnchecked(j), | |||||
glyphOffset.translated (x, 0), | |||||
xOffsets.getUnchecked (j + 1) - x)); | |||||
} | |||||
charPosition += newGlyphs.size(); | |||||
} | |||||
if (t.isWhitespace || t.isNewLine) | |||||
++charPosition; | |||||
if (auto* nextToken = tokens[i + 1]) | |||||
{ | |||||
if (t.font != nextToken->font || t.colour != nextToken->colour) | |||||
{ | |||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||||
runStartPosition = charPosition; | |||||
} | |||||
if (t.line != nextToken->line) | |||||
{ | |||||
if (currentRun == nullptr) | |||||
currentRun = new TextLayout::Run(); | |||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||||
currentLine->stringRange = Range<int> (lineStartPosition, charPosition); | |||||
if (! needToSetLineOrigin) | |||||
layout.addLine (currentLine.release()); | |||||
runStartPosition = charPosition; | |||||
lineStartPosition = charPosition; | |||||
needToSetLineOrigin = true; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
addRun (*currentLine, currentRun.release(), t, runStartPosition, charPosition); | |||||
currentLine->stringRange = Range<int> (lineStartPosition, charPosition); | |||||
if (! needToSetLineOrigin) | |||||
layout.addLine (currentLine.release()); | |||||
needToSetLineOrigin = true; | |||||
} | |||||
} | |||||
if ((text.getJustification().getFlags() & (Justification::right | Justification::horizontallyCentred)) != 0) | |||||
{ | |||||
auto totalW = layout.getWidth(); | |||||
bool isCentred = (text.getJustification().getFlags() & Justification::horizontallyCentred) != 0; | |||||
for (int i = 0; i < layout.getNumLines(); ++i) | |||||
{ | |||||
auto dx = totalW - layout.getLine(i).getLineBoundsX().getLength(); | |||||
if (isCentred) | |||||
dx /= 2.0f; | |||||
layout.getLine(i).lineOrigin.x += dx; | |||||
} | |||||
} | |||||
} | |||||
private: | |||||
static void addRun (TextLayout::Line& glyphLine, TextLayout::Run* glyphRun, | |||||
const Token& t, const int start, const int end) | |||||
{ | |||||
glyphRun->stringRange = { start, end }; | |||||
glyphRun->font = t.font; | |||||
glyphRun->colour = t.colour; | |||||
glyphLine.ascent = jmax (glyphLine.ascent, t.font.getAscent()); | |||||
glyphLine.descent = jmax (glyphLine.descent, t.font.getDescent()); | |||||
glyphLine.runs.add (glyphRun); | |||||
} | |||||
static int getCharacterType (const juce_wchar c) noexcept | |||||
{ | |||||
if (c == '\r' || c == '\n') | |||||
return 0; | |||||
return CharacterFunctions::isWhitespace (c) ? 2 : 1; | |||||
} | |||||
void appendText (const String& stringText, const Font& font, Colour colour) | |||||
{ | |||||
auto t = stringText.getCharPointer(); | |||||
String currentString; | |||||
int lastCharType = 0; | |||||
for (;;) | |||||
{ | |||||
auto c = t.getAndAdvance(); | |||||
if (c == 0) | |||||
break; | |||||
auto charType = getCharacterType (c); | |||||
if (charType == 0 || charType != lastCharType) | |||||
{ | |||||
if (currentString.isNotEmpty()) | |||||
tokens.add (new Token (currentString, font, colour, | |||||
lastCharType == 2 || lastCharType == 0)); | |||||
currentString = String::charToString (c); | |||||
if (c == '\r' && *t == '\n') | |||||
currentString += t.getAndAdvance(); | |||||
} | |||||
else | |||||
{ | |||||
currentString += c; | |||||
} | |||||
lastCharType = charType; | |||||
} | |||||
if (currentString.isNotEmpty()) | |||||
tokens.add (new Token (currentString, font, colour, lastCharType == 2)); | |||||
} | |||||
void layoutRuns (const float maxWidth, const float extraLineSpacing, const AttributedString::WordWrap wordWrap) | |||||
{ | |||||
float x = 0, y = 0, h = 0; | |||||
int i; | |||||
for (i = 0; i < tokens.size(); ++i) | |||||
{ | |||||
auto& t = *tokens.getUnchecked(i); | |||||
t.area.setPosition (x, y); | |||||
t.line = totalLines; | |||||
x += t.area.getWidth(); | |||||
h = jmax (h, t.area.getHeight() + extraLineSpacing); | |||||
auto* nextTok = tokens[i + 1]; | |||||
if (nextTok == nullptr) | |||||
break; | |||||
const bool tokenTooLarge = (x + nextTok->area.getWidth() > maxWidth); | |||||
if (t.isNewLine || ((! nextTok->isWhitespace) && (tokenTooLarge && wordWrap != AttributedString::none))) | |||||
{ | |||||
setLastLineHeight (i + 1, h); | |||||
x = 0; | |||||
y += h; | |||||
h = 0; | |||||
++totalLines; | |||||
} | |||||
} | |||||
setLastLineHeight (jmin (i + 1, tokens.size()), h); | |||||
++totalLines; | |||||
} | |||||
void setLastLineHeight (int i, const float height) noexcept | |||||
{ | |||||
while (--i >= 0) | |||||
{ | |||||
auto& tok = *tokens.getUnchecked (i); | |||||
if (tok.line == totalLines) | |||||
tok.lineHeight = height; | |||||
else | |||||
break; | |||||
} | |||||
} | |||||
void addTextRuns (const AttributedString& text) | |||||
{ | |||||
auto numAttributes = text.getNumAttributes(); | |||||
tokens.ensureStorageAllocated (jmax (64, numAttributes)); | |||||
for (int i = 0; i < numAttributes; ++i) | |||||
{ | |||||
auto& attr = text.getAttribute (i); | |||||
appendText (text.getText().substring (attr.range.getStart(), attr.range.getEnd()), | |||||
attr.font, attr.colour); | |||||
} | |||||
} | |||||
static String getTrimmedEndIfNotAllWhitespace (const String& s) | |||||
{ | |||||
auto trimmed = s.trimEnd(); | |||||
if (trimmed.isEmpty() && s.isNotEmpty()) | |||||
trimmed = s.replaceCharacters ("\r\n\t", " "); | |||||
return trimmed; | |||||
} | |||||
OwnedArray<Token> tokens; | |||||
int totalLines = 0; | |||||
JUCE_DECLARE_NON_COPYABLE (TokenList) | |||||
}; | |||||
} | |||||
//============================================================================== | |||||
void TextLayout::createStandardLayout (const AttributedString& text) | |||||
{ | |||||
TextLayoutHelpers::TokenList l; | |||||
l.createLayout (text, *this); | |||||
} | |||||
void TextLayout::recalculateSize() | |||||
{ | |||||
if (! lines.isEmpty()) | |||||
{ | |||||
auto bounds = lines.getFirst()->getLineBounds(); | |||||
for (auto* line : lines) | |||||
bounds = bounds.getUnion (line->getLineBounds()); | |||||
for (auto* line : lines) | |||||
line->lineOrigin.x -= bounds.getX(); | |||||
width = bounds.getWidth(); | |||||
height = bounds.getHeight(); | |||||
} | |||||
else | |||||
{ | |||||
width = 0; | |||||
height = 0; | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,195 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A Pre-formatted piece of text, which may contain multiple fonts and colours. | |||||
A TextLayout is created from an AttributedString, and once created can be | |||||
quickly drawn into a Graphics context. | |||||
@see AttributedString | |||||
*/ | |||||
class JUCE_API TextLayout | |||||
{ | |||||
public: | |||||
/** Creates an empty layout. | |||||
Having created a TextLayout, you can populate it using createLayout() or | |||||
createLayoutWithBalancedLineLengths(). | |||||
*/ | |||||
TextLayout(); | |||||
TextLayout (const TextLayout&); | |||||
TextLayout& operator= (const TextLayout&); | |||||
TextLayout (TextLayout&&) noexcept; | |||||
TextLayout& operator= (TextLayout&&) noexcept; | |||||
/** Destructor. */ | |||||
~TextLayout(); | |||||
//============================================================================== | |||||
/** Creates a layout from the given attributed string. | |||||
This will replace any data that is currently stored in the layout. | |||||
*/ | |||||
void createLayout (const AttributedString&, float maxWidth); | |||||
/** Creates a layout from the given attributed string, given some size constraints. | |||||
This will replace any data that is currently stored in the layout. | |||||
*/ | |||||
void createLayout (const AttributedString&, float maxWidth, float maxHeight); | |||||
/** Creates a layout, attempting to choose a width which results in lines | |||||
of a similar length. | |||||
This will be slower than the normal createLayout method, but produces a | |||||
tidier result. | |||||
*/ | |||||
void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth); | |||||
/** Creates a layout, attempting to choose a width which results in lines | |||||
of a similar length. | |||||
This will be slower than the normal createLayout method, but produces a | |||||
tidier result. | |||||
*/ | |||||
void createLayoutWithBalancedLineLengths (const AttributedString&, float maxWidth, float maxHeight); | |||||
/** Draws the layout within the specified area. | |||||
The position of the text within the rectangle is controlled by the justification | |||||
flags set in the original AttributedString that was used to create this layout. | |||||
*/ | |||||
void draw (Graphics&, Rectangle<float> area) const; | |||||
//============================================================================== | |||||
/** A positioned glyph. */ | |||||
class JUCE_API Glyph | |||||
{ | |||||
public: | |||||
Glyph (int glyphCode, Point<float> anchor, float width) noexcept; | |||||
Glyph (const Glyph&) noexcept; | |||||
Glyph& operator= (const Glyph&) noexcept; | |||||
~Glyph() noexcept; | |||||
/** The code number of this glyph. */ | |||||
int glyphCode; | |||||
/** The glyph's anchor point - this is relative to the line's origin. | |||||
@see TextLayout::Line::lineOrigin | |||||
*/ | |||||
Point<float> anchor; | |||||
float width; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (Glyph) | |||||
}; | |||||
//============================================================================== | |||||
/** A sequence of glyphs with a common font and colour. */ | |||||
class JUCE_API Run | |||||
{ | |||||
public: | |||||
Run() noexcept; | |||||
Run (const Run&); | |||||
Run (Range<int> stringRange, int numGlyphsToPreallocate); | |||||
~Run() noexcept; | |||||
Font font; /**< The run's font. */ | |||||
Colour colour; /**< The run's colour. */ | |||||
Array<Glyph> glyphs; /**< The glyphs in this run. */ | |||||
Range<int> stringRange; /**< The character range that this run represents in the | |||||
original string that was used to create it. */ | |||||
private: | |||||
Run& operator= (const Run&); | |||||
JUCE_LEAK_DETECTOR (Run) | |||||
}; | |||||
//============================================================================== | |||||
/** A line containing a sequence of glyph-runs. */ | |||||
class JUCE_API Line | |||||
{ | |||||
public: | |||||
Line() noexcept; | |||||
Line (const Line&); | |||||
Line (Range<int> stringRange, Point<float> lineOrigin, | |||||
float ascent, float descent, float leading, int numRunsToPreallocate); | |||||
~Line() noexcept; | |||||
/** Returns the X position range which contains all the glyphs in this line. */ | |||||
Range<float> getLineBoundsX() const noexcept; | |||||
/** Returns the Y position range which contains all the glyphs in this line. */ | |||||
Range<float> getLineBoundsY() const noexcept; | |||||
/** Returns the smallest rectangle which contains all the glyphs in this line. */ | |||||
Rectangle<float> getLineBounds() const noexcept; | |||||
OwnedArray<Run> runs; /**< The glyph-runs in this line. */ | |||||
Range<int> stringRange; /**< The character range that this line represents in the | |||||
original string that was used to create it. */ | |||||
Point<float> lineOrigin; /**< The line's baseline origin. */ | |||||
float ascent, descent, leading; | |||||
private: | |||||
Line& operator= (const Line&); | |||||
JUCE_LEAK_DETECTOR (Line) | |||||
}; | |||||
//============================================================================== | |||||
/** Returns the maximum width of the content. */ | |||||
float getWidth() const noexcept { return width; } | |||||
/** Returns the maximum height of the content. */ | |||||
float getHeight() const noexcept { return height; } | |||||
/** Returns the number of lines in the layout. */ | |||||
int getNumLines() const noexcept { return lines.size(); } | |||||
/** Returns one of the lines. */ | |||||
Line& getLine (int index) const; | |||||
/** Adds a line to the layout. The layout will take ownership of this line object | |||||
and will delete it when it is no longer needed. */ | |||||
void addLine (Line*); | |||||
/** Pre-allocates space for the specified number of lines. */ | |||||
void ensureStorageAllocated (int numLinesNeeded); | |||||
private: | |||||
OwnedArray<Line> lines; | |||||
float width, height; | |||||
Justification justification; | |||||
void createStandardLayout (const AttributedString&); | |||||
bool createNativeLayout (const AttributedString&); | |||||
void recalculateSize(); | |||||
JUCE_LEAK_DETECTOR (TextLayout) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,267 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
struct FontStyleHelpers | |||||
{ | |||||
static const char* getStyleName (const bool bold, | |||||
const bool italic) noexcept | |||||
{ | |||||
if (bold && italic) return "Bold Italic"; | |||||
if (bold) return "Bold"; | |||||
if (italic) return "Italic"; | |||||
return "Regular"; | |||||
} | |||||
static const char* getStyleName (const int styleFlags) noexcept | |||||
{ | |||||
return getStyleName ((styleFlags & Font::bold) != 0, | |||||
(styleFlags & Font::italic) != 0); | |||||
} | |||||
static bool isBold (const String& style) noexcept | |||||
{ | |||||
return style.containsWholeWordIgnoreCase ("Bold"); | |||||
} | |||||
static bool isItalic (const String& style) noexcept | |||||
{ | |||||
return style.containsWholeWordIgnoreCase ("Italic") | |||||
|| style.containsWholeWordIgnoreCase ("Oblique"); | |||||
} | |||||
static bool isPlaceholderFamilyName (const String& family) | |||||
{ | |||||
return family == Font::getDefaultSansSerifFontName() | |||||
|| family == Font::getDefaultSerifFontName() | |||||
|| family == Font::getDefaultMonospacedFontName(); | |||||
} | |||||
struct ConcreteFamilyNames | |||||
{ | |||||
ConcreteFamilyNames() | |||||
: sans (findName (Font::getDefaultSansSerifFontName())), | |||||
serif (findName (Font::getDefaultSerifFontName())), | |||||
mono (findName (Font::getDefaultMonospacedFontName())) | |||||
{ | |||||
} | |||||
String lookUp (const String& placeholder) | |||||
{ | |||||
if (placeholder == Font::getDefaultSansSerifFontName()) return sans; | |||||
if (placeholder == Font::getDefaultSerifFontName()) return serif; | |||||
if (placeholder == Font::getDefaultMonospacedFontName()) return mono; | |||||
return findName (placeholder); | |||||
} | |||||
private: | |||||
static String findName (const String& placeholder) | |||||
{ | |||||
const Font f (placeholder, Font::getDefaultStyle(), 15.0f); | |||||
return Font::getDefaultTypefaceForFont (f)->getName(); | |||||
} | |||||
String sans, serif, mono; | |||||
}; | |||||
static String getConcreteFamilyNameFromPlaceholder (const String& placeholder) | |||||
{ | |||||
static ConcreteFamilyNames names; | |||||
return names.lookUp (placeholder); | |||||
} | |||||
static String getConcreteFamilyName (const Font& font) | |||||
{ | |||||
const String& family = font.getTypefaceName(); | |||||
return isPlaceholderFamilyName (family) ? getConcreteFamilyNameFromPlaceholder (family) | |||||
: family; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
Typeface::Typeface (const String& faceName, const String& styleName) noexcept | |||||
: name (faceName), style (styleName) | |||||
{ | |||||
} | |||||
Typeface::~Typeface() | |||||
{ | |||||
} | |||||
Typeface::Ptr Typeface::getFallbackTypeface() | |||||
{ | |||||
const Font fallbackFont (Font::getFallbackFontName(), Font::getFallbackFontStyle(), 10.0f); | |||||
return fallbackFont.getTypeface(); | |||||
} | |||||
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight) | |||||
{ | |||||
Path path; | |||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) | |||||
{ | |||||
applyVerticalHintingTransform (fontHeight, path); | |||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), | |||||
path, transform); | |||||
} | |||||
return nullptr; | |||||
} | |||||
//============================================================================== | |||||
struct Typeface::HintingParams | |||||
{ | |||||
HintingParams (Typeface& t) | |||||
: cachedSize (0), top (0), middle (0), bottom (0) | |||||
{ | |||||
Font font (&t); | |||||
font = font.withHeight ((float) standardHeight); | |||||
top = getAverageY (font, "BDEFPRTZOQ", true); | |||||
middle = getAverageY (font, "acegmnopqrsuvwxy", true); | |||||
bottom = getAverageY (font, "BDELZOC", false); | |||||
} | |||||
void applyVerticalHintingTransform (float fontSize, Path& path) | |||||
{ | |||||
if (cachedSize != fontSize) | |||||
{ | |||||
cachedSize = fontSize; | |||||
cachedScale = Scaling (top, middle, bottom, fontSize); | |||||
} | |||||
if (bottom < top + 3.0f / fontSize) | |||||
return; | |||||
Path result; | |||||
for (Path::Iterator i (path); i.next();) | |||||
{ | |||||
switch (i.elementType) | |||||
{ | |||||
case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break; | |||||
case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break; | |||||
case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1), | |||||
i.x2, cachedScale.apply (i.y2)); break; | |||||
case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1), | |||||
i.x2, cachedScale.apply (i.y2), | |||||
i.x3, cachedScale.apply (i.y3)); break; | |||||
case Path::Iterator::closePath: result.closeSubPath(); break; | |||||
default: jassertfalse; break; | |||||
} | |||||
} | |||||
result.swapWithPath (path); | |||||
} | |||||
private: | |||||
struct Scaling | |||||
{ | |||||
Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {} | |||||
Scaling (float t, float m, float b, float fontSize) noexcept : middle (m) | |||||
{ | |||||
const float newT = std::floor (fontSize * t + 0.5f) / fontSize; | |||||
const float newB = std::floor (fontSize * b + 0.5f) / fontSize; | |||||
const float newM = std::floor (fontSize * m + 0.3f) / fontSize; // this is slightly biased so that lower-case letters | |||||
// are more likely to become taller than shorter. | |||||
upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t)); | |||||
lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m)); | |||||
upperOffset = newM - m * upperScale; | |||||
lowerOffset = newB - b * lowerScale; | |||||
} | |||||
float apply (float y) const noexcept | |||||
{ | |||||
return y < middle ? (y * upperScale + upperOffset) | |||||
: (y * lowerScale + lowerOffset); | |||||
} | |||||
float middle, upperScale, upperOffset, lowerScale, lowerOffset; | |||||
}; | |||||
float cachedSize; | |||||
Scaling cachedScale; | |||||
static float getAverageY (const Font& font, const char* chars, bool getTop) | |||||
{ | |||||
GlyphArrangement ga; | |||||
ga.addLineOfText (font, chars, 0, 0); | |||||
Array<float> y; | |||||
DefaultElementComparator<float> sorter; | |||||
for (int i = 0; i < ga.getNumGlyphs(); ++i) | |||||
{ | |||||
Path p; | |||||
ga.getGlyph (i).createPath (p); | |||||
Rectangle<float> bounds (p.getBounds()); | |||||
if (! p.isEmpty()) | |||||
y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom()); | |||||
} | |||||
float median = y[y.size() / 2]; | |||||
float total = 0; | |||||
int num = 0; | |||||
for (int i = 0; i < y.size(); ++i) | |||||
{ | |||||
if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight) | |||||
{ | |||||
total += y.getUnchecked(i); | |||||
++num; | |||||
} | |||||
} | |||||
return num < 4 ? 0.0f : total / (num * (float) standardHeight); | |||||
} | |||||
enum { standardHeight = 100 }; | |||||
float top, middle, bottom; | |||||
}; | |||||
void Typeface::applyVerticalHintingTransform (float fontSize, Path& path) | |||||
{ | |||||
if (fontSize > 3.0f && fontSize < 25.0f) | |||||
{ | |||||
ScopedLock sl (hintingLock); | |||||
if (hintingParams == nullptr) | |||||
hintingParams = new HintingParams (*this); | |||||
return hintingParams->applyVerticalHintingTransform (fontSize, path); | |||||
} | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,161 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
A typeface represents a size-independent font. | |||||
This base class is abstract, but calling createSystemTypefaceFor() will return | |||||
a platform-specific subclass that can be used. | |||||
The CustomTypeface subclass allow you to build your own typeface, and to | |||||
load and save it in the Juce typeface format. | |||||
Normally you should never need to deal directly with Typeface objects - the Font | |||||
class does everything you typically need for rendering text. | |||||
@see CustomTypeface, Font | |||||
*/ | |||||
class JUCE_API Typeface : public ReferenceCountedObject | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** A handy typedef for a pointer to a typeface. */ | |||||
typedef ReferenceCountedObjectPtr<Typeface> Ptr; | |||||
//============================================================================== | |||||
/** Returns the font family of the typeface. | |||||
@see Font::getTypefaceName | |||||
*/ | |||||
const String& getName() const noexcept { return name; } | |||||
//============================================================================== | |||||
/** Returns the font style of the typeface. | |||||
@see Font::getTypefaceStyle | |||||
*/ | |||||
const String& getStyle() const noexcept { return style; } | |||||
//============================================================================== | |||||
/** Creates a new system typeface. */ | |||||
static Ptr createSystemTypefaceFor (const Font& font); | |||||
/** Attempts to create a font from some raw font file data (e.g. a TTF or OTF file image). | |||||
The system will take its own internal copy of the data, so you can free the block once | |||||
this method has returned. | |||||
*/ | |||||
static Ptr createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize); | |||||
//============================================================================== | |||||
/** Destructor. */ | |||||
virtual ~Typeface(); | |||||
/** Returns true if this typeface can be used to render the specified font. | |||||
When called, the font will already have been checked to make sure that its name and | |||||
style flags match the typeface. | |||||
*/ | |||||
virtual bool isSuitableForFont (const Font&) const { return true; } | |||||
/** Returns the ascent of the font, as a proportion of its height. | |||||
The height is considered to always be normalised as 1.0, so this will be a | |||||
value less that 1.0, indicating the proportion of the font that lies above | |||||
its baseline. | |||||
*/ | |||||
virtual float getAscent() const = 0; | |||||
/** Returns the descent of the font, as a proportion of its height. | |||||
The height is considered to always be normalised as 1.0, so this will be a | |||||
value less that 1.0, indicating the proportion of the font that lies below | |||||
its baseline. | |||||
*/ | |||||
virtual float getDescent() const = 0; | |||||
/** Returns the value by which you should multiply a juce font-height value to | |||||
convert it to the equivalent point-size. | |||||
*/ | |||||
virtual float getHeightToPointsFactor() const = 0; | |||||
/** Measures the width of a line of text. | |||||
The distance returned is based on the font having an normalised height of 1.0. | |||||
You should never need to call this directly! Use Font::getStringWidth() instead! | |||||
*/ | |||||
virtual float getStringWidth (const String& text) = 0; | |||||
/** Converts a line of text into its glyph numbers and their positions. | |||||
The distances returned are based on the font having an normalised height of 1.0. | |||||
You should never need to call this directly! Use Font::getGlyphPositions() instead! | |||||
*/ | |||||
virtual void getGlyphPositions (const String& text, Array <int>& glyphs, Array<float>& xOffsets) = 0; | |||||
/** Returns the outline for a glyph. | |||||
The path returned will be normalised to a font height of 1.0. | |||||
*/ | |||||
virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0; | |||||
/** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */ | |||||
virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight); | |||||
/** Returns true if the typeface uses hinting. */ | |||||
virtual bool isHinted() const { return false; } | |||||
//============================================================================== | |||||
/** Changes the number of fonts that are cached in memory. */ | |||||
static void setTypefaceCacheSize (int numFontsToCache); | |||||
/** Clears any fonts that are currently cached in memory. */ | |||||
static void clearTypefaceCache(); | |||||
/** On some platforms, this allows a specific path to be scanned. | |||||
Currently only available when using FreeType. | |||||
*/ | |||||
static void scanFolderForFonts (const File& folder); | |||||
/** Makes an attempt at performing a good overall distortion that will scale a font of | |||||
the given size to align vertically with the pixel grid. The path should be an unscaled | |||||
(i.e. normalised to height of 1.0) path for a glyph. | |||||
*/ | |||||
void applyVerticalHintingTransform (float fontHeight, Path& path); | |||||
protected: | |||||
//============================================================================== | |||||
String name, style; | |||||
Typeface (const String& name, const String& style) noexcept; | |||||
static Ptr getFallbackTypeface(); | |||||
private: | |||||
struct HintingParams; | |||||
friend struct ContainerDeletePolicy<HintingParams>; | |||||
ScopedPointer<HintingParams> hintingParams; | |||||
CriticalSection hintingLock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface) | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,269 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
AffineTransform::AffineTransform() noexcept | |||||
: mat00 (1.0f), mat01 (0), mat02 (0), | |||||
mat10 (0), mat11 (1.0f), mat12 (0) | |||||
{ | |||||
} | |||||
AffineTransform::AffineTransform (const AffineTransform& other) noexcept | |||||
: mat00 (other.mat00), mat01 (other.mat01), mat02 (other.mat02), | |||||
mat10 (other.mat10), mat11 (other.mat11), mat12 (other.mat12) | |||||
{ | |||||
} | |||||
AffineTransform::AffineTransform (const float m00, const float m01, const float m02, | |||||
const float m10, const float m11, const float m12) noexcept | |||||
: mat00 (m00), mat01 (m01), mat02 (m02), | |||||
mat10 (m10), mat11 (m11), mat12 (m12) | |||||
{ | |||||
} | |||||
AffineTransform& AffineTransform::operator= (const AffineTransform& other) noexcept | |||||
{ | |||||
mat00 = other.mat00; | |||||
mat01 = other.mat01; | |||||
mat02 = other.mat02; | |||||
mat10 = other.mat10; | |||||
mat11 = other.mat11; | |||||
mat12 = other.mat12; | |||||
return *this; | |||||
} | |||||
bool AffineTransform::operator== (const AffineTransform& other) const noexcept | |||||
{ | |||||
return mat00 == other.mat00 | |||||
&& mat01 == other.mat01 | |||||
&& mat02 == other.mat02 | |||||
&& mat10 == other.mat10 | |||||
&& mat11 == other.mat11 | |||||
&& mat12 == other.mat12; | |||||
} | |||||
bool AffineTransform::operator!= (const AffineTransform& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
//============================================================================== | |||||
bool AffineTransform::isIdentity() const noexcept | |||||
{ | |||||
return (mat01 == 0.0f) | |||||
&& (mat02 == 0.0f) | |||||
&& (mat10 == 0.0f) | |||||
&& (mat12 == 0.0f) | |||||
&& (mat00 == 1.0f) | |||||
&& (mat11 == 1.0f); | |||||
} | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
const AffineTransform AffineTransform::identity; | |||||
#endif | |||||
//============================================================================== | |||||
AffineTransform AffineTransform::followedBy (const AffineTransform& other) const noexcept | |||||
{ | |||||
return AffineTransform (other.mat00 * mat00 + other.mat01 * mat10, | |||||
other.mat00 * mat01 + other.mat01 * mat11, | |||||
other.mat00 * mat02 + other.mat01 * mat12 + other.mat02, | |||||
other.mat10 * mat00 + other.mat11 * mat10, | |||||
other.mat10 * mat01 + other.mat11 * mat11, | |||||
other.mat10 * mat02 + other.mat11 * mat12 + other.mat12); | |||||
} | |||||
AffineTransform AffineTransform::translated (const float dx, const float dy) const noexcept | |||||
{ | |||||
return AffineTransform (mat00, mat01, mat02 + dx, | |||||
mat10, mat11, mat12 + dy); | |||||
} | |||||
AffineTransform AffineTransform::translation (const float dx, const float dy) noexcept | |||||
{ | |||||
return AffineTransform (1.0f, 0, dx, | |||||
0, 1.0f, dy); | |||||
} | |||||
AffineTransform AffineTransform::withAbsoluteTranslation (const float tx, const float ty) const noexcept | |||||
{ | |||||
return AffineTransform (mat00, mat01, tx, | |||||
mat10, mat11, ty); | |||||
} | |||||
AffineTransform AffineTransform::rotated (const float rad) const noexcept | |||||
{ | |||||
const float cosRad = std::cos (rad); | |||||
const float sinRad = std::sin (rad); | |||||
return AffineTransform (cosRad * mat00 + -sinRad * mat10, | |||||
cosRad * mat01 + -sinRad * mat11, | |||||
cosRad * mat02 + -sinRad * mat12, | |||||
sinRad * mat00 + cosRad * mat10, | |||||
sinRad * mat01 + cosRad * mat11, | |||||
sinRad * mat02 + cosRad * mat12); | |||||
} | |||||
AffineTransform AffineTransform::rotation (const float rad) noexcept | |||||
{ | |||||
const float cosRad = std::cos (rad); | |||||
const float sinRad = std::sin (rad); | |||||
return AffineTransform (cosRad, -sinRad, 0, | |||||
sinRad, cosRad, 0); | |||||
} | |||||
AffineTransform AffineTransform::rotation (const float rad, const float pivotX, const float pivotY) noexcept | |||||
{ | |||||
const float cosRad = std::cos (rad); | |||||
const float sinRad = std::sin (rad); | |||||
return AffineTransform (cosRad, -sinRad, -cosRad * pivotX + sinRad * pivotY + pivotX, | |||||
sinRad, cosRad, -sinRad * pivotX + -cosRad * pivotY + pivotY); | |||||
} | |||||
AffineTransform AffineTransform::rotated (const float angle, const float pivotX, const float pivotY) const noexcept | |||||
{ | |||||
return followedBy (rotation (angle, pivotX, pivotY)); | |||||
} | |||||
AffineTransform AffineTransform::scaled (const float factorX, const float factorY) const noexcept | |||||
{ | |||||
return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02, | |||||
factorY * mat10, factorY * mat11, factorY * mat12); | |||||
} | |||||
AffineTransform AffineTransform::scaled (const float factor) const noexcept | |||||
{ | |||||
return AffineTransform (factor * mat00, factor * mat01, factor * mat02, | |||||
factor * mat10, factor * mat11, factor * mat12); | |||||
} | |||||
AffineTransform AffineTransform::scale (const float factorX, const float factorY) noexcept | |||||
{ | |||||
return AffineTransform (factorX, 0, 0, 0, factorY, 0); | |||||
} | |||||
AffineTransform AffineTransform::scale (const float factor) noexcept | |||||
{ | |||||
return AffineTransform (factor, 0, 0, 0, factor, 0); | |||||
} | |||||
AffineTransform AffineTransform::scaled (const float factorX, const float factorY, | |||||
const float pivotX, const float pivotY) const noexcept | |||||
{ | |||||
return AffineTransform (factorX * mat00, factorX * mat01, factorX * mat02 + pivotX * (1.0f - factorX), | |||||
factorY * mat10, factorY * mat11, factorY * mat12 + pivotY * (1.0f - factorY)); | |||||
} | |||||
AffineTransform AffineTransform::scale (const float factorX, const float factorY, | |||||
const float pivotX, const float pivotY) noexcept | |||||
{ | |||||
return AffineTransform (factorX, 0, pivotX * (1.0f - factorX), | |||||
0, factorY, pivotY * (1.0f - factorY)); | |||||
} | |||||
AffineTransform AffineTransform::shear (float shearX, float shearY) noexcept | |||||
{ | |||||
return AffineTransform (1.0f, shearX, 0, | |||||
shearY, 1.0f, 0); | |||||
} | |||||
AffineTransform AffineTransform::sheared (const float shearX, const float shearY) const noexcept | |||||
{ | |||||
return AffineTransform (mat00 + shearX * mat10, | |||||
mat01 + shearX * mat11, | |||||
mat02 + shearX * mat12, | |||||
mat10 + shearY * mat00, | |||||
mat11 + shearY * mat01, | |||||
mat12 + shearY * mat02); | |||||
} | |||||
AffineTransform AffineTransform::verticalFlip (const float height) noexcept | |||||
{ | |||||
return AffineTransform (1.0f, 0, 0, 0, -1.0f, height); | |||||
} | |||||
AffineTransform AffineTransform::inverted() const noexcept | |||||
{ | |||||
double determinant = (mat00 * mat11 - mat10 * mat01); | |||||
if (determinant != 0.0) | |||||
{ | |||||
determinant = 1.0 / determinant; | |||||
const float dst00 = (float) ( mat11 * determinant); | |||||
const float dst10 = (float) (-mat10 * determinant); | |||||
const float dst01 = (float) (-mat01 * determinant); | |||||
const float dst11 = (float) ( mat00 * determinant); | |||||
return AffineTransform (dst00, dst01, -mat02 * dst00 - mat12 * dst01, | |||||
dst10, dst11, -mat02 * dst10 - mat12 * dst11); | |||||
} | |||||
// singularity.. | |||||
return *this; | |||||
} | |||||
bool AffineTransform::isSingularity() const noexcept | |||||
{ | |||||
return (mat00 * mat11 - mat10 * mat01) == 0.0f; | |||||
} | |||||
AffineTransform AffineTransform::fromTargetPoints (const float x00, const float y00, | |||||
const float x10, const float y10, | |||||
const float x01, const float y01) noexcept | |||||
{ | |||||
return AffineTransform (x10 - x00, x01 - x00, x00, | |||||
y10 - y00, y01 - y00, y00); | |||||
} | |||||
AffineTransform AffineTransform::fromTargetPoints (const float sx1, const float sy1, const float tx1, const float ty1, | |||||
const float sx2, const float sy2, const float tx2, const float ty2, | |||||
const float sx3, const float sy3, const float tx3, const float ty3) noexcept | |||||
{ | |||||
return fromTargetPoints (sx1, sy1, sx2, sy2, sx3, sy3) | |||||
.inverted() | |||||
.followedBy (fromTargetPoints (tx1, ty1, tx2, ty2, tx3, ty3)); | |||||
} | |||||
bool AffineTransform::isOnlyTranslation() const noexcept | |||||
{ | |||||
return (mat01 == 0.0f) | |||||
&& (mat10 == 0.0f) | |||||
&& (mat00 == 1.0f) | |||||
&& (mat11 == 1.0f); | |||||
} | |||||
float AffineTransform::getScaleFactor() const noexcept | |||||
{ | |||||
return (std::abs (mat00) + std::abs (mat11)) / 2.0f; | |||||
} | |||||
} // namespace juce |
@@ -0,0 +1,279 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Represents a 2D affine-transformation matrix. | |||||
An affine transformation is a transformation such as a rotation, scale, shear, | |||||
resize or translation. | |||||
These are used for various 2D transformation tasks, e.g. with Path objects. | |||||
@see Path, Point, Line | |||||
*/ | |||||
class JUCE_API AffineTransform | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an identity transform. */ | |||||
AffineTransform() noexcept; | |||||
/** Creates a copy of another transform. */ | |||||
AffineTransform (const AffineTransform& other) noexcept; | |||||
/** Creates a transform from a set of raw matrix values. | |||||
The resulting matrix is: | |||||
(mat00 mat01 mat02) | |||||
(mat10 mat11 mat12) | |||||
( 0 0 1 ) | |||||
*/ | |||||
AffineTransform (float mat00, float mat01, float mat02, | |||||
float mat10, float mat11, float mat12) noexcept; | |||||
/** Copies from another AffineTransform object */ | |||||
AffineTransform& operator= (const AffineTransform& other) noexcept; | |||||
/** Compares two transforms. */ | |||||
bool operator== (const AffineTransform& other) const noexcept; | |||||
/** Compares two transforms. */ | |||||
bool operator!= (const AffineTransform& other) const noexcept; | |||||
#if JUCE_ALLOW_STATIC_NULL_VARIABLES | |||||
/** A ready-to-use identity transform. | |||||
Note that you should always avoid using a static variable like this, and | |||||
prefer AffineTransform() or {} if you need a default-constructed instance. | |||||
*/ | |||||
static const AffineTransform identity; | |||||
#endif | |||||
//============================================================================== | |||||
/** Transforms a 2D coordinate using this matrix. */ | |||||
template <typename ValueType> | |||||
void transformPoint (ValueType& x, ValueType& y) const noexcept | |||||
{ | |||||
const ValueType oldX = x; | |||||
x = static_cast<ValueType> (mat00 * oldX + mat01 * y + mat02); | |||||
y = static_cast<ValueType> (mat10 * oldX + mat11 * y + mat12); | |||||
} | |||||
/** Transforms two 2D coordinates using this matrix. | |||||
This is just a shortcut for calling transformPoint() on each of these pairs of | |||||
coordinates in turn. (And putting all the calculations into one function hopefully | |||||
also gives the compiler a bit more scope for pipelining it). | |||||
*/ | |||||
template <typename ValueType> | |||||
void transformPoints (ValueType& x1, ValueType& y1, | |||||
ValueType& x2, ValueType& y2) const noexcept | |||||
{ | |||||
const ValueType oldX1 = x1, oldX2 = x2; | |||||
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02); | |||||
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12); | |||||
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02); | |||||
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12); | |||||
} | |||||
/** Transforms three 2D coordinates using this matrix. | |||||
This is just a shortcut for calling transformPoint() on each of these pairs of | |||||
coordinates in turn. (And putting all the calculations into one function hopefully | |||||
also gives the compiler a bit more scope for pipelining it). | |||||
*/ | |||||
template <typename ValueType> | |||||
void transformPoints (ValueType& x1, ValueType& y1, | |||||
ValueType& x2, ValueType& y2, | |||||
ValueType& x3, ValueType& y3) const noexcept | |||||
{ | |||||
const ValueType oldX1 = x1, oldX2 = x2, oldX3 = x3; | |||||
x1 = static_cast<ValueType> (mat00 * oldX1 + mat01 * y1 + mat02); | |||||
y1 = static_cast<ValueType> (mat10 * oldX1 + mat11 * y1 + mat12); | |||||
x2 = static_cast<ValueType> (mat00 * oldX2 + mat01 * y2 + mat02); | |||||
y2 = static_cast<ValueType> (mat10 * oldX2 + mat11 * y2 + mat12); | |||||
x3 = static_cast<ValueType> (mat00 * oldX3 + mat01 * y3 + mat02); | |||||
y3 = static_cast<ValueType> (mat10 * oldX3 + mat11 * y3 + mat12); | |||||
} | |||||
//============================================================================== | |||||
/** Returns a new transform which is the same as this one followed by a translation. */ | |||||
AffineTransform translated (float deltaX, | |||||
float deltaY) const noexcept; | |||||
/** Returns a new transform which is the same as this one followed by a translation. */ | |||||
template <typename PointType> | |||||
AffineTransform translated (PointType delta) const noexcept | |||||
{ | |||||
return translated ((float) delta.x, (float) delta.y); | |||||
} | |||||
/** Returns a new transform which is a translation. */ | |||||
static AffineTransform translation (float deltaX, | |||||
float deltaY) noexcept; | |||||
/** Returns a new transform which is a translation. */ | |||||
template <typename PointType> | |||||
static AffineTransform translation (PointType delta) noexcept | |||||
{ | |||||
return translation ((float) delta.x, (float) delta.y); | |||||
} | |||||
/** Returns a copy of this transform with the specified translation matrix values. */ | |||||
AffineTransform withAbsoluteTranslation (float translationX, | |||||
float translationY) const noexcept; | |||||
/** Returns a transform which is the same as this one followed by a rotation. | |||||
The rotation is specified by a number of radians to rotate clockwise, centred around | |||||
the origin (0, 0). | |||||
*/ | |||||
AffineTransform rotated (float angleInRadians) const noexcept; | |||||
/** Returns a transform which is the same as this one followed by a rotation about a given point. | |||||
The rotation is specified by a number of radians to rotate clockwise, centred around | |||||
the coordinates passed in. | |||||
*/ | |||||
AffineTransform rotated (float angleInRadians, | |||||
float pivotX, | |||||
float pivotY) const noexcept; | |||||
/** Returns a new transform which is a rotation about (0, 0). */ | |||||
static AffineTransform rotation (float angleInRadians) noexcept; | |||||
/** Returns a new transform which is a rotation about a given point. */ | |||||
static AffineTransform rotation (float angleInRadians, | |||||
float pivotX, | |||||
float pivotY) noexcept; | |||||
/** Returns a transform which is the same as this one followed by a re-scaling. | |||||
The scaling is centred around the origin (0, 0). | |||||
*/ | |||||
AffineTransform scaled (float factorX, | |||||
float factorY) const noexcept; | |||||
/** Returns a transform which is the same as this one followed by a re-scaling. | |||||
The scaling is centred around the origin (0, 0). | |||||
*/ | |||||
AffineTransform scaled (float factor) const noexcept; | |||||
/** Returns a transform which is the same as this one followed by a re-scaling. | |||||
The scaling is centred around the origin provided. | |||||
*/ | |||||
AffineTransform scaled (float factorX, float factorY, | |||||
float pivotX, float pivotY) const noexcept; | |||||
/** Returns a new transform which is a re-scale about the origin. */ | |||||
static AffineTransform scale (float factorX, | |||||
float factorY) noexcept; | |||||
/** Returns a new transform which is a re-scale about the origin. */ | |||||
static AffineTransform scale (float factor) noexcept; | |||||
/** Returns a new transform which is a re-scale centred around the point provided. */ | |||||
static AffineTransform scale (float factorX, float factorY, | |||||
float pivotX, float pivotY) noexcept; | |||||
/** Returns a transform which is the same as this one followed by a shear. | |||||
The shear is centred around the origin (0, 0). | |||||
*/ | |||||
AffineTransform sheared (float shearX, float shearY) const noexcept; | |||||
/** Returns a shear transform, centred around the origin (0, 0). */ | |||||
static AffineTransform shear (float shearX, float shearY) noexcept; | |||||
/** Returns a transform that will flip coordinates vertically within a window of the given height. | |||||
This is handy for converting between upside-down coordinate systems such as OpenGL or CoreGraphics. | |||||
*/ | |||||
static AffineTransform verticalFlip (float height) noexcept; | |||||
/** Returns a matrix which is the inverse operation of this one. | |||||
Some matrices don't have an inverse - in this case, the method will just return | |||||
an identity transform. | |||||
*/ | |||||
AffineTransform inverted() const noexcept; | |||||
/** Returns the transform that will map three known points onto three coordinates | |||||
that are supplied. | |||||
This returns the transform that will transform (0, 0) into (x00, y00), | |||||
(1, 0) to (x10, y10), and (0, 1) to (x01, y01). | |||||
*/ | |||||
static AffineTransform fromTargetPoints (float x00, float y00, | |||||
float x10, float y10, | |||||
float x01, float y01) noexcept; | |||||
/** Returns the transform that will map three specified points onto three target points. */ | |||||
static AffineTransform fromTargetPoints (float sourceX1, float sourceY1, float targetX1, float targetY1, | |||||
float sourceX2, float sourceY2, float targetX2, float targetY2, | |||||
float sourceX3, float sourceY3, float targetX3, float targetY3) noexcept; | |||||
//============================================================================== | |||||
/** Returns the result of concatenating another transformation after this one. */ | |||||
AffineTransform followedBy (const AffineTransform& other) const noexcept; | |||||
/** Returns true if this transform has no effect on points. */ | |||||
bool isIdentity() const noexcept; | |||||
/** Returns true if this transform maps to a singularity - i.e. if it has no inverse. */ | |||||
bool isSingularity() const noexcept; | |||||
/** Returns true if the transform only translates, and doesn't scale or rotate the | |||||
points. */ | |||||
bool isOnlyTranslation() const noexcept; | |||||
/** If this transform is only a translation, this returns the X offset. | |||||
@see isOnlyTranslation | |||||
*/ | |||||
float getTranslationX() const noexcept { return mat02; } | |||||
/** If this transform is only a translation, this returns the X offset. | |||||
@see isOnlyTranslation | |||||
*/ | |||||
float getTranslationY() const noexcept { return mat12; } | |||||
/** Returns the approximate scale factor by which lengths will be transformed. | |||||
Obviously a length may be scaled by entirely different amounts depending on its | |||||
direction, so this is only appropriate as a rough guide. | |||||
*/ | |||||
float getScaleFactor() const noexcept; | |||||
//============================================================================== | |||||
/* The transform matrix is: | |||||
(mat00 mat01 mat02) | |||||
(mat10 mat11 mat12) | |||||
( 0 0 1 ) | |||||
*/ | |||||
float mat00, mat01, mat02; | |||||
float mat10, mat11, mat12; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,153 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
/** | |||||
Specifies a set of gaps to be left around the sides of a rectangle. | |||||
This is basically the size of the spaces at the top, bottom, left and right of | |||||
a rectangle. It's used by various component classes to specify borders. | |||||
@see Rectangle | |||||
*/ | |||||
template <typename ValueType> | |||||
class BorderSize | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a null border. | |||||
All sizes are left as 0. | |||||
*/ | |||||
BorderSize() noexcept | |||||
: top(), left(), bottom(), right() | |||||
{ | |||||
} | |||||
/** Creates a copy of another border. */ | |||||
BorderSize (const BorderSize& other) noexcept | |||||
: top (other.top), left (other.left), bottom (other.bottom), right (other.right) | |||||
{ | |||||
} | |||||
/** Creates a border with the given gaps. */ | |||||
BorderSize (ValueType topGap, ValueType leftGap, ValueType bottomGap, ValueType rightGap) noexcept | |||||
: top (topGap), left (leftGap), bottom (bottomGap), right (rightGap) | |||||
{ | |||||
} | |||||
/** Creates a border with the given gap on all sides. */ | |||||
explicit BorderSize (ValueType allGaps) noexcept | |||||
: top (allGaps), left (allGaps), bottom (allGaps), right (allGaps) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
/** Returns the gap that should be left at the top of the region. */ | |||||
ValueType getTop() const noexcept { return top; } | |||||
/** Returns the gap that should be left at the top of the region. */ | |||||
ValueType getLeft() const noexcept { return left; } | |||||
/** Returns the gap that should be left at the top of the region. */ | |||||
ValueType getBottom() const noexcept { return bottom; } | |||||
/** Returns the gap that should be left at the top of the region. */ | |||||
ValueType getRight() const noexcept { return right; } | |||||
/** Returns the sum of the top and bottom gaps. */ | |||||
ValueType getTopAndBottom() const noexcept { return top + bottom; } | |||||
/** Returns the sum of the left and right gaps. */ | |||||
ValueType getLeftAndRight() const noexcept { return left + right; } | |||||
/** Returns true if this border has no thickness along any edge. */ | |||||
bool isEmpty() const noexcept { return left + right + top + bottom == ValueType(); } | |||||
//============================================================================== | |||||
/** Changes the top gap. */ | |||||
void setTop (ValueType newTopGap) noexcept { top = newTopGap; } | |||||
/** Changes the left gap. */ | |||||
void setLeft (ValueType newLeftGap) noexcept { left = newLeftGap; } | |||||
/** Changes the bottom gap. */ | |||||
void setBottom (ValueType newBottomGap) noexcept { bottom = newBottomGap; } | |||||
/** Changes the right gap. */ | |||||
void setRight (ValueType newRightGap) noexcept { right = newRightGap; } | |||||
//============================================================================== | |||||
/** Returns a rectangle with these borders removed from it. */ | |||||
Rectangle<ValueType> subtractedFrom (const Rectangle<ValueType>& original) const noexcept | |||||
{ | |||||
return Rectangle<ValueType> (original.getX() + left, | |||||
original.getY() + top, | |||||
original.getWidth() - (left + right), | |||||
original.getHeight() - (top + bottom)); | |||||
} | |||||
/** Removes this border from a given rectangle. */ | |||||
void subtractFrom (Rectangle<ValueType>& rectangle) const noexcept | |||||
{ | |||||
rectangle = subtractedFrom (rectangle); | |||||
} | |||||
/** Returns a rectangle with these borders added around it. */ | |||||
Rectangle<ValueType> addedTo (const Rectangle<ValueType>& original) const noexcept | |||||
{ | |||||
return Rectangle<ValueType> (original.getX() - left, | |||||
original.getY() - top, | |||||
original.getWidth() + (left + right), | |||||
original.getHeight() + (top + bottom)); | |||||
} | |||||
/** Adds this border around a given rectangle. */ | |||||
void addTo (Rectangle<ValueType>& rectangle) const noexcept | |||||
{ | |||||
rectangle = addedTo (rectangle); | |||||
} | |||||
//============================================================================== | |||||
bool operator== (const BorderSize& other) const noexcept | |||||
{ | |||||
return top == other.top && left == other.left && bottom == other.bottom && right == other.right; | |||||
} | |||||
bool operator!= (const BorderSize& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
ValueType top, left, bottom, right; | |||||
}; | |||||
} // namespace juce |
@@ -0,0 +1,838 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
namespace juce | |||||
{ | |||||
const int juce_edgeTableDefaultEdgesPerLine = 32; | |||||
//============================================================================== | |||||
EdgeTable::EdgeTable (const Rectangle<int>& area, | |||||
const Path& path, const AffineTransform& transform) | |||||
: bounds (area), | |||||
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||||
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||||
needToCheckEmptiness (true) | |||||
{ | |||||
allocate(); | |||||
int* t = table; | |||||
for (int i = bounds.getHeight(); --i >= 0;) | |||||
{ | |||||
*t = 0; | |||||
t += lineStrideElements; | |||||
} | |||||
const int leftLimit = bounds.getX() << 8; | |||||
const int topLimit = bounds.getY() << 8; | |||||
const int rightLimit = bounds.getRight() << 8; | |||||
const int heightLimit = bounds.getHeight() << 8; | |||||
PathFlatteningIterator iter (path, transform); | |||||
while (iter.next()) | |||||
{ | |||||
int y1 = roundToInt (iter.y1 * 256.0f); | |||||
int y2 = roundToInt (iter.y2 * 256.0f); | |||||
if (y1 != y2) | |||||
{ | |||||
y1 -= topLimit; | |||||
y2 -= topLimit; | |||||
const int startY = y1; | |||||
int direction = -1; | |||||
if (y1 > y2) | |||||
{ | |||||
std::swap (y1, y2); | |||||
direction = 1; | |||||
} | |||||
if (y1 < 0) | |||||
y1 = 0; | |||||
if (y2 > heightLimit) | |||||
y2 = heightLimit; | |||||
if (y1 < y2) | |||||
{ | |||||
const double startX = 256.0f * iter.x1; | |||||
const double multiplier = (iter.x2 - iter.x1) / (iter.y2 - iter.y1); | |||||
const int stepSize = jlimit (1, 256, 256 / (1 + (int) std::abs (multiplier))); | |||||
do | |||||
{ | |||||
const int step = jmin (stepSize, y2 - y1, 256 - (y1 & 255)); | |||||
int x = roundToInt (startX + multiplier * ((y1 + (step >> 1)) - startY)); | |||||
if (x < leftLimit) | |||||
x = leftLimit; | |||||
else if (x >= rightLimit) | |||||
x = rightLimit - 1; | |||||
addEdgePoint (x, y1 >> 8, direction * step); | |||||
y1 += step; | |||||
} | |||||
while (y1 < y2); | |||||
} | |||||
} | |||||
} | |||||
sanitiseLevels (path.isUsingNonZeroWinding()); | |||||
} | |||||
EdgeTable::EdgeTable (const Rectangle<int>& rectangleToAdd) | |||||
: bounds (rectangleToAdd), | |||||
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||||
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||||
needToCheckEmptiness (true) | |||||
{ | |||||
allocate(); | |||||
table[0] = 0; | |||||
const int x1 = rectangleToAdd.getX() << 8; | |||||
const int x2 = rectangleToAdd.getRight() << 8; | |||||
int* t = table; | |||||
for (int i = rectangleToAdd.getHeight(); --i >= 0;) | |||||
{ | |||||
t[0] = 2; | |||||
t[1] = x1; | |||||
t[2] = 255; | |||||
t[3] = x2; | |||||
t[4] = 0; | |||||
t += lineStrideElements; | |||||
} | |||||
} | |||||
EdgeTable::EdgeTable (const RectangleList<int>& rectanglesToAdd) | |||||
: bounds (rectanglesToAdd.getBounds()), | |||||
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||||
lineStrideElements (juce_edgeTableDefaultEdgesPerLine * 2 + 1), | |||||
needToCheckEmptiness (true) | |||||
{ | |||||
allocate(); | |||||
clearLineSizes(); | |||||
for (auto& r : rectanglesToAdd) | |||||
{ | |||||
const int x1 = r.getX() << 8; | |||||
const int x2 = r.getRight() << 8; | |||||
int y = r.getY() - bounds.getY(); | |||||
for (int j = r.getHeight(); --j >= 0;) | |||||
addEdgePointPair (x1, x2, y++, 255); | |||||
} | |||||
sanitiseLevels (true); | |||||
} | |||||
EdgeTable::EdgeTable (const RectangleList<float>& rectanglesToAdd) | |||||
: bounds (rectanglesToAdd.getBounds().getSmallestIntegerContainer()), | |||||
maxEdgesPerLine (rectanglesToAdd.getNumRectangles() * 2), | |||||
lineStrideElements (rectanglesToAdd.getNumRectangles() * 4 + 1), | |||||
needToCheckEmptiness (true) | |||||
{ | |||||
bounds.setHeight (bounds.getHeight() + 1); | |||||
allocate(); | |||||
clearLineSizes(); | |||||
for (auto& r : rectanglesToAdd) | |||||
{ | |||||
const int x1 = roundToInt (r.getX() * 256.0f); | |||||
const int x2 = roundToInt (r.getRight() * 256.0f); | |||||
const int y1 = roundToInt (r.getY() * 256.0f) - (bounds.getY() << 8); | |||||
const int y2 = roundToInt (r.getBottom() * 256.0f) - (bounds.getY() << 8); | |||||
if (x2 <= x1 || y2 <= y1) | |||||
continue; | |||||
int y = y1 >> 8; | |||||
const int lastLine = y2 >> 8; | |||||
if (y == lastLine) | |||||
{ | |||||
addEdgePointPair (x1, x2, y, y2 - y1); | |||||
} | |||||
else | |||||
{ | |||||
addEdgePointPair (x1, x2, y++, 255 - (y1 & 255)); | |||||
while (y < lastLine) | |||||
addEdgePointPair (x1, x2, y++, 255); | |||||
jassert (y < bounds.getHeight()); | |||||
addEdgePointPair (x1, x2, y, y2 & 255); | |||||
} | |||||
} | |||||
sanitiseLevels (true); | |||||
} | |||||
EdgeTable::EdgeTable (const Rectangle<float>& rectangleToAdd) | |||||
: bounds (Rectangle<int> ((int) std::floor (rectangleToAdd.getX()), | |||||
roundToInt (rectangleToAdd.getY() * 256.0f) >> 8, | |||||
2 + (int) rectangleToAdd.getWidth(), | |||||
2 + (int) rectangleToAdd.getHeight())), | |||||
maxEdgesPerLine (juce_edgeTableDefaultEdgesPerLine), | |||||
lineStrideElements ((juce_edgeTableDefaultEdgesPerLine << 1) + 1), | |||||
needToCheckEmptiness (true) | |||||
{ | |||||
jassert (! rectangleToAdd.isEmpty()); | |||||
allocate(); | |||||
table[0] = 0; | |||||
const int x1 = roundToInt (rectangleToAdd.getX() * 256.0f); | |||||
const int x2 = roundToInt (rectangleToAdd.getRight() * 256.0f); | |||||
int y1 = roundToInt (rectangleToAdd.getY() * 256.0f) - (bounds.getY() << 8); | |||||
jassert (y1 < 256); | |||||
int y2 = roundToInt (rectangleToAdd.getBottom() * 256.0f) - (bounds.getY() << 8); | |||||
if (x2 <= x1 || y2 <= y1) | |||||
{ | |||||
bounds.setHeight (0); | |||||
return; | |||||
} | |||||
int lineY = 0; | |||||
int* t = table; | |||||
if ((y1 >> 8) == (y2 >> 8)) | |||||
{ | |||||
t[0] = 2; | |||||
t[1] = x1; | |||||
t[2] = y2 - y1; | |||||
t[3] = x2; | |||||
t[4] = 0; | |||||
++lineY; | |||||
t += lineStrideElements; | |||||
} | |||||
else | |||||
{ | |||||
t[0] = 2; | |||||
t[1] = x1; | |||||
t[2] = 255 - (y1 & 255); | |||||
t[3] = x2; | |||||
t[4] = 0; | |||||
++lineY; | |||||
t += lineStrideElements; | |||||
while (lineY < (y2 >> 8)) | |||||
{ | |||||
t[0] = 2; | |||||
t[1] = x1; | |||||
t[2] = 255; | |||||
t[3] = x2; | |||||
t[4] = 0; | |||||
++lineY; | |||||
t += lineStrideElements; | |||||
} | |||||
jassert (lineY < bounds.getHeight()); | |||||
t[0] = 2; | |||||
t[1] = x1; | |||||
t[2] = y2 & 255; | |||||
t[3] = x2; | |||||
t[4] = 0; | |||||
++lineY; | |||||
t += lineStrideElements; | |||||
} | |||||
while (lineY < bounds.getHeight()) | |||||
{ | |||||
t[0] = 0; | |||||
t += lineStrideElements; | |||||
++lineY; | |||||
} | |||||
} | |||||
EdgeTable::EdgeTable (const EdgeTable& other) | |||||
{ | |||||
operator= (other); | |||||
} | |||||
EdgeTable& EdgeTable::operator= (const EdgeTable& other) | |||||
{ | |||||
bounds = other.bounds; | |||||
maxEdgesPerLine = other.maxEdgesPerLine; | |||||
lineStrideElements = other.lineStrideElements; | |||||
needToCheckEmptiness = other.needToCheckEmptiness; | |||||
allocate(); | |||||
copyEdgeTableData (table, lineStrideElements, other.table, lineStrideElements, bounds.getHeight()); | |||||
return *this; | |||||
} | |||||
EdgeTable::~EdgeTable() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
static size_t getEdgeTableAllocationSize (int lineStride, int height) noexcept | |||||
{ | |||||
// (leave an extra line at the end for use as scratch space) | |||||
return (size_t) (lineStride * (2 + jmax (0, height))); | |||||
} | |||||
void EdgeTable::allocate() | |||||
{ | |||||
table.malloc (getEdgeTableAllocationSize (lineStrideElements, bounds.getHeight())); | |||||
} | |||||
void EdgeTable::clearLineSizes() noexcept | |||||
{ | |||||
int* t = table; | |||||
for (int i = bounds.getHeight(); --i >= 0;) | |||||
{ | |||||
*t = 0; | |||||
t += lineStrideElements; | |||||
} | |||||
} | |||||
void EdgeTable::copyEdgeTableData (int* dest, const int destLineStride, const int* src, const int srcLineStride, int numLines) noexcept | |||||
{ | |||||
while (--numLines >= 0) | |||||
{ | |||||
memcpy (dest, src, (size_t) (src[0] * 2 + 1) * sizeof (int)); | |||||
src += srcLineStride; | |||||
dest += destLineStride; | |||||
} | |||||
} | |||||
void EdgeTable::sanitiseLevels (const bool useNonZeroWinding) noexcept | |||||
{ | |||||
// Convert the table from relative windings to absolute levels.. | |||||
int* lineStart = table; | |||||
for (int y = bounds.getHeight(); --y >= 0;) | |||||
{ | |||||
int num = lineStart[0]; | |||||
if (num > 0) | |||||
{ | |||||
LineItem* items = reinterpret_cast<LineItem*> (lineStart + 1); | |||||
LineItem* const itemsEnd = items + num; | |||||
// sort the X coords | |||||
std::sort (items, itemsEnd); | |||||
const LineItem* src = items; | |||||
int correctedNum = num; | |||||
int level = 0; | |||||
while (src < itemsEnd) | |||||
{ | |||||
level += src->level; | |||||
const int x = src->x; | |||||
++src; | |||||
while (src < itemsEnd && src->x == x) | |||||
{ | |||||
level += src->level; | |||||
++src; | |||||
--correctedNum; | |||||
} | |||||
int corrected = std::abs (level); | |||||
if (corrected >> 8) | |||||
{ | |||||
if (useNonZeroWinding) | |||||
{ | |||||
corrected = 255; | |||||
} | |||||
else | |||||
{ | |||||
corrected &= 511; | |||||
if (corrected >> 8) | |||||
corrected = 511 - corrected; | |||||
} | |||||
} | |||||
items->x = x; | |||||
items->level = corrected; | |||||
++items; | |||||
} | |||||
lineStart[0] = correctedNum; | |||||
(items - 1)->level = 0; // force the last level to 0, just in case something went wrong in creating the table | |||||
} | |||||
lineStart += lineStrideElements; | |||||
} | |||||
} | |||||
void EdgeTable::remapTableForNumEdges (const int newNumEdgesPerLine) | |||||
{ | |||||
if (newNumEdgesPerLine != maxEdgesPerLine) | |||||
{ | |||||
maxEdgesPerLine = newNumEdgesPerLine; | |||||
jassert (bounds.getHeight() > 0); | |||||
const int newLineStrideElements = maxEdgesPerLine * 2 + 1; | |||||
HeapBlock<int> newTable (getEdgeTableAllocationSize (newLineStrideElements, bounds.getHeight())); | |||||
copyEdgeTableData (newTable, newLineStrideElements, table, lineStrideElements, bounds.getHeight()); | |||||
table.swapWith (newTable); | |||||
lineStrideElements = newLineStrideElements; | |||||
} | |||||
} | |||||
void EdgeTable::optimiseTable() | |||||
{ | |||||
int maxLineElements = 0; | |||||
for (int i = bounds.getHeight(); --i >= 0;) | |||||
maxLineElements = jmax (maxLineElements, table [i * lineStrideElements]); | |||||
remapTableForNumEdges (maxLineElements); | |||||
} | |||||
void EdgeTable::addEdgePoint (const int x, const int y, const int winding) | |||||
{ | |||||
jassert (y >= 0 && y < bounds.getHeight()); | |||||
int* line = table + lineStrideElements * y; | |||||
const int numPoints = line[0]; | |||||
if (numPoints >= maxEdgesPerLine) | |||||
{ | |||||
remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); | |||||
jassert (numPoints < maxEdgesPerLine); | |||||
line = table + lineStrideElements * y; | |||||
} | |||||
line[0]++; | |||||
int n = numPoints << 1; | |||||
line [n + 1] = x; | |||||
line [n + 2] = winding; | |||||
} | |||||
void EdgeTable::addEdgePointPair (int x1, int x2, int y, int winding) | |||||
{ | |||||
jassert (y >= 0 && y < bounds.getHeight()); | |||||
int* line = table + lineStrideElements * y; | |||||
const int numPoints = line[0]; | |||||
if (numPoints + 1 >= maxEdgesPerLine) | |||||
{ | |||||
remapTableForNumEdges (maxEdgesPerLine + juce_edgeTableDefaultEdgesPerLine); | |||||
jassert (numPoints < maxEdgesPerLine); | |||||
line = table + lineStrideElements * y; | |||||
} | |||||
line[0] = numPoints + 2; | |||||
line += numPoints << 1; | |||||
line[1] = x1; | |||||
line[2] = winding; | |||||
line[3] = x2; | |||||
line[4] = -winding; | |||||
} | |||||
void EdgeTable::translate (float dx, const int dy) noexcept | |||||
{ | |||||
bounds.translate ((int) std::floor (dx), dy); | |||||
int* lineStart = table; | |||||
const int intDx = (int) (dx * 256.0f); | |||||
for (int i = bounds.getHeight(); --i >= 0;) | |||||
{ | |||||
int* line = lineStart; | |||||
lineStart += lineStrideElements; | |||||
int num = *line++; | |||||
while (--num >= 0) | |||||
{ | |||||
*line += intDx; | |||||
line += 2; | |||||
} | |||||
} | |||||
} | |||||
void EdgeTable::multiplyLevels (float amount) | |||||
{ | |||||
int* lineStart = table; | |||||
const int multiplier = (int) (amount * 256.0f); | |||||
for (int y = 0; y < bounds.getHeight(); ++y) | |||||
{ | |||||
int numPoints = lineStart[0]; | |||||
LineItem* item = reinterpret_cast<LineItem*> (lineStart + 1); | |||||
lineStart += lineStrideElements; | |||||
while (--numPoints > 0) | |||||
{ | |||||
item->level = jmin (255, (item->level * multiplier) >> 8); | |||||
++item; | |||||
} | |||||
} | |||||
} | |||||
void EdgeTable::intersectWithEdgeTableLine (const int y, const int* const otherLine) | |||||
{ | |||||
jassert (y >= 0 && y < bounds.getHeight()); | |||||
int* srcLine = table + lineStrideElements * y; | |||||
int srcNum1 = *srcLine; | |||||
if (srcNum1 == 0) | |||||
return; | |||||
int srcNum2 = *otherLine; | |||||
if (srcNum2 == 0) | |||||
{ | |||||
*srcLine = 0; | |||||
return; | |||||
} | |||||
const int right = bounds.getRight() << 8; | |||||
// optimise for the common case where our line lies entirely within a | |||||
// single pair of points, as happens when clipping to a simple rect. | |||||
if (srcNum2 == 2 && otherLine[2] >= 255) | |||||
{ | |||||
clipEdgeTableLineToRange (srcLine, otherLine[1], jmin (right, otherLine[3])); | |||||
return; | |||||
} | |||||
bool isUsingTempSpace = false; | |||||
const int* src1 = srcLine + 1; | |||||
int x1 = *src1++; | |||||
const int* src2 = otherLine + 1; | |||||
int x2 = *src2++; | |||||
int destIndex = 0, destTotal = 0; | |||||
int level1 = 0, level2 = 0; | |||||
int lastX = std::numeric_limits<int>::min(), lastLevel = 0; | |||||
while (srcNum1 > 0 && srcNum2 > 0) | |||||
{ | |||||
int nextX; | |||||
if (x1 <= x2) | |||||
{ | |||||
if (x1 == x2) | |||||
{ | |||||
level2 = *src2++; | |||||
x2 = *src2++; | |||||
--srcNum2; | |||||
} | |||||
nextX = x1; | |||||
level1 = *src1++; | |||||
x1 = *src1++; | |||||
--srcNum1; | |||||
} | |||||
else | |||||
{ | |||||
nextX = x2; | |||||
level2 = *src2++; | |||||
x2 = *src2++; | |||||
--srcNum2; | |||||
} | |||||
if (nextX > lastX) | |||||
{ | |||||
if (nextX >= right) | |||||
break; | |||||
lastX = nextX; | |||||
const int nextLevel = (level1 * (level2 + 1)) >> 8; | |||||
jassert (isPositiveAndBelow (nextLevel, 256)); | |||||
if (nextLevel != lastLevel) | |||||
{ | |||||
if (destTotal >= maxEdgesPerLine) | |||||
{ | |||||
srcLine[0] = destTotal; | |||||
if (isUsingTempSpace) | |||||
{ | |||||
const size_t tempSize = (size_t) srcNum1 * 2 * sizeof (int); | |||||
int* const oldTemp = static_cast<int*> (alloca (tempSize)); | |||||
memcpy (oldTemp, src1, tempSize); | |||||
remapTableForNumEdges (jmax (256, destTotal * 2)); | |||||
srcLine = table + lineStrideElements * y; | |||||
int* const newTemp = table + lineStrideElements * bounds.getHeight(); | |||||
memcpy (newTemp, oldTemp, tempSize); | |||||
src1 = newTemp; | |||||
} | |||||
else | |||||
{ | |||||
remapTableForNumEdges (jmax (256, destTotal * 2)); | |||||
srcLine = table + lineStrideElements * y; | |||||
} | |||||
} | |||||
++destTotal; | |||||
lastLevel = nextLevel; | |||||
if (! isUsingTempSpace) | |||||
{ | |||||
isUsingTempSpace = true; | |||||
int* const temp = table + lineStrideElements * bounds.getHeight(); | |||||
memcpy (temp, src1, (size_t) srcNum1 * 2 * sizeof (int)); | |||||
src1 = temp; | |||||
} | |||||
srcLine[++destIndex] = nextX; | |||||
srcLine[++destIndex] = nextLevel; | |||||
} | |||||
} | |||||
} | |||||
if (lastLevel > 0) | |||||
{ | |||||
if (destTotal >= maxEdgesPerLine) | |||||
{ | |||||
srcLine[0] = destTotal; | |||||
remapTableForNumEdges (jmax (256, destTotal * 2)); | |||||
srcLine = table + lineStrideElements * y; | |||||
} | |||||
++destTotal; | |||||
srcLine[++destIndex] = right; | |||||
srcLine[++destIndex] = 0; | |||||
} | |||||
srcLine[0] = destTotal; | |||||
} | |||||
void EdgeTable::clipEdgeTableLineToRange (int* dest, const int x1, const int x2) noexcept | |||||
{ | |||||
int* lastItem = dest + (dest[0] * 2 - 1); | |||||
if (x2 < lastItem[0]) | |||||
{ | |||||
if (x2 <= dest[1]) | |||||
{ | |||||
dest[0] = 0; | |||||
return; | |||||
} | |||||
while (x2 < lastItem[-2]) | |||||
{ | |||||
--(dest[0]); | |||||
lastItem -= 2; | |||||
} | |||||
lastItem[0] = x2; | |||||
lastItem[1] = 0; | |||||
} | |||||
if (x1 > dest[1]) | |||||
{ | |||||
while (lastItem[0] > x1) | |||||
lastItem -= 2; | |||||
const int itemsRemoved = (int) (lastItem - (dest + 1)) / 2; | |||||
if (itemsRemoved > 0) | |||||
{ | |||||
dest[0] -= itemsRemoved; | |||||
memmove (dest + 1, lastItem, (size_t) dest[0] * (sizeof (int) * 2)); | |||||
} | |||||
dest[1] = x1; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void EdgeTable::clipToRectangle (const Rectangle<int>& r) | |||||
{ | |||||
const Rectangle<int> clipped (r.getIntersection (bounds)); | |||||
if (clipped.isEmpty()) | |||||
{ | |||||
needToCheckEmptiness = false; | |||||
bounds.setHeight (0); | |||||
} | |||||
else | |||||
{ | |||||
const int top = clipped.getY() - bounds.getY(); | |||||
const int bottom = clipped.getBottom() - bounds.getY(); | |||||
if (bottom < bounds.getHeight()) | |||||
bounds.setHeight (bottom); | |||||
for (int i = top; --i >= 0;) | |||||
table [lineStrideElements * i] = 0; | |||||
if (clipped.getX() > bounds.getX() || clipped.getRight() < bounds.getRight()) | |||||
{ | |||||
const int x1 = clipped.getX() << 8; | |||||
const int x2 = jmin (bounds.getRight(), clipped.getRight()) << 8; | |||||
int* line = table + lineStrideElements * top; | |||||
for (int i = bottom - top; --i >= 0;) | |||||
{ | |||||
if (line[0] != 0) | |||||
clipEdgeTableLineToRange (line, x1, x2); | |||||
line += lineStrideElements; | |||||
} | |||||
} | |||||
needToCheckEmptiness = true; | |||||
} | |||||
} | |||||
void EdgeTable::excludeRectangle (const Rectangle<int>& r) | |||||
{ | |||||
const Rectangle<int> clipped (r.getIntersection (bounds)); | |||||
if (! clipped.isEmpty()) | |||||
{ | |||||
const int top = clipped.getY() - bounds.getY(); | |||||
const int bottom = clipped.getBottom() - bounds.getY(); | |||||
const int rectLine[] = { 4, std::numeric_limits<int>::min(), 255, | |||||
clipped.getX() << 8, 0, | |||||
clipped.getRight() << 8, 255, | |||||
std::numeric_limits<int>::max(), 0 }; | |||||
for (int i = top; i < bottom; ++i) | |||||
intersectWithEdgeTableLine (i, rectLine); | |||||
needToCheckEmptiness = true; | |||||
} | |||||
} | |||||
void EdgeTable::clipToEdgeTable (const EdgeTable& other) | |||||
{ | |||||
const Rectangle<int> clipped (other.bounds.getIntersection (bounds)); | |||||
if (clipped.isEmpty()) | |||||
{ | |||||
needToCheckEmptiness = false; | |||||
bounds.setHeight (0); | |||||
} | |||||
else | |||||
{ | |||||
const int top = clipped.getY() - bounds.getY(); | |||||
const int bottom = clipped.getBottom() - bounds.getY(); | |||||
if (bottom < bounds.getHeight()) | |||||
bounds.setHeight (bottom); | |||||
if (clipped.getRight() < bounds.getRight()) | |||||
bounds.setRight (clipped.getRight()); | |||||
for (int i = 0; i < top; ++i) | |||||
table [lineStrideElements * i] = 0; | |||||
const int* otherLine = other.table + other.lineStrideElements * (clipped.getY() - other.bounds.getY()); | |||||
for (int i = top; i < bottom; ++i) | |||||
{ | |||||
intersectWithEdgeTableLine (i, otherLine); | |||||
otherLine += other.lineStrideElements; | |||||
} | |||||
needToCheckEmptiness = true; | |||||
} | |||||
} | |||||
void EdgeTable::clipLineToMask (int x, int y, const uint8* mask, int maskStride, int numPixels) | |||||
{ | |||||
y -= bounds.getY(); | |||||
if (y < 0 || y >= bounds.getHeight()) | |||||
return; | |||||
needToCheckEmptiness = true; | |||||
if (numPixels <= 0) | |||||
{ | |||||
table [lineStrideElements * y] = 0; | |||||
return; | |||||
} | |||||
int* tempLine = static_cast<int*> (alloca ((size_t) (numPixels * 2 + 4) * sizeof (int))); | |||||
int destIndex = 0, lastLevel = 0; | |||||
while (--numPixels >= 0) | |||||
{ | |||||
const int alpha = *mask; | |||||
mask += maskStride; | |||||
if (alpha != lastLevel) | |||||
{ | |||||
tempLine[++destIndex] = (x << 8); | |||||
tempLine[++destIndex] = alpha; | |||||
lastLevel = alpha; | |||||
} | |||||
++x; | |||||
} | |||||
if (lastLevel > 0) | |||||
{ | |||||
tempLine[++destIndex] = (x << 8); | |||||
tempLine[++destIndex] = 0; | |||||
} | |||||
tempLine[0] = destIndex >> 1; | |||||
intersectWithEdgeTableLine (y, tempLine); | |||||
} | |||||
bool EdgeTable::isEmpty() noexcept | |||||
{ | |||||
if (needToCheckEmptiness) | |||||
{ | |||||
needToCheckEmptiness = false; | |||||
int* t = table; | |||||
for (int i = bounds.getHeight(); --i >= 0;) | |||||
{ | |||||
if (t[0] > 1) | |||||
return false; | |||||
t += lineStrideElements; | |||||
} | |||||
bounds.setHeight (0); | |||||
} | |||||
return bounds.getHeight() == 0; | |||||
} | |||||
} // namespace juce |