Hello,
I've been experimenting with ways to provide LookAndFeel-like customisation of custom components, as I've got a big stack of them that would be worth sharing in a module or two.
One of the ideas I had was based on a slightly-butchered SharedResourcePointer, and is basically this:
/////////////////////////////////////////////////////////////////////////////// /** A pointer to an automatically-created shared instance of the templated type (see SharedResourcePointer). It is possible to override the local instance referenced by a specific OverridableSharedResourcePtr object (if this is null, the default shared instance is used). It is also possible to globally replace the default instance is used by all objects. */ /////////////////////////////////////////////////////////////////////////////// template <typename SharedObjectType> class OverridableSharedResourcePtr { public: /** Creates an instance of the shared object, which can be overridden. If other OverridableSharedResourcePtr objects for this type already exist, then this one will simply point to the same shared object that they are already using. Otherwise, if this is the first pointer object to be created, then a shared object will be created automatically. */ OverridableSharedResourcePtr() { SharedObjectHolder& holder = getSharedObjectHolder(); if (++(holder.refCount) == 1) { holder.resetInstance(); } } /** Destructor. If no other OverridableSharedResourcePtr objects exist, this will also delete the shared object to which it refers. */ ~OverridableSharedResourcePtr() { SharedObjectHolder& holder = getSharedObjectHolder(); if (--(holder.refCount) == 0) { holder.clear (); } } /** Returns the current default shared instance. */ SharedObjectType* getDefaultInstance () const { return getSharedObjectHolder().get(); } /** Replace the current default shared instance. */ void setDefaultInstance (SharedObjectType* object) { getSharedObjectHolder().set (object); } /** Recreates the default shared instance. */ void resetDefaultInstance () { getSharedObjectHolder().resetInstance(); } /** Overrides the local instance referred to by this pointer. */ void setOverrideInstance (SharedObjectType* object, bool takeOwnership) { localOverride.set (object, takeOwnership); } /** Returns the current instance. */ operator SharedObjectType*() const noexcept { return &get(); } /** Returns the instance this pointer refers to (if no local override has been set, this will return the default shared instance. */ SharedObjectType& get() const { if (localOverride.get() != nullptr) { return *localOverride.get(); } return *getDefaultInstance(); } SharedObjectType* operator->() const noexcept { return &get(); } private: struct SharedObjectHolder : public ReferenceCountedObject { juce::SpinLock lock; juce::ScopedPointer<SharedObjectType> sharedInstance; int refCount; void resetInstance () { const juce::SpinLock::ScopedLockType sl (lock); sharedInstance = new SharedObjectType (); } void set (SharedObjectType* newInstance) { const juce::SpinLock::ScopedLockType sl (lock); if (newInstance != sharedInstance) { jassert (newInstance != nullptr); // Must always have a valid default instance! sharedInstance = newInstance; } } void clear () { const juce::SpinLock::ScopedLockType sl (lock); sharedInstance = nullptr; } SharedObjectType* get () const { const juce::SpinLock::ScopedLockType sl (lock); return sharedInstance; } }; static SharedObjectHolder& getSharedObjectHolder() noexcept { static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 }; return *reinterpret_cast<SharedObjectHolder*> (holder); } juce::OptionalScopedPointer< SharedObjectType > localOverride; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OverridableSharedResourcePtr) };
I'm just wondering how dodgy an idea this is, really!
The main limitation (ignoring dangers for the moment) is that there isn't currently any notification when the default instance is changed. This means that it has to go via the SharedObjectHolder for all access to the default instance - though I guess it'd be possible to include a notifier in the SharedObjectHolder as long as it is cleared up properly in clear().
The only real danger would be that the default instance could be changed when something else doesn't expect it to... but I guess it should be obvious that you shouldn't expect that if you're using such an object.
Anyway, this template leads to a really basic starting base like this...
class ComponentStyle { public: ComponentStyle () {}; virtual ~ComponentStyle () {}; virtual void paintComponent (juce::Graphics& g, juce::Component& component) = 0; private: }; /////////////////////////////////////////////////////////////////////////////// template <class StyleClass> class ComponentStyleHandle : public OverridableSharedResourcePtr< StyleClass > { public: void paintComponent (juce::Graphics& g, juce::Component& component) { get().paintComponent (g, component); } private: };
... which then means you can just have something like this:
class MyComp : public Component { public: class Style : public ComponentStyle { public: virtual void paintComponent (Graphics& g, Component& c) { // blah } }; virtual void paint (Graphics& g) { style->paintComponent (g, *this); } ComponentStyleHandle<Style> style; };
The default style is automatically created, and can also be replaced locally on a component quite easily.
You can also replace them globally by doing something like this:
// Create an instance of this at startup, and destroy it on shutdown... class AppStyles { public: ComponentStyleHandle< MyComp::Style > myCompStyle; ComponentStyleHandle< MyOtherComp::Style > myOtherCompStyle; AppStyles () { myCompStyle.setDefaultInstance (new ReplacementMyCompStyle); myOtherCompStyle.setDefaultInstance (new ReplacementMyOtherCompStyle); } };
I'm still experimenting with it, but I thought I'd post it here to see if it sparks any other thoughts/ideas/worries/suggestions.