build(deps): vendor JUCE 7.0.12
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 action that can be performed by an accessible UI element.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
enum class AccessibilityActionType
|
||||
{
|
||||
/** Represents a "press" action.
|
||||
|
||||
This will be called when the user "clicks" the UI element using an
|
||||
accessibility client.
|
||||
*/
|
||||
press,
|
||||
|
||||
/** Represents a "toggle" action.
|
||||
|
||||
This will be called when the user toggles the state of a UI element,
|
||||
for example a toggle button or the selection of a list item.
|
||||
*/
|
||||
toggle,
|
||||
|
||||
/** Indicates that the UI element has received focus.
|
||||
|
||||
This will be called when a UI element receives focus from an accessibility
|
||||
client, or keyboard focus from the application.
|
||||
*/
|
||||
focus,
|
||||
|
||||
/** Represents the user showing a contextual menu for a UI element.
|
||||
|
||||
This will be called for UI elements which expand and collapse to
|
||||
show contextual information or menus, or show a popup.
|
||||
*/
|
||||
showMenu
|
||||
};
|
||||
|
||||
/** A simple wrapper for building a collection of supported accessibility actions
|
||||
and corresponding callbacks for a UI element.
|
||||
|
||||
Pass one of these when constructing an `AccessibilityHandler` to enable users
|
||||
to interact with a UI element via the supported actions.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityActions
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
|
||||
Creates a default AccessibilityActions object with no action callbacks.
|
||||
*/
|
||||
AccessibilityActions() = default;
|
||||
|
||||
/** Adds an action.
|
||||
|
||||
When the user performs this action with an accessibility client
|
||||
`actionCallback` will be called.
|
||||
|
||||
Returns a reference to itself so that several calls can be chained.
|
||||
*/
|
||||
AccessibilityActions& addAction (AccessibilityActionType type,
|
||||
std::function<void()> actionCallback)
|
||||
{
|
||||
actionMap[type] = std::move (actionCallback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Returns true if the specified action is supported. */
|
||||
bool contains (AccessibilityActionType type) const
|
||||
{
|
||||
return actionMap.find (type) != actionMap.end();
|
||||
}
|
||||
|
||||
/** If an action has been registered for the provided action type, invokes the
|
||||
action and returns true. Otherwise, returns false.
|
||||
*/
|
||||
bool invoke (AccessibilityActionType type) const
|
||||
{
|
||||
auto iter = actionMap.find (type);
|
||||
|
||||
if (iter == actionMap.end())
|
||||
return false;
|
||||
|
||||
iter->second();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<AccessibilityActionType, std::function<void()>> actionMap;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 list of events that can be notified to any subscribed accessibility clients.
|
||||
|
||||
To post a notification, call `AccessibilityHandler::notifyAccessibilityEvent`
|
||||
on the associated handler with the appropriate `AccessibilityEvent` type and
|
||||
listening clients will be notified.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
enum class AccessibilityEvent
|
||||
{
|
||||
/** Indicates that the UI element's value has changed.
|
||||
|
||||
This should be called on the handler that implements `AccessibilityValueInterface`
|
||||
for the UI element that has changed.
|
||||
*/
|
||||
valueChanged,
|
||||
|
||||
/** Indicates that the title of the UI element has changed.
|
||||
|
||||
This should be called on the handler whose title has changed.
|
||||
*/
|
||||
titleChanged,
|
||||
|
||||
/** Indicates that the structure of the UI elements has changed in a
|
||||
significant way.
|
||||
|
||||
This should be called on the top-level handler whose structure has changed.
|
||||
*/
|
||||
structureChanged,
|
||||
|
||||
/** Indicates that the selection of a text element has changed.
|
||||
|
||||
This should be called on the handler that implements `AccessibilityTextInterface`
|
||||
for the text element that has changed.
|
||||
*/
|
||||
textSelectionChanged,
|
||||
|
||||
/** Indicates that the visible text of a text element has changed.
|
||||
|
||||
This should be called on the handler that implements `AccessibilityTextInterface`
|
||||
for the text element that has changed.
|
||||
*/
|
||||
textChanged,
|
||||
|
||||
/** Indicates that the selection of rows in a list or table has changed.
|
||||
|
||||
This should be called on the handler that implements `AccessibilityTableInterface`
|
||||
for the UI element that has changed.
|
||||
*/
|
||||
rowSelectionChanged
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
/** The list of available roles for an AccessibilityHandler object.
|
||||
|
||||
When creating a custom AccessibilityHandler you should select the role that
|
||||
best describes the UI element being represented.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
enum class AccessibilityRole
|
||||
{
|
||||
button,
|
||||
toggleButton,
|
||||
radioButton,
|
||||
comboBox,
|
||||
image,
|
||||
slider,
|
||||
label,
|
||||
staticText,
|
||||
editableText,
|
||||
menuItem,
|
||||
menuBar,
|
||||
popupMenu,
|
||||
table,
|
||||
tableHeader,
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
hyperlink,
|
||||
list,
|
||||
listItem,
|
||||
tree,
|
||||
treeItem,
|
||||
progressBar,
|
||||
group,
|
||||
dialogWindow,
|
||||
window,
|
||||
scrollBar,
|
||||
tooltip,
|
||||
splashScreen,
|
||||
ignored,
|
||||
unspecified
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 abstract interface which represents a UI element that supports a cell interface.
|
||||
|
||||
This typically represents a single cell inside of a UI element which implements an
|
||||
AccessibilityTableInterface.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityCellInterface
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AccessibilityCellInterface() = default;
|
||||
|
||||
/** Returns the indentation level for the cell. */
|
||||
virtual int getDisclosureLevel() const = 0;
|
||||
|
||||
/** Returns the AccessibilityHandler of the table which contains the cell. */
|
||||
virtual const AccessibilityHandler* getTableHandler() const = 0;
|
||||
|
||||
/** Returns a list of the accessibility elements that are disclosed by this element, if any. */
|
||||
virtual std::vector<const AccessibilityHandler*> getDisclosedRows() const { return {}; }
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
third_party/JUCE/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityTableInterface.h
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 abstract interface which represents a UI element that supports a table interface.
|
||||
|
||||
Examples of UI elements which typically support a table interface are lists, tables,
|
||||
and trees.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityTableInterface
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AccessibilityTableInterface() = default;
|
||||
|
||||
/** Returns the total number of rows in the table. */
|
||||
virtual int getNumRows() const = 0;
|
||||
|
||||
/** Returns the total number of columns in the table. */
|
||||
virtual int getNumColumns() const = 0;
|
||||
|
||||
/** Returns the AccessibilityHandler for one of the cells in the table, or
|
||||
nullptr if there is no cell at the specified position.
|
||||
*/
|
||||
virtual const AccessibilityHandler* getCellHandler (int row, int column) const = 0;
|
||||
|
||||
/** Returns the AccessibilityHandler for a row in the table, or nullptr if there is
|
||||
no row at this index.
|
||||
|
||||
The row component should have a child component for each column in the table.
|
||||
*/
|
||||
virtual const AccessibilityHandler* getRowHandler (int row) const = 0;
|
||||
|
||||
/** Returns the AccessibilityHandler for the header, or nullptr if there is
|
||||
no header.
|
||||
|
||||
If you supply a header, it must have exactly the same number of children
|
||||
as there are columns in the table.
|
||||
*/
|
||||
virtual const AccessibilityHandler* getHeaderHandler() const = 0;
|
||||
|
||||
/** A simple span of elements. */
|
||||
struct Span { int begin, num; };
|
||||
|
||||
/** Given the handler of one of the cells in the table, returns the rows covered
|
||||
by that cell, or null if the cell does not exist in the table.
|
||||
|
||||
This function replaces the getRowIndex and getRowSpan
|
||||
functions from AccessibilityCellInterface. Most of the time, it's easier for the
|
||||
table itself to keep track of cell locations, than to delegate to the individual
|
||||
cells.
|
||||
*/
|
||||
virtual Optional<Span> getRowSpan (const AccessibilityHandler&) const = 0;
|
||||
|
||||
/** Given the handler of one of the cells in the table, returns the columns covered
|
||||
by that cell, or null if the cell does not exist in the table.
|
||||
|
||||
This function replaces the getColumnIndex and getColumnSpan
|
||||
functions from AccessibilityCellInterface. Most of the time, it's easier for the
|
||||
table itself to keep track of cell locations, than to delegate to the individual
|
||||
cells.
|
||||
*/
|
||||
virtual Optional<Span> getColumnSpan (const AccessibilityHandler&) const = 0;
|
||||
|
||||
/** Attempts to scroll the table (if necessary) so that the cell with the given handler
|
||||
is visible.
|
||||
*/
|
||||
virtual void showCell (const AccessibilityHandler&) const = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 abstract interface which represents a UI element that supports a text interface.
|
||||
|
||||
A UI element can use this interface to provide extended textual information which
|
||||
cannot be conveyed using just the title, description, and help text properties of
|
||||
AccessibilityHandler. This is typically for text that an accessibility client might
|
||||
want to read line-by-line, or provide text selection and input for.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityTextInterface
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AccessibilityTextInterface() = default;
|
||||
|
||||
/** Returns true if the text being displayed is protected and should not be
|
||||
exposed to the user, for example a password entry field.
|
||||
*/
|
||||
virtual bool isDisplayingProtectedText() const = 0;
|
||||
|
||||
/** Returns true if the text being displayed is read-only or false if editable. */
|
||||
virtual bool isReadOnly() const = 0;
|
||||
|
||||
/** Returns the total number of characters in the text element. */
|
||||
virtual int getTotalNumCharacters() const = 0;
|
||||
|
||||
/** Returns the range of characters that are currently selected, or an empty
|
||||
range if nothing is selected.
|
||||
*/
|
||||
virtual Range<int> getSelection() const = 0;
|
||||
|
||||
/** Selects a section of the text. */
|
||||
virtual void setSelection (Range<int> newRange) = 0;
|
||||
|
||||
/** Gets the current text insertion position, if supported. */
|
||||
virtual int getTextInsertionOffset() const = 0;
|
||||
|
||||
/** Returns a section of text. */
|
||||
virtual String getText (Range<int> range) const = 0;
|
||||
|
||||
/** Returns the full text. */
|
||||
String getAllText() const { return getText ({ 0, getTotalNumCharacters() }); }
|
||||
|
||||
/** Replaces the text with a new string. */
|
||||
virtual void setText (const String& newText) = 0;
|
||||
|
||||
/** Returns the bounding box in screen coordinates for a range of text.
|
||||
As the range may span multiple lines, this method returns a RectangleList.
|
||||
*/
|
||||
virtual RectangleList<int> getTextBounds (Range<int> textRange) const = 0;
|
||||
|
||||
/** Returns the index of the character at a given position in screen coordinates. */
|
||||
virtual int getOffsetAtPoint (Point<int> point) const = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
third_party/JUCE/modules/juce_gui_basics/accessibility/interfaces/juce_AccessibilityValueInterface.h
Vendored
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 abstract interface representing the value of an accessibility element.
|
||||
|
||||
Values should be used when information needs to be conveyed which cannot
|
||||
be represented by the accessibility element's label alone. For example, a
|
||||
gain slider with the label "Gain" needs to also provide a value for its
|
||||
position whereas a "Save" button does not.
|
||||
|
||||
This class allows for full control over the value text/numeric conversion,
|
||||
ranged, and read-only properties but in most cases you'll want to use one
|
||||
of the derived classes below which handle some of this for you.
|
||||
|
||||
@see AccessibilityTextValueInterface, AccessibilityNumericValueInterface,
|
||||
AccessibilityRangedNumericValueInterface
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityValueInterface
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~AccessibilityValueInterface() = default;
|
||||
|
||||
/** Returns true if the value is read-only and cannot be modified by an
|
||||
accessibility client.
|
||||
|
||||
@see setValue, setValueAsString
|
||||
*/
|
||||
virtual bool isReadOnly() const = 0;
|
||||
|
||||
/** Returns the current value as a double. */
|
||||
virtual double getCurrentValue() const = 0;
|
||||
|
||||
/** Returns the current value as a String. */
|
||||
virtual String getCurrentValueAsString() const = 0;
|
||||
|
||||
/** Sets the current value to a new double value. */
|
||||
virtual void setValue (double newValue) = 0;
|
||||
|
||||
/** Sets the current value to a new String value. */
|
||||
virtual void setValueAsString (const String& newValue) = 0;
|
||||
|
||||
/** Represents the range of this value, if supported.
|
||||
|
||||
Return one of these from the `getRange()` method, providing a minimum,
|
||||
maximum, and interval value for the range to indicate that this is a
|
||||
ranged value.
|
||||
|
||||
The default state is an "invalid" range, indicating that the accessibility
|
||||
element does not support ranged values.
|
||||
|
||||
@see AccessibilityRangedNumericValueInterface
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibleValueRange
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
|
||||
Creates a default, "invalid" range that can be returned from
|
||||
`AccessibilityValueInterface::getRange()` to indicate that the value
|
||||
interface does not support ranged values.
|
||||
*/
|
||||
AccessibleValueRange() = default;
|
||||
|
||||
/** The minimum and maximum values for this range, inclusive. */
|
||||
struct JUCE_API MinAndMax { double min, max; };
|
||||
|
||||
/** Constructor.
|
||||
|
||||
Creates a valid AccessibleValueRange with the provided minimum, maximum,
|
||||
and interval values.
|
||||
*/
|
||||
AccessibleValueRange (MinAndMax valueRange, double interval)
|
||||
: valid (true),
|
||||
range (valueRange),
|
||||
stepSize (interval)
|
||||
{
|
||||
jassert (range.min < range.max);
|
||||
}
|
||||
|
||||
/** Returns true if this represents a valid range. */
|
||||
bool isValid() const noexcept { return valid; }
|
||||
|
||||
/** Returns the minimum value for this range. */
|
||||
double getMinimumValue() const noexcept { return range.min; }
|
||||
|
||||
/** Returns the maximum value for this range. */
|
||||
double getMaximumValue() const noexcept { return range.max; }
|
||||
|
||||
/** Returns the interval for this range. */
|
||||
double getInterval() const noexcept { return stepSize; }
|
||||
|
||||
private:
|
||||
bool valid = false;
|
||||
MinAndMax range {};
|
||||
double stepSize = 0.0;
|
||||
};
|
||||
|
||||
/** If this is a ranged value, this should return a valid AccessibleValueRange
|
||||
object representing the supported numerical range.
|
||||
*/
|
||||
virtual AccessibleValueRange getRange() const = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A value interface that represents a text value.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityTextValueInterface : public AccessibilityValueInterface
|
||||
{
|
||||
public:
|
||||
/** Returns true if the value is read-only and cannot be modified by an
|
||||
accessibility client.
|
||||
|
||||
@see setValueAsString
|
||||
*/
|
||||
bool isReadOnly() const override = 0;
|
||||
|
||||
/** Returns the current value. */
|
||||
String getCurrentValueAsString() const override = 0;
|
||||
|
||||
/** Sets the current value to a new value. */
|
||||
void setValueAsString (const String& newValue) override = 0;
|
||||
|
||||
/** @internal */
|
||||
double getCurrentValue() const final { return getCurrentValueAsString().getDoubleValue(); }
|
||||
/** @internal */
|
||||
void setValue (double newValue) final { setValueAsString (String (newValue)); }
|
||||
/** @internal */
|
||||
AccessibleValueRange getRange() const final { return {}; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A value interface that represents a non-ranged numeric value.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityNumericValueInterface : public AccessibilityValueInterface
|
||||
{
|
||||
public:
|
||||
/** Returns true if the value is read-only and cannot be modified by an
|
||||
accessibility client.
|
||||
|
||||
@see setValue
|
||||
*/
|
||||
bool isReadOnly() const override = 0;
|
||||
|
||||
/** Returns the current value. */
|
||||
double getCurrentValue() const override = 0;
|
||||
|
||||
/** Sets the current value to a new value. */
|
||||
void setValue (double newValue) override = 0;
|
||||
|
||||
/** @internal */
|
||||
String getCurrentValueAsString() const final { return String (getCurrentValue()); }
|
||||
/** @internal */
|
||||
void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); }
|
||||
/** @internal */
|
||||
AccessibleValueRange getRange() const final { return {}; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** A value interface that represents a ranged numeric value.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityRangedNumericValueInterface : public AccessibilityValueInterface
|
||||
{
|
||||
public:
|
||||
/** Returns true if the value is read-only and cannot be modified by an
|
||||
accessibility client.
|
||||
|
||||
@see setValueAsString
|
||||
*/
|
||||
bool isReadOnly() const override = 0;
|
||||
|
||||
/** Returns the current value. */
|
||||
double getCurrentValue() const override = 0;
|
||||
|
||||
/** Sets the current value to a new value. */
|
||||
void setValue (double newValue) override = 0;
|
||||
|
||||
/** Returns the range. */
|
||||
AccessibleValueRange getRange() const override = 0;
|
||||
|
||||
/** @internal */
|
||||
String getCurrentValueAsString() const final { return String (getCurrentValue()); }
|
||||
/** @internal */
|
||||
void setValueAsString (const String& newValue) final { setValue (newValue.getDoubleValue()); }
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
AccessibilityHandler* AccessibilityHandler::currentlyFocusedHandler = nullptr;
|
||||
|
||||
class NativeChildHandler
|
||||
{
|
||||
public:
|
||||
static NativeChildHandler& getInstance()
|
||||
{
|
||||
static NativeChildHandler instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void* getNativeChild (Component& component) const
|
||||
{
|
||||
if (auto it = nativeChildForComponent.find (&component);
|
||||
it != nativeChildForComponent.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Component* getComponent (void* nativeChild) const
|
||||
{
|
||||
if (auto it = componentForNativeChild.find (nativeChild);
|
||||
it != componentForNativeChild.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void setNativeChild (Component& component, void* nativeChild)
|
||||
{
|
||||
clearComponent (component);
|
||||
|
||||
if (nativeChild != nullptr)
|
||||
{
|
||||
nativeChildForComponent[&component] = nativeChild;
|
||||
componentForNativeChild[nativeChild] = &component;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NativeChildHandler() = default;
|
||||
|
||||
void clearComponent (Component& component)
|
||||
{
|
||||
if (auto* nativeChild = getNativeChild (component))
|
||||
componentForNativeChild.erase (nativeChild);
|
||||
|
||||
nativeChildForComponent.erase (&component);
|
||||
}
|
||||
|
||||
std::map<void*, Component*> componentForNativeChild;
|
||||
std::map<Component*, void*> nativeChildForComponent;
|
||||
};
|
||||
|
||||
AccessibilityHandler::AccessibilityHandler (Component& comp,
|
||||
AccessibilityRole accessibilityRole,
|
||||
AccessibilityActions accessibilityActions,
|
||||
Interfaces interfacesIn)
|
||||
: component (comp),
|
||||
typeIndex (typeid (component)),
|
||||
role (accessibilityRole),
|
||||
actions (std::move (accessibilityActions)),
|
||||
interfaces (std::move (interfacesIn)),
|
||||
nativeImpl (createNativeImpl (*this))
|
||||
{
|
||||
}
|
||||
|
||||
AccessibilityHandler::~AccessibilityHandler()
|
||||
{
|
||||
giveAwayFocus();
|
||||
detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::elementDestroyed);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
AccessibleState AccessibilityHandler::getCurrentState() const
|
||||
{
|
||||
if (component.isCurrentlyBlockedByAnotherModalComponent()
|
||||
&& Component::getCurrentlyModalComponent()->isVisible())
|
||||
return {};
|
||||
|
||||
auto state = AccessibleState().withFocusable();
|
||||
|
||||
return hasFocus (false) ? state.withFocused() : state;
|
||||
}
|
||||
|
||||
bool AccessibilityHandler::isIgnored() const
|
||||
{
|
||||
return role == AccessibilityRole::ignored || getCurrentState().isIgnored();
|
||||
}
|
||||
|
||||
static bool isComponentVisibleWithinWindow (const Component& comp)
|
||||
{
|
||||
if (auto* peer = comp.getPeer())
|
||||
return ! peer->getAreaCoveredBy (comp).getIntersection (peer->getComponent().getLocalBounds()).isEmpty();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isComponentVisibleWithinParent (Component* comp)
|
||||
{
|
||||
if (auto* parent = comp->getParentComponent())
|
||||
{
|
||||
if (comp->getBoundsInParent().getIntersection (parent->getLocalBounds()).isEmpty())
|
||||
return false;
|
||||
|
||||
return isComponentVisibleWithinParent (parent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AccessibilityHandler::isVisibleWithinParent() const
|
||||
{
|
||||
return getCurrentState().isAccessibleOffscreen()
|
||||
|| (isComponentVisibleWithinParent (&component) && isComponentVisibleWithinWindow (component));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const AccessibilityActions& AccessibilityHandler::getActions() const noexcept
|
||||
{
|
||||
return actions;
|
||||
}
|
||||
|
||||
AccessibilityValueInterface* AccessibilityHandler::getValueInterface() const
|
||||
{
|
||||
return interfaces.value.get();
|
||||
}
|
||||
|
||||
AccessibilityTableInterface* AccessibilityHandler::getTableInterface() const
|
||||
{
|
||||
return interfaces.table.get();
|
||||
}
|
||||
|
||||
AccessibilityCellInterface* AccessibilityHandler::getCellInterface() const
|
||||
{
|
||||
return interfaces.cell.get();
|
||||
}
|
||||
|
||||
AccessibilityTextInterface* AccessibilityHandler::getTextInterface() const
|
||||
{
|
||||
return interfaces.text.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static AccessibilityHandler* findEnclosingHandler (Component* comp)
|
||||
{
|
||||
if (comp != nullptr)
|
||||
{
|
||||
if (auto* handler = comp->getAccessibilityHandler())
|
||||
return handler;
|
||||
|
||||
return findEnclosingHandler (comp->getParentComponent());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static AccessibilityHandler* getUnignoredAncestor (AccessibilityHandler* handler)
|
||||
{
|
||||
while (handler != nullptr
|
||||
&& (handler->isIgnored() || ! handler->isVisibleWithinParent())
|
||||
&& handler->getParent() != nullptr)
|
||||
{
|
||||
handler = handler->getParent();
|
||||
}
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
static AccessibilityHandler* findFirstUnignoredChild (const std::vector<AccessibilityHandler*>& handlers)
|
||||
{
|
||||
if (! handlers.empty())
|
||||
{
|
||||
const auto iter = std::find_if (handlers.cbegin(), handlers.cend(),
|
||||
[] (const AccessibilityHandler* handler) { return ! handler->isIgnored() && handler->isVisibleWithinParent(); });
|
||||
|
||||
if (iter != handlers.cend())
|
||||
return *iter;
|
||||
|
||||
for (auto* handler : handlers)
|
||||
if (auto* unignored = findFirstUnignoredChild (handler->getChildren()))
|
||||
return unignored;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static AccessibilityHandler* getFirstUnignoredDescendant (AccessibilityHandler* handler)
|
||||
{
|
||||
if (handler != nullptr && (handler->isIgnored() || ! handler->isVisibleWithinParent()))
|
||||
return findFirstUnignoredChild (handler->getChildren());
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
AccessibilityHandler* AccessibilityHandler::getParent() const
|
||||
{
|
||||
if (auto* focusContainer = component.findFocusContainer())
|
||||
return getUnignoredAncestor (findEnclosingHandler (focusContainer));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<AccessibilityHandler*> AccessibilityHandler::getChildren() const
|
||||
{
|
||||
if (! component.isFocusContainer() && component.getParentComponent() != nullptr)
|
||||
return {};
|
||||
|
||||
const auto addChildComponentHandler = [this] (Component* focusableComponent,
|
||||
std::vector<AccessibilityHandler*>& childHandlers)
|
||||
{
|
||||
if (focusableComponent == nullptr)
|
||||
return;
|
||||
|
||||
if (auto* handler = findEnclosingHandler (focusableComponent))
|
||||
{
|
||||
if (! handler->getCurrentState().isFocusable() || ! isParentOf (handler))
|
||||
return;
|
||||
|
||||
if (auto* unignored = getFirstUnignoredDescendant (handler))
|
||||
if (std::find (childHandlers.cbegin(), childHandlers.cend(), unignored) == childHandlers.cend())
|
||||
childHandlers.push_back (unignored);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<AccessibilityHandler*> children;
|
||||
|
||||
if (auto traverser = component.createFocusTraverser())
|
||||
{
|
||||
addChildComponentHandler (traverser->getDefaultComponent (&component), children);
|
||||
|
||||
for (auto* focusableChild : traverser->getAllComponents (&component))
|
||||
addChildComponentHandler (focusableChild, children);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
bool AccessibilityHandler::isParentOf (const AccessibilityHandler* possibleChild) const noexcept
|
||||
{
|
||||
while (possibleChild != nullptr)
|
||||
{
|
||||
possibleChild = possibleChild->getParent();
|
||||
|
||||
if (possibleChild == this)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AccessibilityHandler* AccessibilityHandler::getChildAt (Point<int> screenPoint)
|
||||
{
|
||||
if (auto* comp = Desktop::getInstance().findComponentAt (screenPoint))
|
||||
{
|
||||
if (auto* handler = getUnignoredAncestor (findEnclosingHandler (comp)))
|
||||
if (isParentOf (handler))
|
||||
return handler;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AccessibilityHandler* AccessibilityHandler::getChildFocus()
|
||||
{
|
||||
return hasFocus (true) ? getUnignoredAncestor (currentlyFocusedHandler)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
bool AccessibilityHandler::hasFocus (bool trueIfChildFocused) const
|
||||
{
|
||||
return currentlyFocusedHandler != nullptr
|
||||
&& (currentlyFocusedHandler == this
|
||||
|| (trueIfChildFocused && isParentOf (currentlyFocusedHandler)));
|
||||
}
|
||||
|
||||
void AccessibilityHandler::grabFocus()
|
||||
{
|
||||
if (! hasFocus (false))
|
||||
grabFocusInternal (true);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::giveAwayFocus() const
|
||||
{
|
||||
if (hasFocus (true))
|
||||
giveAwayFocusInternal();
|
||||
}
|
||||
|
||||
void AccessibilityHandler::grabFocusInternal (bool canTryParent)
|
||||
{
|
||||
if (getCurrentState().isFocusable() && ! isIgnored())
|
||||
{
|
||||
takeFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isParentOf (currentlyFocusedHandler))
|
||||
return;
|
||||
|
||||
if (auto traverser = component.createFocusTraverser())
|
||||
{
|
||||
if (auto* defaultComp = traverser->getDefaultComponent (&component))
|
||||
{
|
||||
if (auto* handler = getUnignoredAncestor (findEnclosingHandler (defaultComp)))
|
||||
{
|
||||
if (isParentOf (handler))
|
||||
{
|
||||
handler->grabFocusInternal (false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canTryParent)
|
||||
if (auto* parent = getParent())
|
||||
parent->grabFocusInternal (true);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::giveAwayFocusInternal() const
|
||||
{
|
||||
currentlyFocusedHandler = nullptr;
|
||||
detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::focusChanged);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::takeFocus()
|
||||
{
|
||||
currentlyFocusedHandler = this;
|
||||
detail::AccessibilityHelpers::notifyAccessibilityEvent (*this, detail::AccessibilityHelpers::Event::focusChanged);
|
||||
|
||||
if ((component.isShowing() || component.isOnDesktop())
|
||||
&& component.getWantsKeyboardFocus()
|
||||
&& ! component.hasKeyboardFocus (true))
|
||||
{
|
||||
component.grabKeyboardFocus();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler::AccessibilityNativeImpl> AccessibilityHandler::createNativeImpl (AccessibilityHandler& handler)
|
||||
{
|
||||
#if JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||
return std::make_unique<AccessibilityNativeImpl> (handler);
|
||||
#else
|
||||
ignoreUnused (handler);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void* AccessibilityHandler::getNativeChildForComponent (Component& component)
|
||||
{
|
||||
return NativeChildHandler::getInstance().getNativeChild (component);
|
||||
}
|
||||
|
||||
Component* AccessibilityHandler::getComponentForNativeChild (void* nativeChild)
|
||||
{
|
||||
return NativeChildHandler::getInstance().getComponent (nativeChild);
|
||||
}
|
||||
|
||||
void AccessibilityHandler::setNativeChildForComponent (Component& component, void* nativeChild)
|
||||
{
|
||||
NativeChildHandler::getInstance().setNativeChild (component, nativeChild);
|
||||
}
|
||||
|
||||
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||
void AccessibilityHandler::notifyAccessibilityEvent (AccessibilityEvent) const {}
|
||||
void AccessibilityHandler::postAnnouncement (const String&, AnnouncementPriority) {}
|
||||
AccessibilityNativeHandle* AccessibilityHandler::getNativeImplementation() const { return nullptr; }
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 AccessibilityNativeHandle;
|
||||
|
||||
/** Base class for accessible Components.
|
||||
|
||||
This class wraps a Component and provides methods that allow an accessibility client,
|
||||
such as VoiceOver on macOS, or Narrator on Windows, to control it.
|
||||
|
||||
It handles hierarchical navigation, properties, state, and various interfaces.
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
/** Utility struct which holds one or more accessibility interfaces.
|
||||
|
||||
The main purpose of this class is to provide convenience constructors from each
|
||||
of the four types of accessibility interface.
|
||||
*/
|
||||
struct JUCE_API Interfaces
|
||||
{
|
||||
Interfaces() = default;
|
||||
|
||||
Interfaces (std::unique_ptr<AccessibilityValueInterface> ptr) : value (std::move (ptr)) {}
|
||||
Interfaces (std::unique_ptr<AccessibilityTextInterface> ptr) : text (std::move (ptr)) {}
|
||||
Interfaces (std::unique_ptr<AccessibilityTableInterface> ptr) : table (std::move (ptr)) {}
|
||||
Interfaces (std::unique_ptr<AccessibilityCellInterface> ptr) : cell (std::move (ptr)) {}
|
||||
|
||||
Interfaces (std::unique_ptr<AccessibilityValueInterface> valueIn,
|
||||
std::unique_ptr<AccessibilityTextInterface> textIn,
|
||||
std::unique_ptr<AccessibilityTableInterface> tableIn,
|
||||
std::unique_ptr<AccessibilityCellInterface> cellIn)
|
||||
: value (std::move (valueIn)),
|
||||
text (std::move (textIn)),
|
||||
table (std::move (tableIn)),
|
||||
cell (std::move (cellIn))
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityValueInterface> value;
|
||||
std::unique_ptr<AccessibilityTextInterface> text;
|
||||
std::unique_ptr<AccessibilityTableInterface> table;
|
||||
std::unique_ptr<AccessibilityCellInterface> cell;
|
||||
};
|
||||
|
||||
/** Constructor.
|
||||
|
||||
This will create a AccessibilityHandler which wraps the provided Component and makes
|
||||
it visible to accessibility clients. You must also specify a role for the UI element
|
||||
from the `AccessibilityRole` list which best describes it.
|
||||
|
||||
To enable users to interact with the UI element you should provide the set of supported
|
||||
actions and their associated callbacks via the `accessibilityActions` parameter.
|
||||
|
||||
For UI elements that support more complex interaction the value, text, table, and cell
|
||||
interfaces should be implemented as required and passed as the final argument of this
|
||||
constructor. See the documentation of these classes for more information about the
|
||||
types of control they represent and which methods need to be implemented.
|
||||
*/
|
||||
AccessibilityHandler (Component& componentToWrap,
|
||||
AccessibilityRole accessibilityRole,
|
||||
AccessibilityActions actions = {},
|
||||
Interfaces interfaces = {});
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~AccessibilityHandler();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the Component that this handler represents. */
|
||||
const Component& getComponent() const noexcept { return component; }
|
||||
|
||||
/** Returns the Component that this handler represents. */
|
||||
Component& getComponent() noexcept { return component; }
|
||||
|
||||
//==============================================================================
|
||||
/** The type of UI element that this accessibility handler represents.
|
||||
|
||||
@see AccessibilityRole
|
||||
*/
|
||||
AccessibilityRole getRole() const noexcept { return role; }
|
||||
|
||||
/** The title of the UI element.
|
||||
|
||||
This will be read out by the system and should be concise, preferably matching
|
||||
the visible title of the UI element (if any). For example, this might be the
|
||||
text of a button or a simple label.
|
||||
|
||||
The default implementation will call `Component::getTitle()`, but you can override
|
||||
this to return a different string if required.
|
||||
|
||||
If neither a name nor a description is provided then the UI element may be
|
||||
ignored by accessibility clients.
|
||||
|
||||
This must be a localised string.
|
||||
*/
|
||||
virtual String getTitle() const { return component.getTitle(); }
|
||||
|
||||
/** A short description of the UI element.
|
||||
|
||||
This may be read out by the system. It should not include the type of the UI
|
||||
element and should ideally be a single word, for example "Open" for a button
|
||||
that opens a window.
|
||||
|
||||
The default implementation will call `Component::getDescription()`, but you
|
||||
can override this to return a different string if required.
|
||||
|
||||
If neither a name nor a description is provided then the UI element may be
|
||||
ignored by accessibility clients.
|
||||
|
||||
This must be a localised string.
|
||||
*/
|
||||
virtual String getDescription() const { return component.getDescription(); }
|
||||
|
||||
/** Some help text for the UI element (if required).
|
||||
|
||||
This may be read out by the system. This string functions in a similar way to
|
||||
a tooltip, for example "Click to open window." for a button which opens a window.
|
||||
|
||||
The default implementation will call `Component::getHelpText()`, but you can
|
||||
override this to return a different string if required.
|
||||
|
||||
This must be a localised string.
|
||||
*/
|
||||
virtual String getHelp() const { return component.getHelpText(); }
|
||||
|
||||
/** Returns the current state of the UI element.
|
||||
|
||||
The default implementation of this method will set the focusable flag and, if
|
||||
this UI element is currently focused, will also set the focused flag.
|
||||
*/
|
||||
virtual AccessibleState getCurrentState() const;
|
||||
|
||||
/** Returns true if this UI element should be ignored by accessibility clients. */
|
||||
bool isIgnored() const;
|
||||
|
||||
/** Returns true if this UI element is visible within its parent.
|
||||
|
||||
This will always return true for UI elements with the AccessibleState::accessibleOffscreen
|
||||
flag set.
|
||||
*/
|
||||
bool isVisibleWithinParent() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the set of actions that the UI element supports and the associated
|
||||
callbacks.
|
||||
*/
|
||||
const AccessibilityActions& getActions() const noexcept;
|
||||
|
||||
/** Returns the value interface for this UI element, or nullptr if it is not supported.
|
||||
|
||||
@see AccessibilityValueInterface
|
||||
*/
|
||||
AccessibilityValueInterface* getValueInterface() const;
|
||||
|
||||
/** Returns the table interface for this UI element, or nullptr if it is not supported.
|
||||
|
||||
@see AccessibilityTableInterface
|
||||
*/
|
||||
AccessibilityTableInterface* getTableInterface() const;
|
||||
|
||||
/** Returns the cell interface for this UI element, or nullptr if it is not supported.
|
||||
|
||||
@see AccessibilityCellInterface
|
||||
*/
|
||||
AccessibilityCellInterface* getCellInterface() const;
|
||||
|
||||
/** Returns the text interface for this UI element, or nullptr if it is not supported.
|
||||
|
||||
@see AccessibilityTextInterface
|
||||
*/
|
||||
AccessibilityTextInterface* getTextInterface() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the first unignored parent of this UI element in the accessibility hierarchy,
|
||||
or nullptr if this is a root element without a parent.
|
||||
*/
|
||||
AccessibilityHandler* getParent() const;
|
||||
|
||||
/** Returns the unignored children of this UI element in the accessibility hierarchy. */
|
||||
std::vector<AccessibilityHandler*> getChildren() const;
|
||||
|
||||
/** Checks whether a given UI element is a child of this one in the accessibility
|
||||
hierarchy.
|
||||
*/
|
||||
bool isParentOf (const AccessibilityHandler* possibleChild) const noexcept;
|
||||
|
||||
/** Returns the deepest child of this UI element in the accessibility hierarchy that
|
||||
contains the given screen point, or nullptr if there is no child at this point.
|
||||
*/
|
||||
AccessibilityHandler* getChildAt (Point<int> screenPoint);
|
||||
|
||||
/** Returns the deepest UI element which currently has focus.
|
||||
|
||||
This can be a child of this UI element or, if no child is focused,
|
||||
this element itself.
|
||||
|
||||
Note that this can be different to the value of the Component with keyboard
|
||||
focus returned by Component::getCurrentlyFocusedComponent().
|
||||
|
||||
@see hasFocus
|
||||
*/
|
||||
AccessibilityHandler* getChildFocus();
|
||||
|
||||
/** Returns true if this UI element has the focus.
|
||||
|
||||
@param trueIfChildFocused if this is true, this method will also return true
|
||||
if any child of this UI element in the accessibility
|
||||
hierarchy has focus
|
||||
*/
|
||||
bool hasFocus (bool trueIfChildFocused) const;
|
||||
|
||||
/** Tries to give focus to this UI element.
|
||||
|
||||
If the UI element is focusable and not ignored this will update the currently focused
|
||||
element, try to give keyboard focus to the Component it represents, and notify any
|
||||
listening accessibility clients that the current focus has changed.
|
||||
|
||||
@see hasFocus, giveAwayFocus
|
||||
*/
|
||||
void grabFocus();
|
||||
|
||||
/** If this UI element or any of its children in the accessibility hierarchy currently
|
||||
have focus, this will defocus it.
|
||||
|
||||
This will also give away the keyboard focus from the Component it represents, and
|
||||
notify any listening accessibility clients that the current focus has changed.
|
||||
|
||||
@see hasFocus, grabFocus
|
||||
*/
|
||||
void giveAwayFocus() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Used to send a notification to any observing accessibility clients that something
|
||||
has changed in the UI element.
|
||||
|
||||
@see AccessibilityEvent
|
||||
*/
|
||||
void notifyAccessibilityEvent (AccessibilityEvent event) const;
|
||||
|
||||
/** A priority level that can help an accessibility client determine how to handle
|
||||
an announcement request.
|
||||
|
||||
Exactly what this controls is platform-specific, but generally a low priority
|
||||
announcement will be read when the screen reader is free, whereas a high priority
|
||||
announcement will interrupt the current speech.
|
||||
*/
|
||||
enum class AnnouncementPriority
|
||||
{
|
||||
low,
|
||||
medium,
|
||||
high
|
||||
};
|
||||
|
||||
/** Posts an announcement to be made to the user.
|
||||
|
||||
@param announcementString a localised string containing the announcement to be read out
|
||||
@param priority the appropriate priority level for the announcement
|
||||
*/
|
||||
static void postAnnouncement (const String& announcementString, AnnouncementPriority priority);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
AccessibilityNativeHandle* getNativeImplementation() const;
|
||||
/** @internal */
|
||||
std::type_index getTypeIndex() const { return typeIndex; }
|
||||
/** @internal */
|
||||
static void clearCurrentlyFocusedHandler() { currentlyFocusedHandler = nullptr; }
|
||||
|
||||
/** @internal
|
||||
|
||||
The following functions provide the means to associate JUCE Components with OS specific
|
||||
types that provide their own accessibility mechanisms. This way accessibility navigation
|
||||
can move from a JUCE Component to a native, embedded window and back.
|
||||
|
||||
These functions assume that the concrete types behind the void* are
|
||||
- Windows: HWND
|
||||
- MacOS: NSView*
|
||||
- iOS: UIView*
|
||||
- Android: GlobalRef that points to an android.view.View
|
||||
*/
|
||||
static void* getNativeChildForComponent (Component& component);
|
||||
/** @internal */
|
||||
static void setNativeChildForComponent (Component& component, void* nativeChild);
|
||||
/** @internal */
|
||||
static Component* getComponentForNativeChild (void* nativeChild);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class AccessibilityNativeHandle;
|
||||
|
||||
//==============================================================================
|
||||
void grabFocusInternal (bool);
|
||||
void giveAwayFocusInternal() const;
|
||||
void takeFocus();
|
||||
|
||||
static AccessibilityHandler* currentlyFocusedHandler;
|
||||
|
||||
//==============================================================================
|
||||
Component& component;
|
||||
std::type_index typeIndex;
|
||||
|
||||
const AccessibilityRole role;
|
||||
AccessibilityActions actions;
|
||||
|
||||
Interfaces interfaces;
|
||||
|
||||
//==============================================================================
|
||||
class AccessibilityNativeImpl;
|
||||
std::unique_ptr<AccessibilityNativeImpl> nativeImpl;
|
||||
|
||||
static std::unique_ptr<AccessibilityNativeImpl> createNativeImpl (AccessibilityHandler&);
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 the state of an accessible UI element.
|
||||
|
||||
An instance of this class is returned by `AccessibilityHandler::getCurrentState()`
|
||||
to convey its current state to an accessibility client.
|
||||
|
||||
@see AccessibilityHandler
|
||||
|
||||
@tags{Accessibility}
|
||||
*/
|
||||
class JUCE_API AccessibleState
|
||||
{
|
||||
public:
|
||||
/** Constructor.
|
||||
|
||||
Represents a "default" state with no flags set. To set a flag, use one of the
|
||||
`withX()` methods - these can be chained together to set multiple flags.
|
||||
*/
|
||||
AccessibleState() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the checkable flag and returns the new state.
|
||||
|
||||
@see isCheckable
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withCheckable() const noexcept { return withFlag (Flags::checkable); }
|
||||
|
||||
/** Sets the checked flag and returns the new state.
|
||||
|
||||
@see isChecked
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withChecked() const noexcept { return withFlag (Flags::checked); }
|
||||
|
||||
/** Sets the collapsed flag and returns the new state.
|
||||
|
||||
@see isCollapsed
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withCollapsed() const noexcept { return withFlag (Flags::collapsed); }
|
||||
|
||||
/** Sets the expandable flag and returns the new state.
|
||||
|
||||
@see isExpandable
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withExpandable() const noexcept { return withFlag (Flags::expandable); }
|
||||
|
||||
/** Sets the expanded flag and returns the new state.
|
||||
|
||||
@see isExpanded
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withExpanded() const noexcept { return withFlag (Flags::expanded); }
|
||||
|
||||
/** Sets the focusable flag and returns the new state.
|
||||
|
||||
@see isFocusable
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withFocusable() const noexcept { return withFlag (Flags::focusable); }
|
||||
|
||||
/** Sets the focused flag and returns the new state.
|
||||
|
||||
@see isFocused
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withFocused() const noexcept { return withFlag (Flags::focused); }
|
||||
|
||||
/** Sets the ignored flag and returns the new state.
|
||||
|
||||
@see isIgnored
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withIgnored() const noexcept { return withFlag (Flags::ignored); }
|
||||
|
||||
/** Sets the selectable flag and returns the new state.
|
||||
|
||||
@see isSelectable
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withSelectable() const noexcept { return withFlag (Flags::selectable); }
|
||||
|
||||
/** Sets the multiSelectable flag and returns the new state.
|
||||
|
||||
@see isMultiSelectable
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withMultiSelectable() const noexcept { return withFlag (Flags::multiSelectable); }
|
||||
|
||||
/** Sets the selected flag and returns the new state.
|
||||
|
||||
@see isSelected
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withSelected() const noexcept { return withFlag (Flags::selected); }
|
||||
|
||||
/** Sets the accessible offscreen flag and returns the new state.
|
||||
|
||||
@see isSelected
|
||||
*/
|
||||
[[nodiscard]] AccessibleState withAccessibleOffscreen() const noexcept { return withFlag (Flags::accessibleOffscreen); }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the UI element is checkable.
|
||||
|
||||
@see withCheckable
|
||||
*/
|
||||
bool isCheckable() const noexcept { return isFlagSet (Flags::checkable); }
|
||||
|
||||
/** Returns true if the UI element is checked.
|
||||
|
||||
@see withChecked
|
||||
*/
|
||||
bool isChecked() const noexcept { return isFlagSet (Flags::checked); }
|
||||
|
||||
/** Returns true if the UI element is collapsed.
|
||||
|
||||
@see withCollapsed
|
||||
*/
|
||||
bool isCollapsed() const noexcept { return isFlagSet (Flags::collapsed); }
|
||||
|
||||
/** Returns true if the UI element is expandable.
|
||||
|
||||
@see withExpandable
|
||||
*/
|
||||
bool isExpandable() const noexcept { return isFlagSet (Flags::expandable); }
|
||||
|
||||
/** Returns true if the UI element is expanded.
|
||||
|
||||
@see withExpanded
|
||||
*/
|
||||
bool isExpanded() const noexcept { return isFlagSet (Flags::expanded); }
|
||||
|
||||
/** Returns true if the UI element is focusable.
|
||||
|
||||
@see withFocusable
|
||||
*/
|
||||
bool isFocusable() const noexcept { return isFlagSet (Flags::focusable); }
|
||||
|
||||
/** Returns true if the UI element is focused.
|
||||
|
||||
@see withFocused
|
||||
*/
|
||||
bool isFocused() const noexcept { return isFlagSet (Flags::focused); }
|
||||
|
||||
/** Returns true if the UI element is ignored.
|
||||
|
||||
@see withIgnored
|
||||
*/
|
||||
bool isIgnored() const noexcept { return isFlagSet (Flags::ignored); }
|
||||
|
||||
/** Returns true if the UI element supports multiple item selection.
|
||||
|
||||
@see withMultiSelectable
|
||||
*/
|
||||
bool isMultiSelectable() const noexcept { return isFlagSet (Flags::multiSelectable); }
|
||||
|
||||
/** Returns true if the UI element is selectable.
|
||||
|
||||
@see withSelectable
|
||||
*/
|
||||
bool isSelectable() const noexcept { return isFlagSet (Flags::selectable); }
|
||||
|
||||
/** Returns true if the UI element is selected.
|
||||
|
||||
@see withSelected
|
||||
*/
|
||||
bool isSelected() const noexcept { return isFlagSet (Flags::selected); }
|
||||
|
||||
/** Returns true if the UI element is accessible offscreen.
|
||||
|
||||
@see withSelected
|
||||
*/
|
||||
bool isAccessibleOffscreen() const noexcept { return isFlagSet (Flags::accessibleOffscreen); }
|
||||
|
||||
private:
|
||||
enum Flags
|
||||
{
|
||||
checkable = (1 << 0),
|
||||
checked = (1 << 1),
|
||||
collapsed = (1 << 2),
|
||||
expandable = (1 << 3),
|
||||
expanded = (1 << 4),
|
||||
focusable = (1 << 5),
|
||||
focused = (1 << 6),
|
||||
ignored = (1 << 7),
|
||||
multiSelectable = (1 << 8),
|
||||
selectable = (1 << 9),
|
||||
selected = (1 << 10),
|
||||
accessibleOffscreen = (1 << 11)
|
||||
};
|
||||
|
||||
[[nodiscard]] AccessibleState withFlag (int flag) const noexcept
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.flags |= flag;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool isFlagSet (int flag) const noexcept
|
||||
{
|
||||
return (flags & flag) != 0;
|
||||
}
|
||||
|
||||
int flags = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
JUCEApplication::JUCEApplication() {}
|
||||
JUCEApplication::~JUCEApplication() {}
|
||||
|
||||
//==============================================================================
|
||||
JUCEApplication* JUCE_CALLTYPE JUCEApplication::getInstance() noexcept
|
||||
{
|
||||
return dynamic_cast<JUCEApplication*> (JUCEApplicationBase::getInstance());
|
||||
}
|
||||
|
||||
bool JUCEApplication::moreThanOneInstanceAllowed() { return true; }
|
||||
void JUCEApplication::anotherInstanceStarted (const String&) {}
|
||||
|
||||
void JUCEApplication::suspended() {}
|
||||
void JUCEApplication::resumed() {}
|
||||
|
||||
void JUCEApplication::systemRequestedQuit() { quit(); }
|
||||
|
||||
void JUCEApplication::unhandledException (const std::exception*, const String&, int)
|
||||
{
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* JUCEApplication::getNextCommandTarget()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JUCEApplication::getAllCommands (Array<CommandID>& commands)
|
||||
{
|
||||
commands.add (StandardApplicationCommandIDs::quit);
|
||||
}
|
||||
|
||||
void JUCEApplication::getCommandInfo (const CommandID commandID, ApplicationCommandInfo& result)
|
||||
{
|
||||
if (commandID == StandardApplicationCommandIDs::quit)
|
||||
{
|
||||
result.setInfo (TRANS ("Quit"),
|
||||
TRANS ("Quits the application"),
|
||||
"Application", 0);
|
||||
|
||||
result.defaultKeypresses.add (KeyPress ('q', ModifierKeys::commandModifier, 0));
|
||||
}
|
||||
}
|
||||
|
||||
bool JUCEApplication::perform (const InvocationInfo& info)
|
||||
{
|
||||
if (info.commandID == StandardApplicationCommandIDs::quit)
|
||||
{
|
||||
systemRequestedQuit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool JUCEApplication::initialiseApp()
|
||||
{
|
||||
if (JUCEApplicationBase::initialiseApp())
|
||||
{
|
||||
#if JUCE_MAC
|
||||
initialiseMacMainMenu(); // (needs to get the app's name)
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 instance of this class is used to specify initialisation and shutdown
|
||||
code for the application.
|
||||
|
||||
Any application that wants to run an event loop must declare a subclass of
|
||||
JUCEApplicationBase or JUCEApplication, 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.
|
||||
|
||||
Note that this class is derived from JUCEApplicationBase, which contains most
|
||||
of the useful methods and functionality. This derived class is here simply as
|
||||
a convenient way to also inherit from an ApplicationCommandTarget, and to implement
|
||||
default versions of some of the pure virtual base class methods. But you can derive
|
||||
your app object directly from JUCEApplicationBase if you want to, and by doing so
|
||||
can avoid having a dependency on the juce_gui_basics module.
|
||||
|
||||
e.g. @code
|
||||
class MyJUCEApp : public JUCEApplication
|
||||
{
|
||||
public:
|
||||
MyJUCEApp() {}
|
||||
~MyJUCEApp() {}
|
||||
|
||||
void initialise (const String& commandLine) override
|
||||
{
|
||||
myMainWindow.reset (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:
|
||||
std::unique_ptr<MyApplicationWindow> myMainWindow;
|
||||
};
|
||||
|
||||
// this generates boilerplate code to launch our app class:
|
||||
START_JUCE_APPLICATION (MyJUCEApp)
|
||||
@endcode
|
||||
|
||||
@see JUCEApplicationBase, START_JUCE_APPLICATION
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API JUCEApplication : public JUCEApplicationBase,
|
||||
public ApplicationCommandTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructs a JUCE app object.
|
||||
|
||||
If subclasses implement a constructor or destructor, they shouldn't call any
|
||||
JUCE code in there - put your startup/shutdown code in initialise() and
|
||||
shutdown() instead.
|
||||
*/
|
||||
JUCEApplication();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
If subclasses implement a constructor or destructor, they shouldn't call any
|
||||
JUCE code in there - put your startup/shutdown code in initialise() and
|
||||
shutdown() instead.
|
||||
*/
|
||||
~JUCEApplication() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the global instance of the application object being run. */
|
||||
static JUCEApplication* JUCE_CALLTYPE getInstance() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
#if DOXYGEN
|
||||
/** Returns the application's name. */
|
||||
virtual const String getApplicationName() = 0;
|
||||
|
||||
/** Returns the application's version number. */
|
||||
virtual const String getApplicationVersion() = 0;
|
||||
#endif
|
||||
|
||||
/** Checks whether multiple instances of the app are allowed.
|
||||
|
||||
If your application class returns true for this, more than one instance is
|
||||
permitted to run (except on OSX where the OS automatically stops you launching
|
||||
a second instance of an app without explicitly starting it from the command-line).
|
||||
|
||||
If it's false, the second instance won't start, but 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.
|
||||
*/
|
||||
bool moreThanOneInstanceAllowed() override;
|
||||
|
||||
/** Indicates that the user has tried to start up another instance of the app.
|
||||
This will get called even if moreThanOneInstanceAllowed() is false.
|
||||
*/
|
||||
void anotherInstanceStarted (const String& commandLine) override;
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
void systemRequestedQuit() override;
|
||||
|
||||
/** This method is called when the application is being put into background mode
|
||||
by the operating system.
|
||||
*/
|
||||
void suspended() override;
|
||||
|
||||
/** This method is called when the application is being woken from background mode
|
||||
by the operating system.
|
||||
*/
|
||||
void resumed() override;
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
void unhandledException (const std::exception* e,
|
||||
const String& sourceFilename,
|
||||
int lineNumber) override;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
ApplicationCommandTarget* getNextCommandTarget() override;
|
||||
/** @internal */
|
||||
void getCommandInfo (CommandID, ApplicationCommandInfo&) override;
|
||||
/** @internal */
|
||||
void getAllCommands (Array<CommandID>&) override;
|
||||
/** @internal */
|
||||
bool perform (const InvocationInfo&) override;
|
||||
|
||||
private:
|
||||
bool initialiseApp() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (JUCEApplication)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ArrowButton::ArrowButton (const String& name, float arrowDirectionInRadians, Colour arrowColour)
|
||||
: Button (name), colour (arrowColour)
|
||||
{
|
||||
path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f);
|
||||
path.applyTransform (AffineTransform::rotation (MathConstants<float>::twoPi * arrowDirectionInRadians, 0.5f, 0.5f));
|
||||
}
|
||||
|
||||
ArrowButton::~ArrowButton() {}
|
||||
|
||||
void ArrowButton::paintButton (Graphics& g, bool /*shouldDrawButtonAsHighlighted*/, bool shouldDrawButtonAsDown)
|
||||
{
|
||||
Path p (path);
|
||||
|
||||
const float offset = shouldDrawButtonAsDown ? 1.0f : 0.0f;
|
||||
p.applyTransform (path.getTransformToScaleToFit (offset, offset, (float) getWidth() - 3.0f, (float) getHeight() - 3.0f, false));
|
||||
|
||||
DropShadow (Colours::black.withAlpha (0.3f), shouldDrawButtonAsDown ? 2 : 4, Point<int>()).drawForPath (g, p);
|
||||
|
||||
g.setColour (colour);
|
||||
g.fillPath (p);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button with an arrow in it.
|
||||
|
||||
@see Button
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ArrowButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ArrowButton.
|
||||
|
||||
@param buttonName the name to give the button
|
||||
@param arrowDirection the direction the arrow should point in, where 0.0 is
|
||||
pointing right, 0.25 is down, 0.5 is left, 0.75 is up
|
||||
@param arrowColour the colour to use for the arrow
|
||||
*/
|
||||
ArrowButton (const String& buttonName,
|
||||
float arrowDirection,
|
||||
Colour arrowColour);
|
||||
|
||||
/** Destructor. */
|
||||
~ArrowButton() override;
|
||||
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
|
||||
private:
|
||||
Colour colour;
|
||||
Path path;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArrowButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,721 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 Button::CallbackHelper final : public Timer,
|
||||
public ApplicationCommandManagerListener,
|
||||
public Value::Listener,
|
||||
public KeyListener
|
||||
{
|
||||
CallbackHelper (Button& b) : button (b) {}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
button.repeatTimerCallback();
|
||||
}
|
||||
|
||||
bool keyStateChanged (bool, Component*) override
|
||||
{
|
||||
return button.keyStateChangedCallback();
|
||||
}
|
||||
|
||||
void valueChanged (Value& value) override
|
||||
{
|
||||
if (value.refersToSameSourceAs (button.isOn))
|
||||
button.setToggleState (button.isOn.getValue(), dontSendNotification, sendNotification);
|
||||
}
|
||||
|
||||
bool keyPressed (const KeyPress&, Component*) override
|
||||
{
|
||||
// returning true will avoid forwarding events for keys that we're using as shortcuts
|
||||
return button.isShortcutPressed();
|
||||
}
|
||||
|
||||
void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) override
|
||||
{
|
||||
if (info.commandID == button.commandID
|
||||
&& (info.commandFlags & ApplicationCommandInfo::dontTriggerVisualFeedback) == 0)
|
||||
button.flashButtonState();
|
||||
}
|
||||
|
||||
void applicationCommandListChanged() override
|
||||
{
|
||||
button.applicationCommandListChangeCallback();
|
||||
}
|
||||
|
||||
Button& button;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHelper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
Button::Button (const String& name) : Component (name), text (name)
|
||||
{
|
||||
callbackHelper.reset (new CallbackHelper (*this));
|
||||
|
||||
setWantsKeyboardFocus (true);
|
||||
isOn.addListener (callbackHelper.get());
|
||||
}
|
||||
|
||||
Button::~Button()
|
||||
{
|
||||
clearShortcuts();
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->removeListener (callbackHelper.get());
|
||||
|
||||
isOn.removeListener (callbackHelper.get());
|
||||
callbackHelper.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setButtonText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setTooltip (const String& newTooltip)
|
||||
{
|
||||
SettableTooltipClient::setTooltip (newTooltip);
|
||||
generateTooltip = false;
|
||||
}
|
||||
|
||||
void Button::updateAutomaticTooltip (const ApplicationCommandInfo& info)
|
||||
{
|
||||
if (generateTooltip && commandManagerToUse != nullptr)
|
||||
{
|
||||
auto tt = info.description.isNotEmpty() ? info.description
|
||||
: info.shortName;
|
||||
|
||||
for (auto& kp : commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID))
|
||||
{
|
||||
auto key = kp.getTextDescription();
|
||||
|
||||
tt << " [";
|
||||
|
||||
if (key.length() == 1)
|
||||
tt << TRANS ("shortcut") << ": '" << key << "']";
|
||||
else
|
||||
tt << key << ']';
|
||||
}
|
||||
|
||||
SettableTooltipClient::setTooltip (tt);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setConnectedEdges (int newFlags)
|
||||
{
|
||||
if (connectedEdgeFlags != newFlags)
|
||||
{
|
||||
connectedEdgeFlags = newFlags;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::checkToggleableState (bool wasToggleable)
|
||||
{
|
||||
if (isToggleable() != wasToggleable)
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
|
||||
void Button::setToggleable (bool isNowToggleable)
|
||||
{
|
||||
const auto wasToggleable = isToggleable();
|
||||
|
||||
canBeToggled = isNowToggleable;
|
||||
checkToggleableState (wasToggleable);
|
||||
}
|
||||
|
||||
void Button::setToggleState (bool shouldBeOn, NotificationType notification)
|
||||
{
|
||||
setToggleState (shouldBeOn, notification, notification);
|
||||
}
|
||||
|
||||
void Button::setToggleState (bool shouldBeOn, NotificationType clickNotification, NotificationType stateNotification)
|
||||
{
|
||||
if (shouldBeOn != lastToggleState)
|
||||
{
|
||||
WeakReference<Component> deletionWatcher (this);
|
||||
|
||||
if (shouldBeOn)
|
||||
{
|
||||
turnOffOtherButtonsInGroup (clickNotification, stateNotification);
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
// This test is done so that if the value is void rather than explicitly set to
|
||||
// false, the value won't be changed unless the required value is true.
|
||||
if (getToggleState() != shouldBeOn)
|
||||
{
|
||||
isOn = shouldBeOn;
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
lastToggleState = shouldBeOn;
|
||||
repaint();
|
||||
|
||||
if (clickNotification != dontSendNotification)
|
||||
{
|
||||
// async callbacks aren't possible here
|
||||
jassert (clickNotification != sendNotificationAsync);
|
||||
|
||||
sendClickMessage (ModifierKeys::currentModifiers);
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
|
||||
if (stateNotification != dontSendNotification)
|
||||
sendStateMessage();
|
||||
else
|
||||
buttonStateChanged();
|
||||
|
||||
if (auto* handler = getAccessibilityHandler())
|
||||
handler->notifyAccessibilityEvent (AccessibilityEvent::valueChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::setToggleState (bool shouldBeOn, bool sendChange)
|
||||
{
|
||||
setToggleState (shouldBeOn, sendChange ? sendNotification : dontSendNotification);
|
||||
}
|
||||
|
||||
void Button::setClickingTogglesState (bool shouldToggle) noexcept
|
||||
{
|
||||
const auto wasToggleable = isToggleable();
|
||||
|
||||
clickTogglesState = shouldToggle;
|
||||
checkToggleableState (wasToggleable);
|
||||
|
||||
// if you've got clickTogglesState turned on, you shouldn't also connect the button
|
||||
// up to be a command invoker. Instead, your command handler must flip the state of whatever
|
||||
// it is that this button represents, and the button will update its state to reflect this
|
||||
// in the applicationCommandListChanged() method.
|
||||
jassert (commandManagerToUse == nullptr || ! clickTogglesState);
|
||||
}
|
||||
|
||||
void Button::setRadioGroupId (int newGroupId, NotificationType notification)
|
||||
{
|
||||
if (radioGroupId != newGroupId)
|
||||
{
|
||||
radioGroupId = newGroupId;
|
||||
|
||||
if (lastToggleState)
|
||||
turnOffOtherButtonsInGroup (notification, notification);
|
||||
|
||||
setToggleable (true);
|
||||
invalidateAccessibilityHandler();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::turnOffOtherButtonsInGroup (NotificationType clickNotification, NotificationType stateNotification)
|
||||
{
|
||||
if (auto* p = getParentComponent())
|
||||
{
|
||||
if (radioGroupId != 0)
|
||||
{
|
||||
WeakReference<Component> deletionWatcher (this);
|
||||
|
||||
for (auto* c : p->getChildren())
|
||||
{
|
||||
if (c != this)
|
||||
{
|
||||
if (auto b = dynamic_cast<Button*> (c))
|
||||
{
|
||||
if (b->getRadioGroupId() == radioGroupId)
|
||||
{
|
||||
b->setToggleState (false, clickNotification, stateNotification);
|
||||
|
||||
if (deletionWatcher == nullptr)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::enablementChanged()
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
Button::ButtonState Button::updateState()
|
||||
{
|
||||
return updateState (isMouseOver (true), isMouseButtonDown());
|
||||
}
|
||||
|
||||
Button::ButtonState Button::updateState (bool over, bool down)
|
||||
{
|
||||
ButtonState newState = buttonNormal;
|
||||
|
||||
if (isEnabled() && isVisible() && ! isCurrentlyBlockedByAnotherModalComponent())
|
||||
{
|
||||
if ((down && (over || (triggerOnMouseDown && buttonState == buttonDown))) || isKeyDown)
|
||||
newState = buttonDown;
|
||||
else if (over)
|
||||
newState = buttonOver;
|
||||
}
|
||||
|
||||
setState (newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
void Button::setState (ButtonState newState)
|
||||
{
|
||||
if (buttonState != newState)
|
||||
{
|
||||
buttonState = newState;
|
||||
repaint();
|
||||
|
||||
if (buttonState == buttonDown)
|
||||
{
|
||||
buttonPressTime = Time::getApproximateMillisecondCounter();
|
||||
lastRepeatTime = 0;
|
||||
}
|
||||
|
||||
sendStateMessage();
|
||||
}
|
||||
}
|
||||
|
||||
bool Button::isDown() const noexcept { return buttonState == buttonDown; }
|
||||
bool Button::isOver() const noexcept { return buttonState != buttonNormal; }
|
||||
|
||||
void Button::buttonStateChanged() {}
|
||||
|
||||
uint32 Button::getMillisecondsSinceButtonDown() const noexcept
|
||||
{
|
||||
auto now = Time::getApproximateMillisecondCounter();
|
||||
return now > buttonPressTime ? now - buttonPressTime : 0;
|
||||
}
|
||||
|
||||
void Button::setTriggeredOnMouseDown (bool isTriggeredOnMouseDown) noexcept
|
||||
{
|
||||
triggerOnMouseDown = isTriggeredOnMouseDown;
|
||||
}
|
||||
|
||||
bool Button::getTriggeredOnMouseDown() const noexcept
|
||||
{
|
||||
return triggerOnMouseDown;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::clicked()
|
||||
{
|
||||
}
|
||||
|
||||
void Button::clicked (const ModifierKeys&)
|
||||
{
|
||||
clicked();
|
||||
}
|
||||
|
||||
enum { clickMessageId = 0x2f3f4f99 };
|
||||
|
||||
void Button::triggerClick()
|
||||
{
|
||||
postCommandMessage (clickMessageId);
|
||||
}
|
||||
|
||||
void Button::internalClickCallback (const ModifierKeys& modifiers)
|
||||
{
|
||||
if (clickTogglesState)
|
||||
{
|
||||
const bool shouldBeOn = (radioGroupId != 0 || ! lastToggleState);
|
||||
|
||||
if (shouldBeOn != getToggleState())
|
||||
{
|
||||
setToggleState (shouldBeOn, sendNotification);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sendClickMessage (modifiers);
|
||||
}
|
||||
|
||||
void Button::flashButtonState()
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
needsToRelease = true;
|
||||
setState (buttonDown);
|
||||
callbackHelper->startTimer (100);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::handleCommandMessage (int commandId)
|
||||
{
|
||||
if (commandId == clickMessageId)
|
||||
{
|
||||
if (isEnabled())
|
||||
{
|
||||
flashButtonState();
|
||||
internalClickCallback (ModifierKeys::currentModifiers);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::handleCommandMessage (commandId);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::addListener (Listener* l) { buttonListeners.add (l); }
|
||||
void Button::removeListener (Listener* l) { buttonListeners.remove (l); }
|
||||
|
||||
void Button::sendClickMessage (const ModifierKeys& modifiers)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
if (commandManagerToUse != nullptr && commandID != 0)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromButton;
|
||||
info.originatingComponent = this;
|
||||
|
||||
commandManagerToUse->invoke (info, true);
|
||||
}
|
||||
|
||||
clicked (modifiers);
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
buttonListeners.callChecked (checker, [this] (Listener& l) { l.buttonClicked (this); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
NullCheckedInvocation::invoke (onClick);
|
||||
}
|
||||
|
||||
void Button::sendStateMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
buttonStateChanged();
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
buttonListeners.callChecked (checker, [this] (Listener& l) { l.buttonStateChanged (this); });
|
||||
|
||||
if (checker.shouldBailOut())
|
||||
return;
|
||||
|
||||
NullCheckedInvocation::invoke (onStateChange);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::paint (Graphics& g)
|
||||
{
|
||||
if (needsToRelease && isEnabled())
|
||||
{
|
||||
needsToRelease = false;
|
||||
needsRepainting = true;
|
||||
}
|
||||
|
||||
paintButton (g, isOver(), isDown());
|
||||
lastStatePainted = buttonState;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::mouseEnter (const MouseEvent&) { updateState (true, false); }
|
||||
void Button::mouseExit (const MouseEvent&) { updateState (false, false); }
|
||||
|
||||
void Button::mouseDown (const MouseEvent& e)
|
||||
{
|
||||
updateState (true, true);
|
||||
|
||||
if (isDown())
|
||||
{
|
||||
if (autoRepeatDelay >= 0)
|
||||
callbackHelper->startTimer (autoRepeatDelay);
|
||||
|
||||
if (triggerOnMouseDown)
|
||||
internalClickCallback (e.mods);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::mouseUp (const MouseEvent& e)
|
||||
{
|
||||
const auto wasDown = isDown();
|
||||
const auto wasOver = isOver();
|
||||
updateState (isMouseSourceOver (e), false);
|
||||
|
||||
if (wasDown && wasOver && ! triggerOnMouseDown)
|
||||
{
|
||||
if (lastStatePainted != buttonDown)
|
||||
flashButtonState();
|
||||
|
||||
WeakReference<Component> deletionWatcher (this);
|
||||
|
||||
internalClickCallback (e.mods);
|
||||
|
||||
if (deletionWatcher != nullptr)
|
||||
updateState (isMouseSourceOver (e), false);
|
||||
}
|
||||
}
|
||||
|
||||
void Button::mouseDrag (const MouseEvent& e)
|
||||
{
|
||||
auto oldState = buttonState;
|
||||
updateState (isMouseSourceOver (e), true);
|
||||
|
||||
if (autoRepeatDelay >= 0 && buttonState != oldState && isDown())
|
||||
callbackHelper->startTimer (autoRepeatSpeed);
|
||||
}
|
||||
|
||||
bool Button::isMouseSourceOver (const MouseEvent& e)
|
||||
{
|
||||
if (e.source.isTouch() || e.source.isPen())
|
||||
return getLocalBounds().toFloat().contains (e.position);
|
||||
|
||||
return isMouseOver();
|
||||
}
|
||||
|
||||
void Button::focusGained (FocusChangeType)
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Button::focusLost (FocusChangeType)
|
||||
{
|
||||
updateState();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Button::visibilityChanged()
|
||||
{
|
||||
needsToRelease = false;
|
||||
updateState();
|
||||
}
|
||||
|
||||
void Button::parentHierarchyChanged()
|
||||
{
|
||||
auto* newKeySource = shortcuts.isEmpty() ? nullptr : getTopLevelComponent();
|
||||
|
||||
if (newKeySource != keySource.get())
|
||||
{
|
||||
if (keySource != nullptr)
|
||||
keySource->removeKeyListener (callbackHelper.get());
|
||||
|
||||
keySource = newKeySource;
|
||||
|
||||
if (keySource != nullptr)
|
||||
keySource->addKeyListener (callbackHelper.get());
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setCommandToTrigger (ApplicationCommandManager* newCommandManager,
|
||||
CommandID newCommandID, bool generateTip)
|
||||
{
|
||||
commandID = newCommandID;
|
||||
generateTooltip = generateTip;
|
||||
|
||||
if (commandManagerToUse != newCommandManager)
|
||||
{
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->removeListener (callbackHelper.get());
|
||||
|
||||
commandManagerToUse = newCommandManager;
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
commandManagerToUse->addListener (callbackHelper.get());
|
||||
|
||||
// if you've got clickTogglesState turned on, you shouldn't also connect the button
|
||||
// up to be a command invoker. Instead, your command handler must flip the state of whatever
|
||||
// it is that this button represents, and the button will update its state to reflect this
|
||||
// in the applicationCommandListChanged() method.
|
||||
jassert (commandManagerToUse == nullptr || ! clickTogglesState);
|
||||
}
|
||||
|
||||
if (commandManagerToUse != nullptr)
|
||||
applicationCommandListChangeCallback();
|
||||
else
|
||||
setEnabled (true);
|
||||
}
|
||||
|
||||
void Button::applicationCommandListChangeCallback()
|
||||
{
|
||||
if (commandManagerToUse != nullptr)
|
||||
{
|
||||
ApplicationCommandInfo info (0);
|
||||
|
||||
if (commandManagerToUse->getTargetForCommand (commandID, info) != nullptr)
|
||||
{
|
||||
updateAutomaticTooltip (info);
|
||||
setEnabled ((info.flags & ApplicationCommandInfo::isDisabled) == 0);
|
||||
setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, dontSendNotification);
|
||||
}
|
||||
else
|
||||
{
|
||||
setEnabled (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::addShortcut (const KeyPress& key)
|
||||
{
|
||||
if (key.isValid())
|
||||
{
|
||||
jassert (! isRegisteredForShortcut (key)); // already registered!
|
||||
|
||||
shortcuts.add (key);
|
||||
parentHierarchyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void Button::clearShortcuts()
|
||||
{
|
||||
shortcuts.clear();
|
||||
parentHierarchyChanged();
|
||||
}
|
||||
|
||||
bool Button::isShortcutPressed() const
|
||||
{
|
||||
if (isShowing() && ! isCurrentlyBlockedByAnotherModalComponent())
|
||||
for (auto& s : shortcuts)
|
||||
if (s.isCurrentlyDown())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Button::isRegisteredForShortcut (const KeyPress& key) const
|
||||
{
|
||||
for (auto& s : shortcuts)
|
||||
if (key == s)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Button::keyStateChangedCallback()
|
||||
{
|
||||
if (! isEnabled())
|
||||
return false;
|
||||
|
||||
const bool wasDown = isKeyDown;
|
||||
isKeyDown = isShortcutPressed();
|
||||
|
||||
if (autoRepeatDelay >= 0 && (isKeyDown && ! wasDown))
|
||||
callbackHelper->startTimer (autoRepeatDelay);
|
||||
|
||||
updateState();
|
||||
|
||||
if (isEnabled() && wasDown && ! isKeyDown)
|
||||
{
|
||||
internalClickCallback (ModifierKeys::currentModifiers);
|
||||
|
||||
// (return immediately - this button may now have been deleted)
|
||||
return true;
|
||||
}
|
||||
|
||||
return wasDown || isKeyDown;
|
||||
}
|
||||
|
||||
bool Button::keyPressed (const KeyPress& key)
|
||||
{
|
||||
if (isEnabled() && key.isKeyCode (KeyPress::returnKey))
|
||||
{
|
||||
triggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Button::setRepeatSpeed (int initialDelayMillisecs,
|
||||
int repeatMillisecs,
|
||||
int minimumDelayInMillisecs) noexcept
|
||||
{
|
||||
autoRepeatDelay = initialDelayMillisecs;
|
||||
autoRepeatSpeed = repeatMillisecs;
|
||||
autoRepeatMinimumDelay = jmin (autoRepeatSpeed, minimumDelayInMillisecs);
|
||||
}
|
||||
|
||||
void Button::repeatTimerCallback()
|
||||
{
|
||||
if (needsRepainting)
|
||||
{
|
||||
callbackHelper->stopTimer();
|
||||
updateState();
|
||||
needsRepainting = false;
|
||||
}
|
||||
else if (autoRepeatSpeed > 0 && (isKeyDown || (updateState() == buttonDown)))
|
||||
{
|
||||
auto repeatSpeed = autoRepeatSpeed;
|
||||
|
||||
if (autoRepeatMinimumDelay >= 0)
|
||||
{
|
||||
auto timeHeldDown = jmin (1.0, getMillisecondsSinceButtonDown() / 4000.0);
|
||||
timeHeldDown *= timeHeldDown;
|
||||
|
||||
repeatSpeed = repeatSpeed + (int) (timeHeldDown * (autoRepeatMinimumDelay - repeatSpeed));
|
||||
}
|
||||
|
||||
repeatSpeed = jmax (1, repeatSpeed);
|
||||
|
||||
auto now = Time::getMillisecondCounter();
|
||||
|
||||
// if we've been blocked from repeating often enough, speed up the repeat timer to compensate..
|
||||
if (lastRepeatTime != 0 && (int) (now - lastRepeatTime) > repeatSpeed * 2)
|
||||
repeatSpeed = jmax (1, repeatSpeed / 2);
|
||||
|
||||
lastRepeatTime = now;
|
||||
callbackHelper->startTimer (repeatSpeed);
|
||||
|
||||
internalClickCallback (ModifierKeys::currentModifiers);
|
||||
}
|
||||
else if (! needsToRelease)
|
||||
{
|
||||
callbackHelper->stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> Button::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<detail::ButtonAccessibilityHandler> (*this, AccessibilityRole::button);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 base class for buttons.
|
||||
|
||||
This contains all the logic for button behaviours such as enabling/disabling,
|
||||
responding to shortcut keystrokes, auto-repeating when held down, toggle-buttons
|
||||
and radio groups, etc.
|
||||
|
||||
@see TextButton, DrawableButton, ToggleButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Button : public Component,
|
||||
public SettableTooltipClient
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Creates a button.
|
||||
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit Button (const String& buttonName);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
~Button() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the button's text.
|
||||
@see getButtonText
|
||||
*/
|
||||
void setButtonText (const String& newText);
|
||||
|
||||
/** Returns the text displayed in the button.
|
||||
@see setButtonText
|
||||
*/
|
||||
const String& getButtonText() const { return text; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true if the button is currently being held down.
|
||||
@see isOver
|
||||
*/
|
||||
bool isDown() const noexcept;
|
||||
|
||||
/** Returns true if the mouse is currently over the button.
|
||||
This will be also be true if the button is being held down.
|
||||
@see isDown
|
||||
*/
|
||||
bool isOver() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Indicates that the button's on/off state is toggleable.
|
||||
|
||||
By default this is false, and will only be true for ToggleButtons, buttons that
|
||||
are a part of a radio button group, and buttons for which
|
||||
getClickingTogglesState() == true, however you can use this method to manually
|
||||
indicate that a button is toggleable.
|
||||
|
||||
This will present the button as toggleable to accessibility clients and add an
|
||||
accessible "toggle" action for the button that invokes setToggleState().
|
||||
|
||||
@see ToggleButton, isToggleable, setToggleState, setClickingTogglesState, setRadioGroupId
|
||||
*/
|
||||
void setToggleable (bool shouldBeToggleable);
|
||||
|
||||
/** Returns true if the button's on/off state is toggleable.
|
||||
|
||||
@see setToggleable, setClickingTogglesState
|
||||
*/
|
||||
bool isToggleable() const noexcept { return canBeToggled || clickTogglesState; }
|
||||
|
||||
/** A button has an on/off state associated with it, and this changes that.
|
||||
|
||||
By default buttons are 'off' and for simple buttons that you click to perform
|
||||
an action you won't change this. Toggle buttons, however will want to
|
||||
change their state when turned on or off.
|
||||
|
||||
@param shouldBeOn whether to set the button's toggle state to be on or
|
||||
off. If it's a member of a button group, this will
|
||||
always try to turn it on, and to turn off any other
|
||||
buttons in the group
|
||||
@param notification determines the behaviour if the value changes - this
|
||||
can invoke a synchronous call to clicked(), but
|
||||
sendNotificationAsync is not supported
|
||||
@see getToggleState, setRadioGroupId
|
||||
*/
|
||||
void setToggleState (bool shouldBeOn, NotificationType notification);
|
||||
|
||||
/** Returns true if the button is 'on'.
|
||||
|
||||
By default buttons are 'off' and for simple buttons that you click to perform
|
||||
an action you won't change this. Toggle buttons, however will want to
|
||||
change their state when turned on or off.
|
||||
|
||||
@see setToggleState
|
||||
*/
|
||||
bool getToggleState() const noexcept { return isOn.getValue(); }
|
||||
|
||||
/** Returns the Value object that represents the button's toggle state.
|
||||
|
||||
You can use this Value object to connect the button's state to external values or setters,
|
||||
either by taking a copy of the Value, or by using Value::referTo() to make it point to
|
||||
your own Value object.
|
||||
|
||||
@see getToggleState, Value
|
||||
*/
|
||||
Value& getToggleStateValue() noexcept { return isOn; }
|
||||
|
||||
/** This tells the button to automatically flip the toggle state when
|
||||
the button is clicked.
|
||||
|
||||
If set to true, then before the clicked() callback occurs, the toggle-state
|
||||
of the button is flipped. This will also cause isToggleable() to return true.
|
||||
|
||||
@see isToggleable
|
||||
*/
|
||||
void setClickingTogglesState (bool shouldAutoToggleOnClick) noexcept;
|
||||
|
||||
/** Returns true if this button is set to be an automatic toggle-button.
|
||||
This returns the last value that was passed to setClickingTogglesState().
|
||||
*/
|
||||
bool getClickingTogglesState() const noexcept { return clickTogglesState; }
|
||||
|
||||
//==============================================================================
|
||||
/** Enables the button to act as a member of a mutually-exclusive group
|
||||
of 'radio buttons'.
|
||||
|
||||
If the group ID is set to a non-zero number, then this button will
|
||||
act as part of a group of buttons with the same ID, only one of
|
||||
which can be 'on' at the same time. Note that when it's part of
|
||||
a group, clicking a toggle-button that's 'on' won't turn it off.
|
||||
|
||||
To find other buttons with the same ID, this button will search through
|
||||
its sibling components for ToggleButtons, so all the buttons for a
|
||||
particular group must be placed inside the same parent component.
|
||||
|
||||
Set the group ID back to zero if you want it to act as a normal toggle
|
||||
button again.
|
||||
|
||||
The notification argument lets you specify how other buttons should react
|
||||
to being turned on or off in response to this call.
|
||||
|
||||
@see getRadioGroupId
|
||||
*/
|
||||
void setRadioGroupId (int newGroupId, NotificationType notification = sendNotification);
|
||||
|
||||
/** Returns the ID of the group to which this button belongs.
|
||||
(See setRadioGroupId() for an explanation of this).
|
||||
*/
|
||||
int getRadioGroupId() const noexcept { return radioGroupId; }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Used to receive callbacks when a button is clicked.
|
||||
|
||||
@see Button::addListener, Button::removeListener
|
||||
*/
|
||||
class JUCE_API Listener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~Listener() = default;
|
||||
|
||||
/** Called when the button is clicked. */
|
||||
virtual void buttonClicked (Button*) = 0;
|
||||
|
||||
/** Called when the button's state changes. */
|
||||
virtual void buttonStateChanged (Button*) {}
|
||||
};
|
||||
|
||||
/** Registers a listener to receive events when this button's state changes.
|
||||
If the listener is already registered, this will not register it again.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (Listener* newListener);
|
||||
|
||||
/** Removes a previously-registered button listener
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (Listener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** You can assign a lambda to this callback object to have it called when the button is clicked. */
|
||||
std::function<void()> onClick;
|
||||
|
||||
/** You can assign a lambda to this callback object to have it called when the button's state changes. */
|
||||
std::function<void()> onStateChange;
|
||||
|
||||
//==============================================================================
|
||||
/** Causes the button to act as if it's been clicked.
|
||||
|
||||
This will asynchronously make the button draw itself going down and up, and
|
||||
will then call back the clicked() method as if mouse was clicked on it.
|
||||
|
||||
@see clicked
|
||||
*/
|
||||
virtual void triggerClick();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a command ID for this button to automatically invoke when it's clicked.
|
||||
|
||||
When the button is pressed, it will use the given manager to trigger the
|
||||
command ID.
|
||||
|
||||
Obviously be careful that the ApplicationCommandManager doesn't get deleted
|
||||
before this button is. To disable the command triggering, call this method and
|
||||
pass nullptr as the command manager.
|
||||
|
||||
If generateTooltip is true, then the button's tooltip will be automatically
|
||||
generated based on the name of this command and its current shortcut key.
|
||||
|
||||
@see addShortcut, getCommandID
|
||||
*/
|
||||
void setCommandToTrigger (ApplicationCommandManager* commandManagerToUse,
|
||||
CommandID commandID,
|
||||
bool generateTooltip);
|
||||
|
||||
/** Returns the command ID that was set by setCommandToTrigger(). */
|
||||
CommandID getCommandID() const noexcept { return commandID; }
|
||||
|
||||
//==============================================================================
|
||||
/** Assigns a shortcut key to trigger the button.
|
||||
|
||||
The button registers itself with its top-level parent component for keypresses.
|
||||
|
||||
Note that a different way of linking buttons to keypresses is by using the
|
||||
setCommandToTrigger() method to invoke a command.
|
||||
|
||||
@see clearShortcuts
|
||||
*/
|
||||
void addShortcut (const KeyPress&);
|
||||
|
||||
/** Removes all key shortcuts that had been set for this button.
|
||||
@see addShortcut
|
||||
*/
|
||||
void clearShortcuts();
|
||||
|
||||
/** Returns true if the given keypress is a shortcut for this button.
|
||||
@see addShortcut
|
||||
*/
|
||||
bool isRegisteredForShortcut (const KeyPress&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets an auto-repeat speed for the button when it is held down.
|
||||
|
||||
(Auto-repeat is disabled by default).
|
||||
|
||||
@param initialDelayInMillisecs how long to wait after the mouse is pressed before
|
||||
triggering the next click. If this is zero, auto-repeat
|
||||
is disabled
|
||||
@param repeatDelayInMillisecs the frequently subsequent repeated clicks should be
|
||||
triggered
|
||||
@param minimumDelayInMillisecs if this is greater than 0, the auto-repeat speed will
|
||||
get faster, the longer the button is held down, up to the
|
||||
minimum interval specified here
|
||||
*/
|
||||
void setRepeatSpeed (int initialDelayInMillisecs,
|
||||
int repeatDelayInMillisecs,
|
||||
int minimumDelayInMillisecs = -1) noexcept;
|
||||
|
||||
/** Sets whether the button click should happen when the mouse is pressed or released.
|
||||
|
||||
By default the button is only considered to have been clicked when the mouse is
|
||||
released, but setting this to true will make it call the clicked() method as soon
|
||||
as the button is pressed.
|
||||
|
||||
This is useful if the button is being used to show a pop-up menu, as it allows
|
||||
the click to be used as a drag onto the menu.
|
||||
*/
|
||||
void setTriggeredOnMouseDown (bool isTriggeredOnMouseDown) noexcept;
|
||||
|
||||
/** Returns whether the button click happens when the mouse is pressed or released.
|
||||
@see setTriggeredOnMouseDown
|
||||
*/
|
||||
bool getTriggeredOnMouseDown() const noexcept;
|
||||
|
||||
/** Returns the number of milliseconds since the last time the button
|
||||
went into the 'down' state.
|
||||
*/
|
||||
uint32 getMillisecondsSinceButtonDown() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the tooltip for this button.
|
||||
@see TooltipClient, TooltipWindow
|
||||
*/
|
||||
void setTooltip (const String& newTooltip) override;
|
||||
|
||||
//==============================================================================
|
||||
/** A combination of these flags are used by setConnectedEdges(). */
|
||||
enum ConnectedEdgeFlags
|
||||
{
|
||||
ConnectedOnLeft = 1,
|
||||
ConnectedOnRight = 2,
|
||||
ConnectedOnTop = 4,
|
||||
ConnectedOnBottom = 8
|
||||
};
|
||||
|
||||
/** Hints about which edges of the button might be connected to adjoining buttons.
|
||||
|
||||
The value passed in is a bitwise combination of any of the values in the
|
||||
ConnectedEdgeFlags enum.
|
||||
|
||||
E.g. if you are placing two buttons adjacent to each other, you could use this to
|
||||
indicate which edges are touching, and the LookAndFeel might choose to drawn them
|
||||
without rounded corners on the edges that connect. It's only a hint, so the
|
||||
LookAndFeel can choose to ignore it if it's not relevant for this type of
|
||||
button.
|
||||
*/
|
||||
void setConnectedEdges (int connectedEdgeFlags);
|
||||
|
||||
/** Returns the set of flags passed into setConnectedEdges(). */
|
||||
int getConnectedEdgeFlags() const noexcept { return connectedEdgeFlags; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its left edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnLeft() const noexcept { return (connectedEdgeFlags & ConnectedOnLeft) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its right edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnRight() const noexcept { return (connectedEdgeFlags & ConnectedOnRight) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its top edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnTop() const noexcept { return (connectedEdgeFlags & ConnectedOnTop) != 0; }
|
||||
|
||||
/** Indicates whether the button adjoins another one on its bottom edge.
|
||||
@see setConnectedEdges
|
||||
*/
|
||||
bool isConnectedOnBottom() const noexcept { return (connectedEdgeFlags & ConnectedOnBottom) != 0; }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Used by setState(). */
|
||||
enum ButtonState
|
||||
{
|
||||
buttonNormal,
|
||||
buttonOver,
|
||||
buttonDown
|
||||
};
|
||||
|
||||
/** Can be used to force the button into a particular state.
|
||||
|
||||
This only changes the button's appearance, it won't trigger a click, or stop any mouse-clicks
|
||||
from happening.
|
||||
|
||||
The state that you set here will only last until it is automatically changed when the mouse
|
||||
enters or exits the button, or the mouse-button is pressed or released.
|
||||
*/
|
||||
void setState (ButtonState newState);
|
||||
|
||||
/** Returns the button's current over/down/up state. */
|
||||
ButtonState getState() const noexcept { return buttonState; }
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
button-drawing functionality.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawButtonBackground (Graphics&, Button&, const Colour& backgroundColour,
|
||||
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
|
||||
|
||||
virtual Font getTextButtonFont (TextButton&, int buttonHeight) = 0;
|
||||
virtual int getTextButtonWidthToFitText (TextButton&, int buttonHeight) = 0;
|
||||
|
||||
/** Draws the text for a TextButton. */
|
||||
virtual void drawButtonText (Graphics&, TextButton&,
|
||||
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
|
||||
|
||||
/** Draws the contents of a standard ToggleButton. */
|
||||
virtual void drawToggleButton (Graphics&, ToggleButton&,
|
||||
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
|
||||
|
||||
virtual void changeToggleButtonWidthToFitText (ToggleButton&) = 0;
|
||||
|
||||
virtual void drawTickBox (Graphics&, Component&, float x, float y, float w, float h,
|
||||
bool ticked, bool isEnabled,
|
||||
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
|
||||
|
||||
virtual void drawDrawableButton (Graphics&, DrawableButton&,
|
||||
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
[[deprecated ("This method's parameters have changed.")]]
|
||||
void setToggleState (bool, bool);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** This method is called when the button has been clicked.
|
||||
|
||||
Subclasses can override this to perform whatever actions they need to do.
|
||||
In general, you wouldn't use this method to receive clicks, but should get your callbacks
|
||||
by attaching a std::function to the onClick callback, or adding a Button::Listener.
|
||||
@see triggerClick, onClick
|
||||
*/
|
||||
virtual void clicked();
|
||||
|
||||
/** This method is called when the button has been clicked.
|
||||
|
||||
By default it just calls clicked(), but you might want to override it to handle
|
||||
things like clicking when a modifier key is pressed, etc.
|
||||
|
||||
@see ModifierKeys
|
||||
*/
|
||||
virtual void clicked (const ModifierKeys& modifiers);
|
||||
|
||||
/** Subclasses should override this to actually paint the button's contents.
|
||||
|
||||
It's better to use this than the paint method, because it gives you information
|
||||
about the over/down state of the button.
|
||||
|
||||
@param g the graphics context to use
|
||||
@param shouldDrawButtonAsHighlighted true if the button is either in the 'over' or 'down' state
|
||||
@param shouldDrawButtonAsDown true if the button should be drawn in the 'down' position
|
||||
*/
|
||||
virtual void paintButton (Graphics& g,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown) = 0;
|
||||
|
||||
/** Called when the button's up/down/over state changes.
|
||||
|
||||
Subclasses can override this if they need to do something special when the button
|
||||
goes up or down.
|
||||
|
||||
@see isDown, isOver
|
||||
*/
|
||||
virtual void buttonStateChanged();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
virtual void internalClickCallback (const ModifierKeys&);
|
||||
/** @internal */
|
||||
void handleCommandMessage (int commandId) override;
|
||||
/** @internal */
|
||||
void mouseEnter (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseExit (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDown (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseDrag (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void mouseUp (const MouseEvent&) override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
using Component::keyStateChanged;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void visibilityChanged() override;
|
||||
/** @internal */
|
||||
void focusGained (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void focusLost (FocusChangeType) override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Array<KeyPress> shortcuts;
|
||||
WeakReference<Component> keySource;
|
||||
String text;
|
||||
ListenerList<Listener> buttonListeners;
|
||||
|
||||
struct CallbackHelper;
|
||||
std::unique_ptr<CallbackHelper> callbackHelper;
|
||||
uint32 buttonPressTime = 0, lastRepeatTime = 0;
|
||||
ApplicationCommandManager* commandManagerToUse = nullptr;
|
||||
int autoRepeatDelay = -1, autoRepeatSpeed = 0, autoRepeatMinimumDelay = -1;
|
||||
int radioGroupId = 0, connectedEdgeFlags = 0;
|
||||
CommandID commandID = {};
|
||||
ButtonState buttonState = buttonNormal, lastStatePainted = buttonNormal;
|
||||
|
||||
Value isOn;
|
||||
bool canBeToggled = false;
|
||||
bool lastToggleState = false;
|
||||
bool clickTogglesState = false;
|
||||
bool needsToRelease = false;
|
||||
bool needsRepainting = false;
|
||||
bool isKeyDown = false;
|
||||
bool triggerOnMouseDown = false;
|
||||
bool generateTooltip = false;
|
||||
|
||||
void checkToggleableState (bool wasToggleable);
|
||||
|
||||
void repeatTimerCallback();
|
||||
bool keyStateChangedCallback();
|
||||
void applicationCommandListChangeCallback();
|
||||
void updateAutomaticTooltip (const ApplicationCommandInfo&);
|
||||
|
||||
ButtonState updateState();
|
||||
ButtonState updateState (bool isOver, bool isDown);
|
||||
bool isShortcutPressed() const;
|
||||
void turnOffOtherButtonsInGroup (NotificationType click, NotificationType state);
|
||||
|
||||
void flashButtonState();
|
||||
void sendClickMessage (const ModifierKeys&);
|
||||
void sendStateMessage();
|
||||
void setToggleState (bool shouldBeOn, NotificationType click, NotificationType state);
|
||||
|
||||
bool isMouseSourceOver (const MouseEvent& e);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Button)
|
||||
};
|
||||
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle)
|
||||
: Button (name), style (buttonStyle)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableButton::~DrawableButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static std::unique_ptr<Drawable> copyDrawableIfNotNull (const Drawable* const d)
|
||||
{
|
||||
if (d != nullptr)
|
||||
return d->createCopy();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void DrawableButton::setImages (const Drawable* normal,
|
||||
const Drawable* over,
|
||||
const Drawable* down,
|
||||
const Drawable* disabled,
|
||||
const Drawable* normalOn,
|
||||
const Drawable* overOn,
|
||||
const Drawable* downOn,
|
||||
const Drawable* disabledOn)
|
||||
{
|
||||
jassert (normal != nullptr); // you really need to give it at least a normal image..
|
||||
|
||||
normalImage = copyDrawableIfNotNull (normal);
|
||||
overImage = copyDrawableIfNotNull (over);
|
||||
downImage = copyDrawableIfNotNull (down);
|
||||
disabledImage = copyDrawableIfNotNull (disabled);
|
||||
normalImageOn = copyDrawableIfNotNull (normalOn);
|
||||
overImageOn = copyDrawableIfNotNull (overOn);
|
||||
downImageOn = copyDrawableIfNotNull (downOn);
|
||||
disabledImageOn = copyDrawableIfNotNull (disabledOn);
|
||||
|
||||
currentImage = nullptr;
|
||||
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableButton::setButtonStyle (const DrawableButton::ButtonStyle newStyle)
|
||||
{
|
||||
if (style != newStyle)
|
||||
{
|
||||
style = newStyle;
|
||||
buttonStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableButton::setEdgeIndent (const int numPixelsIndent)
|
||||
{
|
||||
edgeIndent = numPixelsIndent;
|
||||
repaint();
|
||||
resized();
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableButton::getImageBounds() const
|
||||
{
|
||||
auto r = getLocalBounds();
|
||||
|
||||
if (style != ImageStretched)
|
||||
{
|
||||
auto indentX = jmin (edgeIndent, proportionOfWidth (0.3f));
|
||||
auto indentY = jmin (edgeIndent, proportionOfHeight (0.3f));
|
||||
|
||||
if (shouldDrawButtonBackground())
|
||||
{
|
||||
indentX = jmax (getWidth() / 4, indentX);
|
||||
indentY = jmax (getHeight() / 4, indentY);
|
||||
}
|
||||
else if (style == ImageAboveTextLabel)
|
||||
{
|
||||
r = r.withTrimmedBottom (jmin (16, proportionOfHeight (0.25f)));
|
||||
}
|
||||
|
||||
r = r.reduced (indentX, indentY);
|
||||
}
|
||||
|
||||
return r.toFloat();
|
||||
}
|
||||
|
||||
void DrawableButton::resized()
|
||||
{
|
||||
Button::resized();
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
if (style != ImageRaw)
|
||||
{
|
||||
int transformFlags = 0;
|
||||
|
||||
if (style == ImageStretched)
|
||||
{
|
||||
transformFlags |= RectanglePlacement::stretchToFit;
|
||||
}
|
||||
else
|
||||
{
|
||||
transformFlags |= RectanglePlacement::centred;
|
||||
|
||||
if (style == ImageOnButtonBackgroundOriginalSize)
|
||||
transformFlags |= RectanglePlacement::doNotResize;
|
||||
}
|
||||
|
||||
currentImage->setTransformToFit (getImageBounds(), transformFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableButton::buttonStateChanged()
|
||||
{
|
||||
repaint();
|
||||
|
||||
Drawable* imageToDraw = nullptr;
|
||||
float opacity = 1.0f;
|
||||
|
||||
if (isEnabled())
|
||||
{
|
||||
imageToDraw = getCurrentImage();
|
||||
}
|
||||
else
|
||||
{
|
||||
imageToDraw = getToggleState() ? disabledImageOn.get()
|
||||
: disabledImage.get();
|
||||
|
||||
if (imageToDraw == nullptr)
|
||||
{
|
||||
opacity = 0.4f;
|
||||
imageToDraw = getNormalImage();
|
||||
}
|
||||
}
|
||||
|
||||
if (imageToDraw != currentImage)
|
||||
{
|
||||
removeChildComponent (currentImage);
|
||||
currentImage = imageToDraw;
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
currentImage->setInterceptsMouseClicks (false, false);
|
||||
addAndMakeVisible (currentImage);
|
||||
resized();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentImage != nullptr)
|
||||
currentImage->setAlpha (opacity);
|
||||
}
|
||||
|
||||
void DrawableButton::enablementChanged()
|
||||
{
|
||||
Button::enablementChanged();
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
void DrawableButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableButton::paintButton (Graphics& g,
|
||||
const bool shouldDrawButtonAsHighlighted,
|
||||
const bool shouldDrawButtonAsDown)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
if (shouldDrawButtonBackground())
|
||||
lf.drawButtonBackground (g, *this,
|
||||
findColour (getToggleState() ? TextButton::buttonOnColourId
|
||||
: TextButton::buttonColourId),
|
||||
shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
|
||||
else
|
||||
lf.drawDrawableButton (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Drawable* DrawableButton::getCurrentImage() const noexcept
|
||||
{
|
||||
if (isDown()) return getDownImage();
|
||||
if (isOver()) return getOverImage();
|
||||
|
||||
return getNormalImage();
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getNormalImage() const noexcept
|
||||
{
|
||||
return (getToggleState() && normalImageOn != nullptr) ? normalImageOn.get()
|
||||
: normalImage.get();
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getOverImage() const noexcept
|
||||
{
|
||||
if (getToggleState())
|
||||
{
|
||||
if (overImageOn != nullptr) return overImageOn.get();
|
||||
if (normalImageOn != nullptr) return normalImageOn.get();
|
||||
}
|
||||
|
||||
return overImage != nullptr ? overImage.get() : normalImage.get();
|
||||
}
|
||||
|
||||
Drawable* DrawableButton::getDownImage() const noexcept
|
||||
{
|
||||
if (auto* d = getToggleState() ? downImageOn.get() : downImage.get())
|
||||
return d;
|
||||
|
||||
return getOverImage();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button that displays a Drawable.
|
||||
|
||||
Up to three Drawable objects can be given to this button, to represent the
|
||||
'normal', 'over' and 'down' states.
|
||||
|
||||
@see Button
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
enum ButtonStyle
|
||||
{
|
||||
ImageFitted, /**< The button will just display the images, but will resize and centre them to fit inside it. */
|
||||
ImageRaw, /**< The button will just display the images in their normal size and position.
|
||||
This leaves it up to the caller to make sure the images are the correct size and position for the button. */
|
||||
ImageAboveTextLabel, /**< Draws the button as a text label across the bottom with the image resized and scaled to fit above it. */
|
||||
ImageOnButtonBackground, /**< Draws the button as a standard rounded-rectangle button with the image on top. The image will be resized
|
||||
to match the button's proportions.
|
||||
Note that if you use this style, the colour IDs that control the button colour are
|
||||
TextButton::buttonColourId and TextButton::buttonOnColourId. */
|
||||
ImageOnButtonBackgroundOriginalSize, /** Same as ImageOnButtonBackground, but keeps the original image size. */
|
||||
ImageStretched /**< Fills the button with a stretched version of the image. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a DrawableButton.
|
||||
|
||||
After creating one of these, use setImages() to specify the drawables to use.
|
||||
|
||||
@param buttonName the name to give the component
|
||||
@param buttonStyle the layout to use
|
||||
|
||||
@see ButtonStyle, setButtonStyle, setImages
|
||||
*/
|
||||
DrawableButton (const String& buttonName,
|
||||
ButtonStyle buttonStyle);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up the images to draw for the various button states.
|
||||
|
||||
The button will keep its own internal copies of these drawables.
|
||||
|
||||
@param normalImage the thing to draw for the button's 'normal' state. An internal copy
|
||||
will be made of the object passed-in if it is non-null.
|
||||
@param overImage the thing to draw for the button's 'over' state - if this is
|
||||
null, the button's normal image will be used when the mouse is
|
||||
over it. An internal copy will be made of the object passed-in
|
||||
if it is non-null.
|
||||
@param downImage the thing to draw for the button's 'down' state - if this is
|
||||
null, the 'over' image will be used instead (or the normal image
|
||||
as a last resort). An internal copy will be made of the object
|
||||
passed-in if it is non-null.
|
||||
@param disabledImage an image to draw when the button is disabled. If this is null,
|
||||
the normal image will be drawn with a reduced opacity instead.
|
||||
An internal copy will be made of the object passed-in if it is
|
||||
non-null.
|
||||
@param normalImageOn same as the normalImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normal image is used instead
|
||||
@param overImageOn same as the overImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normalImageOn is drawn instead
|
||||
@param downImageOn same as the downImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the overImageOn is drawn instead
|
||||
@param disabledImageOn same as the disabledImage, but this is used when the button's toggle
|
||||
state is 'on'. If this is nullptr, the normal image will be drawn instead
|
||||
with a reduced opacity
|
||||
*/
|
||||
void setImages (const Drawable* normalImage,
|
||||
const Drawable* overImage = nullptr,
|
||||
const Drawable* downImage = nullptr,
|
||||
const Drawable* disabledImage = nullptr,
|
||||
const Drawable* normalImageOn = nullptr,
|
||||
const Drawable* overImageOn = nullptr,
|
||||
const Drawable* downImageOn = nullptr,
|
||||
const Drawable* disabledImageOn = nullptr);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the button's style.
|
||||
@see ButtonStyle
|
||||
*/
|
||||
void setButtonStyle (ButtonStyle newStyle);
|
||||
|
||||
/** Returns the current style. */
|
||||
ButtonStyle getStyle() const noexcept { return style; }
|
||||
|
||||
//==============================================================================
|
||||
/** Gives the button an optional amount of space around the edge of the drawable.
|
||||
By default there's a gap of about 3 pixels.
|
||||
*/
|
||||
void setEdgeIndent (int numPixelsIndent);
|
||||
|
||||
/** Returns the current edge indent size. */
|
||||
int getEdgeIndent() const noexcept { return edgeIndent; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the image that the button is currently displaying. */
|
||||
Drawable* getCurrentImage() const noexcept;
|
||||
|
||||
/** Returns the image that the button will use for its normal state. */
|
||||
Drawable* getNormalImage() const noexcept;
|
||||
/** Returns the image that the button will use when the mouse is over it. */
|
||||
Drawable* getOverImage() const noexcept;
|
||||
/** Returns the image that the button will use when the mouse is held down on it. */
|
||||
Drawable* getDownImage() const noexcept;
|
||||
|
||||
/** Can be overridden to specify a custom position for the image within the button. */
|
||||
virtual Rectangle<float> getImageBounds() const;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the link.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
Note that when the ImageOnButtonBackground style is used, the colour IDs that control
|
||||
the button colour are TextButton::buttonColourId and TextButton::buttonOnColourId.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1004010, /**< The colour to use for the button's text label. */
|
||||
textColourOnId = 0x1004013, /**< The colour to use for the button's text when the button's toggle state is "on". */
|
||||
|
||||
backgroundColourId = 0x1004011, /**< The colour used to fill the button's background (when
|
||||
the button is toggled 'off'). Note that if you use the
|
||||
ImageOnButtonBackground style, you should use TextButton::buttonColourId
|
||||
to change the button's colour. */
|
||||
backgroundOnColourId = 0x1004012, /**< The colour used to fill the button's background (when
|
||||
the button is toggled 'on'). Note that if you use the
|
||||
ImageOnButtonBackground style, you should use TextButton::buttonOnColourId
|
||||
to change the button's colour. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
/** @internal */
|
||||
void buttonStateChanged() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool shouldDrawButtonBackground() const { return style == ImageOnButtonBackground || style == ImageOnButtonBackgroundOriginalSize; }
|
||||
|
||||
//==============================================================================
|
||||
ButtonStyle style;
|
||||
std::unique_ptr<Drawable> normalImage, overImage, downImage, disabledImage,
|
||||
normalImageOn, overImageOn, downImageOn, disabledImageOn;
|
||||
Drawable* currentImage = nullptr;
|
||||
int edgeIndent = 3;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
HyperlinkButton::HyperlinkButton (const String& linkText,
|
||||
const URL& linkURL)
|
||||
: Button (linkText),
|
||||
url (linkURL),
|
||||
font (14.0f, Font::underlined),
|
||||
resizeFont (true),
|
||||
justification (Justification::centred)
|
||||
{
|
||||
setMouseCursor (MouseCursor::PointingHandCursor);
|
||||
setTooltip (linkURL.toString (false));
|
||||
}
|
||||
|
||||
HyperlinkButton::HyperlinkButton()
|
||||
: Button (String()),
|
||||
font (14.0f, Font::underlined),
|
||||
resizeFont (true),
|
||||
justification (Justification::centred)
|
||||
{
|
||||
setMouseCursor (MouseCursor::PointingHandCursor);
|
||||
}
|
||||
|
||||
HyperlinkButton::~HyperlinkButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void HyperlinkButton::setFont (const Font& newFont,
|
||||
const bool resizeToMatchComponentHeight,
|
||||
Justification justificationType)
|
||||
{
|
||||
font = newFont;
|
||||
resizeFont = resizeToMatchComponentHeight;
|
||||
justification = justificationType;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void HyperlinkButton::setURL (const URL& newURL) noexcept
|
||||
{
|
||||
url = newURL;
|
||||
setTooltip (newURL.toString (false));
|
||||
}
|
||||
|
||||
Font HyperlinkButton::getFontToUse() const
|
||||
{
|
||||
if (resizeFont)
|
||||
return font.withHeight ((float) getHeight() * 0.7f);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
void HyperlinkButton::changeWidthToFitText()
|
||||
{
|
||||
setSize (getFontToUse().getStringWidth (getButtonText()) + 6, getHeight());
|
||||
}
|
||||
|
||||
void HyperlinkButton::setJustificationType (Justification newJustification)
|
||||
{
|
||||
if (justification != newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void HyperlinkButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void HyperlinkButton::clicked()
|
||||
{
|
||||
if (url.isWellFormed())
|
||||
url.launchInDefaultBrowser();
|
||||
}
|
||||
|
||||
void HyperlinkButton::paintButton (Graphics& g,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown)
|
||||
{
|
||||
const Colour textColour (findColour (textColourId));
|
||||
|
||||
if (isEnabled())
|
||||
g.setColour ((shouldDrawButtonAsHighlighted) ? textColour.darker ((shouldDrawButtonAsDown) ? 1.3f : 0.4f)
|
||||
: textColour);
|
||||
else
|
||||
g.setColour (textColour.withMultipliedAlpha (0.4f));
|
||||
|
||||
g.setFont (getFontToUse());
|
||||
|
||||
g.drawText (getButtonText(), getLocalBounds().reduced (1, 0),
|
||||
justification.getOnlyHorizontalFlags() | Justification::verticallyCentred,
|
||||
true);
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> HyperlinkButton::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<detail::ButtonAccessibilityHandler> (*this, AccessibilityRole::hyperlink);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button showing an underlined weblink, that will launch the link
|
||||
when it's clicked.
|
||||
|
||||
@see Button
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API HyperlinkButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a HyperlinkButton.
|
||||
|
||||
@param linkText the text that will be displayed in the button - this is
|
||||
also set as the Component's name, but the text can be
|
||||
changed later with the Button::setButtonText() method
|
||||
@param linkURL the URL to launch when the user clicks the button
|
||||
*/
|
||||
HyperlinkButton (const String& linkText,
|
||||
const URL& linkURL);
|
||||
|
||||
/** Creates a HyperlinkButton. */
|
||||
HyperlinkButton();
|
||||
|
||||
/** Destructor. */
|
||||
~HyperlinkButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the font to use for the text.
|
||||
|
||||
If resizeToMatchComponentHeight is true, the font's height will be adjusted
|
||||
to match the size of the component.
|
||||
*/
|
||||
void setFont (const Font& newFont,
|
||||
bool resizeToMatchComponentHeight,
|
||||
Justification justificationType = Justification::horizontallyCentred);
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the link.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1001f00, /**< The colour to use for the URL text. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the URL that the button will trigger. */
|
||||
void setURL (const URL& newURL) noexcept;
|
||||
|
||||
/** Returns the URL that the button will trigger. */
|
||||
const URL& getURL() const noexcept { return url; }
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes the button horizontally to fit snugly around the text.
|
||||
|
||||
This won't affect the button's height.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the style of justification to be used for positioning the text.
|
||||
(The default is Justification::centred)
|
||||
*/
|
||||
void setJustificationType (Justification justification);
|
||||
|
||||
/** Returns the type of justification, as set in setJustificationType(). */
|
||||
Justification getJustificationType() const noexcept { return justification; }
|
||||
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void clicked() override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
using Button::clicked;
|
||||
Font getFontToUse() const;
|
||||
|
||||
//==============================================================================
|
||||
URL url;
|
||||
Font font;
|
||||
bool resizeFont;
|
||||
Justification justification;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HyperlinkButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ImageButton::ImageButton (const String& text_)
|
||||
: Button (text_),
|
||||
scaleImageToFit (true),
|
||||
preserveProportions (true),
|
||||
alphaThreshold (0)
|
||||
{
|
||||
}
|
||||
|
||||
ImageButton::~ImageButton()
|
||||
{
|
||||
}
|
||||
|
||||
void ImageButton::setImages (const bool resizeButtonNowToFitThisImage,
|
||||
const bool rescaleImagesWhenButtonSizeChanges,
|
||||
const bool preserveImageProportions,
|
||||
const Image& normalImage_,
|
||||
const float imageOpacityWhenNormal,
|
||||
Colour overlayColourWhenNormal,
|
||||
const Image& overImage_,
|
||||
const float imageOpacityWhenOver,
|
||||
Colour overlayColourWhenOver,
|
||||
const Image& downImage_,
|
||||
const float imageOpacityWhenDown,
|
||||
Colour overlayColourWhenDown,
|
||||
const float hitTestAlphaThreshold)
|
||||
{
|
||||
normalImage = normalImage_;
|
||||
overImage = overImage_;
|
||||
downImage = downImage_;
|
||||
|
||||
if (resizeButtonNowToFitThisImage && normalImage.isValid())
|
||||
{
|
||||
imageBounds.setSize (normalImage.getWidth(),
|
||||
normalImage.getHeight());
|
||||
|
||||
setSize (imageBounds.getWidth(), imageBounds.getHeight());
|
||||
}
|
||||
|
||||
scaleImageToFit = rescaleImagesWhenButtonSizeChanges;
|
||||
preserveProportions = preserveImageProportions;
|
||||
|
||||
normalOpacity = imageOpacityWhenNormal;
|
||||
normalOverlay = overlayColourWhenNormal;
|
||||
overOpacity = imageOpacityWhenOver;
|
||||
overOverlay = overlayColourWhenOver;
|
||||
downOpacity = imageOpacityWhenDown;
|
||||
downOverlay = overlayColourWhenDown;
|
||||
|
||||
alphaThreshold = (uint8) jlimit (0, 0xff, roundToInt (255.0f * hitTestAlphaThreshold));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
Image ImageButton::getCurrentImage() const
|
||||
{
|
||||
if (isDown() || getToggleState())
|
||||
return getDownImage();
|
||||
|
||||
if (isOver())
|
||||
return getOverImage();
|
||||
|
||||
return getNormalImage();
|
||||
}
|
||||
|
||||
Image ImageButton::getNormalImage() const
|
||||
{
|
||||
return normalImage;
|
||||
}
|
||||
|
||||
Image ImageButton::getOverImage() const
|
||||
{
|
||||
return overImage.isValid() ? overImage
|
||||
: normalImage;
|
||||
}
|
||||
|
||||
Image ImageButton::getDownImage() const
|
||||
{
|
||||
return downImage.isValid() ? downImage
|
||||
: getOverImage();
|
||||
}
|
||||
|
||||
void ImageButton::paintButton (Graphics& g,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown)
|
||||
{
|
||||
if (! isEnabled())
|
||||
{
|
||||
shouldDrawButtonAsHighlighted = false;
|
||||
shouldDrawButtonAsDown = false;
|
||||
}
|
||||
|
||||
Image im (getCurrentImage());
|
||||
|
||||
if (im.isValid())
|
||||
{
|
||||
const int iw = im.getWidth();
|
||||
const int ih = im.getHeight();
|
||||
int w = getWidth();
|
||||
int h = getHeight();
|
||||
int x = (w - iw) / 2;
|
||||
int y = (h - ih) / 2;
|
||||
|
||||
if (scaleImageToFit)
|
||||
{
|
||||
if (preserveProportions)
|
||||
{
|
||||
int newW, newH;
|
||||
const float imRatio = (float) ih / (float) iw;
|
||||
const float destRatio = (float) h / (float) w;
|
||||
|
||||
if (imRatio > destRatio)
|
||||
{
|
||||
newW = roundToInt ((float) h / imRatio);
|
||||
newH = h;
|
||||
}
|
||||
else
|
||||
{
|
||||
newW = w;
|
||||
newH = roundToInt ((float) w * imRatio);
|
||||
}
|
||||
|
||||
x = (w - newW) / 2;
|
||||
y = (h - newH) / 2;
|
||||
w = newW;
|
||||
h = newH;
|
||||
}
|
||||
else
|
||||
{
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (! scaleImageToFit)
|
||||
{
|
||||
w = iw;
|
||||
h = ih;
|
||||
}
|
||||
|
||||
imageBounds.setBounds (x, y, w, h);
|
||||
|
||||
const bool useDownImage = shouldDrawButtonAsDown || getToggleState();
|
||||
|
||||
getLookAndFeel().drawImageButton (g, &im, x, y, w, h,
|
||||
useDownImage ? downOverlay
|
||||
: (shouldDrawButtonAsHighlighted ? overOverlay
|
||||
: normalOverlay),
|
||||
useDownImage ? downOpacity
|
||||
: (shouldDrawButtonAsHighlighted ? overOpacity
|
||||
: normalOpacity),
|
||||
*this);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImageButton::hitTest (int x, int y)
|
||||
{
|
||||
if (! Component::hitTest (x, y)) // handle setInterceptsMouseClicks
|
||||
return false;
|
||||
|
||||
if (alphaThreshold == 0)
|
||||
return true;
|
||||
|
||||
Image im (getCurrentImage());
|
||||
|
||||
return im.isNull() || ((! imageBounds.isEmpty())
|
||||
&& alphaThreshold < im.getPixelAt (((x - imageBounds.getX()) * im.getWidth()) / imageBounds.getWidth(),
|
||||
((y - imageBounds.getY()) * im.getHeight()) / imageBounds.getHeight()).getAlpha());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
As the title suggests, this is a button containing an image.
|
||||
|
||||
The colour and transparency of the image can be set to vary when the
|
||||
button state changes.
|
||||
|
||||
@see Button, ShapeButton, TextButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ImageButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ImageButton.
|
||||
|
||||
Use setImage() to specify the image to use. The colours and opacities that
|
||||
are specified here can be changed later using setImages().
|
||||
|
||||
@param name the name to give the component
|
||||
*/
|
||||
explicit ImageButton (const String& name = String());
|
||||
|
||||
/** Destructor. */
|
||||
~ImageButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets up the images to draw in various states.
|
||||
|
||||
@param resizeButtonNowToFitThisImage if true, the button will be immediately
|
||||
resized to the same dimensions as the normal image
|
||||
@param rescaleImagesWhenButtonSizeChanges if true, the image will be rescaled to fit the
|
||||
button when the button's size changes
|
||||
@param preserveImageProportions if true then any rescaling of the image to fit
|
||||
the button will keep the image's x and y proportions
|
||||
correct - i.e. it won't distort its shape, although
|
||||
this might create gaps around the edges
|
||||
@param normalImage the image to use when the button is in its normal state.
|
||||
button no longer needs it.
|
||||
@param imageOpacityWhenNormal the opacity to use when drawing the normal image.
|
||||
@param overlayColourWhenNormal an overlay colour to use to fill the alpha channel of the
|
||||
normal image - if this colour is transparent, no overlay
|
||||
will be drawn. The overlay will be drawn over the top of the
|
||||
image, so you can basically add a solid or semi-transparent
|
||||
colour to the image to brighten or darken it
|
||||
@param overImage the image to use when the mouse is over the button. If
|
||||
you want to use the same image as was set in the normalImage
|
||||
parameter, this value can be a null image.
|
||||
@param imageOpacityWhenOver the opacity to use when drawing the image when the mouse
|
||||
is over the button
|
||||
@param overlayColourWhenOver an overlay colour to use to fill the alpha channel of the
|
||||
image when the mouse is over - if this colour is transparent,
|
||||
no overlay will be drawn
|
||||
@param downImage an image to use when the button is pressed down. If set
|
||||
to a null image, the 'over' image will be drawn instead (or the
|
||||
normal image if there isn't an 'over' image either).
|
||||
@param imageOpacityWhenDown the opacity to use when drawing the image when the button
|
||||
is pressed
|
||||
@param overlayColourWhenDown an overlay colour to use to fill the alpha channel of the
|
||||
image when the button is pressed down - if this colour is
|
||||
transparent, no overlay will be drawn
|
||||
@param hitTestAlphaThreshold if set to zero, the mouse is considered to be over the button
|
||||
whenever it's inside the button's bounding rectangle. If
|
||||
set to values higher than 0, the mouse will only be
|
||||
considered to be over the image when the value of the
|
||||
image's alpha channel at that position is greater than
|
||||
this level.
|
||||
*/
|
||||
void setImages (bool resizeButtonNowToFitThisImage,
|
||||
bool rescaleImagesWhenButtonSizeChanges,
|
||||
bool preserveImageProportions,
|
||||
const Image& normalImage,
|
||||
float imageOpacityWhenNormal,
|
||||
Colour overlayColourWhenNormal,
|
||||
const Image& overImage,
|
||||
float imageOpacityWhenOver,
|
||||
Colour overlayColourWhenOver,
|
||||
const Image& downImage,
|
||||
float imageOpacityWhenDown,
|
||||
Colour overlayColourWhenDown,
|
||||
float hitTestAlphaThreshold = 0.0f);
|
||||
|
||||
/** Returns the currently set 'normal' image. */
|
||||
Image getNormalImage() const;
|
||||
|
||||
/** Returns the image that's drawn when the mouse is over the button.
|
||||
|
||||
If a valid 'over' image has been set, this will return it; otherwise it'll
|
||||
just return the normal image.
|
||||
*/
|
||||
Image getOverImage() const;
|
||||
|
||||
/** Returns the image that's drawn when the button is held down.
|
||||
|
||||
If a valid 'down' image has been set, this will return it; otherwise it'll
|
||||
return the 'over' image or normal image, depending on what's available.
|
||||
*/
|
||||
Image getDownImage() const;
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes. */
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
virtual void drawImageButton (Graphics&, Image*,
|
||||
int imageX, int imageY, int imageW, int imageH,
|
||||
const Colour& overlayColour, float imageOpacity, ImageButton&) = 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool scaleImageToFit, preserveProportions;
|
||||
uint8 alphaThreshold;
|
||||
Rectangle<int> imageBounds;
|
||||
Image normalImage, overImage, downImage;
|
||||
float normalOpacity, overOpacity, downOpacity;
|
||||
Colour normalOverlay, overOverlay, downOverlay;
|
||||
|
||||
Image getCurrentImage() const;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImageButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ShapeButton::ShapeButton (const String& t, Colour n, Colour o, Colour d)
|
||||
: Button (t),
|
||||
normalColour (n), overColour (o), downColour (d),
|
||||
normalColourOn (n), overColourOn (o), downColourOn (d),
|
||||
useOnColours (false),
|
||||
maintainShapeProportions (false),
|
||||
outlineWidth (0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
ShapeButton::~ShapeButton() {}
|
||||
|
||||
void ShapeButton::setColours (Colour newNormalColour, Colour newOverColour, Colour newDownColour)
|
||||
{
|
||||
normalColour = newNormalColour;
|
||||
overColour = newOverColour;
|
||||
downColour = newDownColour;
|
||||
}
|
||||
|
||||
void ShapeButton::setOnColours (Colour newNormalColourOn, Colour newOverColourOn, Colour newDownColourOn)
|
||||
{
|
||||
normalColourOn = newNormalColourOn;
|
||||
overColourOn = newOverColourOn;
|
||||
downColourOn = newDownColourOn;
|
||||
}
|
||||
|
||||
void ShapeButton::shouldUseOnColours (bool shouldUse)
|
||||
{
|
||||
useOnColours = shouldUse;
|
||||
}
|
||||
|
||||
void ShapeButton::setOutline (Colour newOutlineColour, const float newOutlineWidth)
|
||||
{
|
||||
outlineColour = newOutlineColour;
|
||||
outlineWidth = newOutlineWidth;
|
||||
}
|
||||
|
||||
void ShapeButton::setBorderSize (BorderSize<int> newBorder)
|
||||
{
|
||||
border = newBorder;
|
||||
}
|
||||
|
||||
void ShapeButton::setShape (const Path& newShape,
|
||||
const bool resizeNowToFitThisShape,
|
||||
const bool maintainShapeProportions_,
|
||||
const bool hasShadow)
|
||||
{
|
||||
shape = newShape;
|
||||
maintainShapeProportions = maintainShapeProportions_;
|
||||
|
||||
shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.5f), 3, Point<int>()));
|
||||
setComponentEffect (hasShadow ? &shadow : nullptr);
|
||||
|
||||
if (resizeNowToFitThisShape)
|
||||
{
|
||||
auto newBounds = shape.getBounds();
|
||||
|
||||
if (hasShadow)
|
||||
newBounds = newBounds.expanded (4.0f);
|
||||
|
||||
shape.applyTransform (AffineTransform::translation (-newBounds.getX(),
|
||||
-newBounds.getY()));
|
||||
|
||||
setSize (1 + (int) (newBounds.getWidth() + outlineWidth) + border.getLeftAndRight(),
|
||||
1 + (int) (newBounds.getHeight() + outlineWidth) + border.getTopAndBottom());
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void ShapeButton::paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
|
||||
{
|
||||
if (! isEnabled())
|
||||
{
|
||||
shouldDrawButtonAsHighlighted = false;
|
||||
shouldDrawButtonAsDown = false;
|
||||
}
|
||||
|
||||
auto r = border.subtractedFrom (getLocalBounds())
|
||||
.toFloat()
|
||||
.reduced (outlineWidth * 0.5f);
|
||||
|
||||
if (getComponentEffect() != nullptr)
|
||||
r = r.reduced (2.0f);
|
||||
|
||||
if (shouldDrawButtonAsDown)
|
||||
{
|
||||
const float sizeReductionWhenPressed = 0.04f;
|
||||
|
||||
r = r.reduced (sizeReductionWhenPressed * r.getWidth(),
|
||||
sizeReductionWhenPressed * r.getHeight());
|
||||
}
|
||||
|
||||
auto trans = shape.getTransformToScaleToFit (r, maintainShapeProportions);
|
||||
|
||||
if (shouldDrawButtonAsDown) g.setColour (getToggleState() && useOnColours ? downColourOn : downColour);
|
||||
else if (shouldDrawButtonAsHighlighted) g.setColour (getToggleState() && useOnColours ? overColourOn : overColour);
|
||||
else g.setColour (getToggleState() && useOnColours ? normalColourOn : normalColour);
|
||||
|
||||
g.fillPath (shape, trans);
|
||||
|
||||
if (outlineWidth > 0.0f)
|
||||
{
|
||||
g.setColour (outlineColour);
|
||||
g.strokePath (shape, PathStrokeType (outlineWidth), trans);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button that contains a filled shape.
|
||||
|
||||
@see Button, ImageButton, TextButton, ArrowButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ShapeButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ShapeButton.
|
||||
|
||||
@param name a name to give the component - see Component::setName()
|
||||
@param normalColour the colour to fill the shape with when the mouse isn't over
|
||||
@param overColour the colour to use when the mouse is over the shape
|
||||
@param downColour the colour to use when the button is in the pressed-down state
|
||||
*/
|
||||
ShapeButton (const String& name,
|
||||
Colour normalColour,
|
||||
Colour overColour,
|
||||
Colour downColour);
|
||||
|
||||
/** Destructor. */
|
||||
~ShapeButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the shape to use.
|
||||
|
||||
@param newShape the shape to use
|
||||
@param resizeNowToFitThisShape if true, the button will be resized to fit the shape's bounds
|
||||
@param maintainShapeProportions if true, the shape's proportions will be kept fixed when
|
||||
the button is resized
|
||||
@param hasDropShadow if true, the button will be given a drop-shadow effect
|
||||
*/
|
||||
void setShape (const Path& newShape,
|
||||
bool resizeNowToFitThisShape,
|
||||
bool maintainShapeProportions,
|
||||
bool hasDropShadow);
|
||||
|
||||
/** Set the colours to use for drawing the shape.
|
||||
|
||||
@param normalColour the colour to fill the shape with when the mouse isn't over
|
||||
@param overColour the colour to use when the mouse is over the shape
|
||||
@param downColour the colour to use when the button is in the pressed-down state
|
||||
*/
|
||||
void setColours (Colour normalColour,
|
||||
Colour overColour,
|
||||
Colour downColour);
|
||||
|
||||
/** Sets the colours to use for drawing the shape when the button's toggle state is 'on'. To enable this behaviour, use the
|
||||
shouldUseOnColours() method.
|
||||
|
||||
@param normalColourOn the colour to fill the shape with when the mouse isn't over and the button's toggle state is 'on'
|
||||
@param overColourOn the colour to use when the mouse is over the shape and the button's toggle state is 'on'
|
||||
@param downColourOn the colour to use when the button is in the pressed-down state and the button's toggle state is 'on'
|
||||
*/
|
||||
void setOnColours (Colour normalColourOn,
|
||||
Colour overColourOn,
|
||||
Colour downColourOn);
|
||||
|
||||
/** Set whether the button should use the 'on' set of colours when its toggle state is 'on'.
|
||||
By default these will be the same as the normal colours but the setOnColours method can be
|
||||
used to provide a different set of colours.
|
||||
*/
|
||||
void shouldUseOnColours (bool shouldUse);
|
||||
|
||||
/** Sets up an outline to draw around the shape.
|
||||
|
||||
@param outlineColour the colour to use
|
||||
@param outlineStrokeWidth the thickness of line to draw
|
||||
*/
|
||||
void setOutline (Colour outlineColour, float outlineStrokeWidth);
|
||||
|
||||
/** This lets you specify a border to be left around the edge of the button when
|
||||
drawing the shape.
|
||||
*/
|
||||
void setBorderSize (BorderSize<int> border);
|
||||
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Colour normalColour, overColour, downColour,
|
||||
normalColourOn, overColourOn, downColourOn, outlineColour;
|
||||
bool useOnColours;
|
||||
DropShadowEffect shadow;
|
||||
Path shape;
|
||||
BorderSize<int> border;
|
||||
bool maintainShapeProportions;
|
||||
float outlineWidth;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ShapeButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
TextButton::TextButton() : Button (String())
|
||||
{
|
||||
}
|
||||
|
||||
TextButton::TextButton (const String& name) : Button (name)
|
||||
{
|
||||
}
|
||||
|
||||
TextButton::TextButton (const String& name, const String& toolTip) : Button (name)
|
||||
{
|
||||
setTooltip (toolTip);
|
||||
}
|
||||
|
||||
TextButton::~TextButton()
|
||||
{
|
||||
}
|
||||
|
||||
void TextButton::paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
|
||||
{
|
||||
auto& lf = getLookAndFeel();
|
||||
|
||||
lf.drawButtonBackground (g, *this,
|
||||
findColour (getToggleState() ? buttonOnColourId : buttonColourId),
|
||||
shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
|
||||
|
||||
lf.drawButtonText (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
|
||||
}
|
||||
|
||||
void TextButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
void TextButton::changeWidthToFitText()
|
||||
{
|
||||
changeWidthToFitText (getHeight());
|
||||
}
|
||||
|
||||
void TextButton::changeWidthToFitText (const int newHeight)
|
||||
{
|
||||
setSize (getBestWidthForHeight (newHeight), newHeight);
|
||||
}
|
||||
|
||||
int TextButton::getBestWidthForHeight (int buttonHeight)
|
||||
{
|
||||
return getLookAndFeel().getTextButtonWidthToFitText (*this, buttonHeight);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button that uses the standard lozenge-shaped background with a line of
|
||||
text on it.
|
||||
|
||||
@see Button, DrawableButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API TextButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a TextButton. */
|
||||
TextButton();
|
||||
|
||||
/** Creates a TextButton.
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit TextButton (const String& buttonName);
|
||||
|
||||
/** Creates a TextButton.
|
||||
@param buttonName the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
@param toolTip an optional string to use as a tooltip
|
||||
*/
|
||||
TextButton (const String& buttonName, const String& toolTip);
|
||||
|
||||
/** Destructor. */
|
||||
~TextButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the button.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
buttonColourId = 0x1000100, /**< The colour used to fill the button shape (when the button is toggled
|
||||
'off'). The look-and-feel class might re-interpret this to add
|
||||
effects, etc. */
|
||||
buttonOnColourId = 0x1000101, /**< The colour used to fill the button shape (when the button is toggled
|
||||
'on'). The look-and-feel class might re-interpret this to add
|
||||
effects, etc. */
|
||||
textColourOffId = 0x1000102, /**< The colour to use for the button's text when the button's toggle state is "off". */
|
||||
textColourOnId = 0x1000103 /**< The colour to use for the button's text.when the button's toggle state is "on". */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Changes this button's width to fit neatly around its current text, without
|
||||
changing its height.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
/** Resizes the button's width to fit neatly around its current text, and gives it
|
||||
the specified height.
|
||||
*/
|
||||
void changeWidthToFitText (int newHeight);
|
||||
|
||||
/** Returns the width that the LookAndFeel suggests would be best for this button if it
|
||||
had the given height.
|
||||
*/
|
||||
int getBestWidthForHeight (int buttonHeight);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ToggleButton::ToggleButton()
|
||||
: Button (String())
|
||||
{
|
||||
setClickingTogglesState (true);
|
||||
}
|
||||
|
||||
ToggleButton::ToggleButton (const String& buttonText)
|
||||
: Button (buttonText)
|
||||
{
|
||||
setClickingTogglesState (true);
|
||||
}
|
||||
|
||||
ToggleButton::~ToggleButton()
|
||||
{
|
||||
}
|
||||
|
||||
void ToggleButton::paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown)
|
||||
{
|
||||
getLookAndFeel().drawToggleButton (g, *this, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown);
|
||||
}
|
||||
|
||||
void ToggleButton::changeWidthToFitText()
|
||||
{
|
||||
getLookAndFeel().changeToggleButtonWidthToFitText (*this);
|
||||
}
|
||||
|
||||
void ToggleButton::colourChanged()
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
|
||||
std::unique_ptr<AccessibilityHandler> ToggleButton::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<detail::ButtonAccessibilityHandler> (*this, AccessibilityRole::toggleButton);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 button that can be toggled on/off.
|
||||
|
||||
All buttons can be toggle buttons, but this lets you create one of the
|
||||
standard ones which has a tick-box and a text label next to it.
|
||||
|
||||
@see Button, DrawableButton, TextButton
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ToggleButton : public Button
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ToggleButton. */
|
||||
ToggleButton();
|
||||
|
||||
/** Creates a ToggleButton.
|
||||
|
||||
@param buttonText the text to put in the button (the component's name is also
|
||||
initially set to this string, but these can be changed later
|
||||
using the setName() and setButtonText() methods)
|
||||
*/
|
||||
explicit ToggleButton (const String& buttonText);
|
||||
|
||||
/** Destructor. */
|
||||
~ToggleButton() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Resizes the button to fit neatly around its current text.
|
||||
The button's height won't be affected, only its width.
|
||||
*/
|
||||
void changeWidthToFitText();
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the button.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
textColourId = 0x1006501, /**< The colour to use for the button's text. */
|
||||
tickColourId = 0x1006502, /**< The colour to use for the tick mark. */
|
||||
tickDisabledColourId = 0x1006503 /**< The colour to use for the disabled tick mark and/or outline. */
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paintButton (Graphics&, bool, bool) override;
|
||||
/** @internal */
|
||||
void colourChanged() override;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToggleButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ToolbarButton::ToolbarButton (const int iid, const String& buttonText,
|
||||
std::unique_ptr<Drawable> normalIm,
|
||||
std::unique_ptr<Drawable> toggledOnIm)
|
||||
: ToolbarItemComponent (iid, buttonText, true),
|
||||
normalImage (std::move (normalIm)),
|
||||
toggledOnImage (std::move (toggledOnIm))
|
||||
{
|
||||
jassert (normalImage != nullptr);
|
||||
}
|
||||
|
||||
ToolbarButton::~ToolbarButton()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ToolbarButton::getToolbarItemSizes (int toolbarDepth, bool /*isToolbarVertical*/, int& preferredSize, int& minSize, int& maxSize)
|
||||
{
|
||||
preferredSize = minSize = maxSize = toolbarDepth;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ToolbarButton::paintButtonArea (Graphics&, int /*width*/, int /*height*/, bool /*isMouseOver*/, bool /*isMouseDown*/)
|
||||
{
|
||||
}
|
||||
|
||||
void ToolbarButton::contentAreaChanged (const Rectangle<int>&)
|
||||
{
|
||||
buttonStateChanged();
|
||||
}
|
||||
|
||||
void ToolbarButton::setCurrentImage (Drawable* const newImage)
|
||||
{
|
||||
if (newImage != currentImage)
|
||||
{
|
||||
removeChildComponent (currentImage);
|
||||
currentImage = newImage;
|
||||
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
enablementChanged();
|
||||
addAndMakeVisible (currentImage);
|
||||
updateDrawable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarButton::updateDrawable()
|
||||
{
|
||||
if (currentImage != nullptr)
|
||||
{
|
||||
currentImage->setInterceptsMouseClicks (false, false);
|
||||
currentImage->setTransformToFit (getContentArea().toFloat(), RectanglePlacement::centred);
|
||||
currentImage->setAlpha (isEnabled() ? 1.0f : 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolbarButton::resized()
|
||||
{
|
||||
ToolbarItemComponent::resized();
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
void ToolbarButton::enablementChanged()
|
||||
{
|
||||
ToolbarItemComponent::enablementChanged();
|
||||
updateDrawable();
|
||||
}
|
||||
|
||||
Drawable* ToolbarButton::getImageToUse() const
|
||||
{
|
||||
if (getStyle() == Toolbar::textOnly)
|
||||
return nullptr;
|
||||
|
||||
if (getToggleState() && toggledOnImage != nullptr)
|
||||
return toggledOnImage.get();
|
||||
|
||||
return normalImage.get();
|
||||
}
|
||||
|
||||
void ToolbarButton::buttonStateChanged()
|
||||
{
|
||||
setCurrentImage (getImageToUse());
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 type of button designed to go on a toolbar.
|
||||
|
||||
This simple button can have two Drawable objects specified - one for normal
|
||||
use and another one (optionally) for the button's "on" state if it's a
|
||||
toggle button.
|
||||
|
||||
@see Toolbar, ToolbarItemFactory, ToolbarItemComponent, Drawable, Button
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ToolbarButton : public ToolbarItemComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a ToolbarButton.
|
||||
|
||||
@param itemId the ID for this toolbar item type. This is passed through to the
|
||||
ToolbarItemComponent constructor
|
||||
@param labelText the text to display on the button (if the toolbar is using a style
|
||||
that shows text labels). This is passed through to the
|
||||
ToolbarItemComponent constructor
|
||||
@param normalImage a drawable object that the button should use as its icon. The object
|
||||
that is passed-in here will be kept by this object and will be
|
||||
deleted when no longer needed or when this button is deleted.
|
||||
@param toggledOnImage a drawable object that the button can use as its icon if the button
|
||||
is in a toggled-on state (see the Button::getToggleState() method). If
|
||||
nullptr is passed-in here, then the normal image will be used instead,
|
||||
regardless of the toggle state. The object that is passed-in here will be
|
||||
owned by this object and will be deleted when no longer needed or when
|
||||
this button is deleted.
|
||||
*/
|
||||
ToolbarButton (int itemId,
|
||||
const String& labelText,
|
||||
std::unique_ptr<Drawable> normalImage,
|
||||
std::unique_ptr<Drawable> toggledOnImage);
|
||||
|
||||
/** Destructor. */
|
||||
~ToolbarButton() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool getToolbarItemSizes (int toolbarDepth, bool isToolbarVertical, int& preferredSize,
|
||||
int& minSize, int& maxSize) override;
|
||||
/** @internal */
|
||||
void paintButtonArea (Graphics&, int width, int height, bool isMouseOver, bool isMouseDown) override;
|
||||
/** @internal */
|
||||
void contentAreaChanged (const Rectangle<int>&) override;
|
||||
/** @internal */
|
||||
void buttonStateChanged() override;
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void enablementChanged() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<Drawable> normalImage, toggledOnImage;
|
||||
Drawable* currentImage = nullptr;
|
||||
|
||||
void updateDrawable();
|
||||
Drawable* getImageToUse() const;
|
||||
void setCurrentImage (Drawable*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarButton)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 type used to hold the unique ID for an application command.
|
||||
|
||||
This is a numeric type, so it can be stored as an integer.
|
||||
|
||||
@see ApplicationCommandInfo, ApplicationCommandManager,
|
||||
ApplicationCommandTarget, KeyPressMappingSet
|
||||
*/
|
||||
using CommandID = int;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of general-purpose application command IDs.
|
||||
|
||||
Because these commands are likely to be used in most apps, they're defined
|
||||
here to help different apps to use the same numeric values for them.
|
||||
|
||||
Of course you don't have to use these, but some of them are used internally by
|
||||
JUCE - e.g. the quit ID is recognised as a command by the JUCEApplication class.
|
||||
|
||||
@see ApplicationCommandInfo, ApplicationCommandManager,
|
||||
ApplicationCommandTarget, KeyPressMappingSet
|
||||
*/
|
||||
namespace StandardApplicationCommandIDs
|
||||
{
|
||||
enum
|
||||
{
|
||||
/** This command ID should be used to send a "Quit the App" command.
|
||||
|
||||
This command is recognised by the JUCEApplication class, so if it is invoked
|
||||
and no other ApplicationCommandTarget handles the event first, the JUCEApplication
|
||||
object will catch it and call JUCEApplicationBase::systemRequestedQuit().
|
||||
*/
|
||||
quit = 0x1001,
|
||||
|
||||
/** The command ID that should be used to send a "Delete" command. */
|
||||
del = 0x1002,
|
||||
|
||||
/** The command ID that should be used to send a "Cut" command. */
|
||||
cut = 0x1003,
|
||||
|
||||
/** The command ID that should be used to send a "Copy to clipboard" command. */
|
||||
copy = 0x1004,
|
||||
|
||||
/** The command ID that should be used to send a "Paste from clipboard" command. */
|
||||
paste = 0x1005,
|
||||
|
||||
/** The command ID that should be used to send a "Select all" command. */
|
||||
selectAll = 0x1006,
|
||||
|
||||
/** The command ID that should be used to send a "Deselect all" command. */
|
||||
deselectAll = 0x1007,
|
||||
|
||||
/** The command ID that should be used to send a "undo" command. */
|
||||
undo = 0x1008,
|
||||
|
||||
/** The command ID that should be used to send a "redo" command. */
|
||||
redo = 0x1009
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ApplicationCommandInfo::ApplicationCommandInfo (const CommandID cid) noexcept
|
||||
: commandID (cid), flags (0)
|
||||
{
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setInfo (const String& shortName_,
|
||||
const String& description_,
|
||||
const String& categoryName_,
|
||||
const int flags_) noexcept
|
||||
{
|
||||
shortName = shortName_;
|
||||
description = description_;
|
||||
categoryName = categoryName_;
|
||||
flags = flags_;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setActive (const bool b) noexcept
|
||||
{
|
||||
if (b)
|
||||
flags &= ~isDisabled;
|
||||
else
|
||||
flags |= isDisabled;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::setTicked (const bool b) noexcept
|
||||
{
|
||||
if (b)
|
||||
flags |= isTicked;
|
||||
else
|
||||
flags &= ~isTicked;
|
||||
}
|
||||
|
||||
void ApplicationCommandInfo::addDefaultKeypress (const int keyCode, ModifierKeys modifiers) noexcept
|
||||
{
|
||||
defaultKeypresses.add (KeyPress (keyCode, modifiers, 0));
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Holds information describing an application command.
|
||||
|
||||
This object is used to pass information about a particular command, such as its
|
||||
name, description and other usage flags.
|
||||
|
||||
When an ApplicationCommandTarget is asked to provide information about the commands
|
||||
it can perform, this is the structure gets filled-in to describe each one.
|
||||
|
||||
@see ApplicationCommandTarget, ApplicationCommandTarget::getCommandInfo(),
|
||||
ApplicationCommandManager
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
struct JUCE_API ApplicationCommandInfo
|
||||
{
|
||||
//==============================================================================
|
||||
explicit ApplicationCommandInfo (CommandID commandID) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a number of the structures values at once.
|
||||
|
||||
The meanings of each of the parameters is described below, in the appropriate
|
||||
member variable's description.
|
||||
*/
|
||||
void setInfo (const String& shortName,
|
||||
const String& description,
|
||||
const String& categoryName,
|
||||
int flags) noexcept;
|
||||
|
||||
/** An easy way to set or remove the isDisabled bit in the structure's flags field.
|
||||
|
||||
If isActive is true, the flags member has the isDisabled bit cleared; if isActive
|
||||
is false, the bit is set.
|
||||
*/
|
||||
void setActive (bool isActive) noexcept;
|
||||
|
||||
/** An easy way to set or remove the isTicked bit in the structure's flags field.
|
||||
*/
|
||||
void setTicked (bool isTicked) noexcept;
|
||||
|
||||
/** Handy method for adding a keypress to the defaultKeypresses array.
|
||||
|
||||
This is just so you can write things like:
|
||||
@code
|
||||
myinfo.addDefaultKeypress ('s', ModifierKeys::commandModifier);
|
||||
@endcode
|
||||
instead of
|
||||
@code
|
||||
myinfo.defaultKeypresses.add (KeyPress ('s', ModifierKeys::commandModifier));
|
||||
@endcode
|
||||
*/
|
||||
void addDefaultKeypress (int keyCode, ModifierKeys modifiers) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The command's unique ID number.
|
||||
*/
|
||||
CommandID commandID;
|
||||
|
||||
/** A short name to describe the command.
|
||||
|
||||
This should be suitable for use in menus, on buttons that trigger the command, etc.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String shortName;
|
||||
|
||||
/** A longer description of the command.
|
||||
|
||||
This should be suitable for use in contexts such as a KeyMappingEditorComponent or
|
||||
pop-up tooltip describing what the command does.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String description;
|
||||
|
||||
/** A named category that the command fits into.
|
||||
|
||||
You can give your commands any category you like, and these will be displayed in
|
||||
contexts such as the KeyMappingEditorComponent, where the category is used to group
|
||||
commands together.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
String categoryName;
|
||||
|
||||
/** A list of zero or more keypresses that should be used as the default keys for
|
||||
this command.
|
||||
|
||||
Methods such as KeyPressMappingSet::resetToDefaultMappings() will use the keypresses in
|
||||
this list to initialise the default set of key-to-command mappings.
|
||||
|
||||
@see addDefaultKeypress
|
||||
*/
|
||||
Array<KeyPress> defaultKeypresses;
|
||||
|
||||
//==============================================================================
|
||||
/** Flags describing the ways in which this command should be used.
|
||||
|
||||
A bitwise-OR of these values is stored in the ApplicationCommandInfo::flags
|
||||
variable.
|
||||
*/
|
||||
enum CommandFlags
|
||||
{
|
||||
/** Indicates that the command can't currently be performed.
|
||||
|
||||
The ApplicationCommandTarget::getCommandInfo() method must set this flag if it's
|
||||
not currently permissible to perform the command. If the flag is set, then
|
||||
components that trigger the command, e.g. PopupMenu, may choose to grey-out the
|
||||
command or show themselves as not being enabled.
|
||||
|
||||
@see ApplicationCommandInfo::setActive
|
||||
*/
|
||||
isDisabled = 1 << 0,
|
||||
|
||||
/** Indicates that the command should have a tick next to it on a menu.
|
||||
|
||||
If your command is shown on a menu and this is set, it'll show a tick next to
|
||||
it. Other components such as buttons may also use this flag to indicate that it
|
||||
is a value that can be toggled, and is currently in the 'on' state.
|
||||
|
||||
@see ApplicationCommandInfo::setTicked
|
||||
*/
|
||||
isTicked = 1 << 1,
|
||||
|
||||
/** If this flag is present, then when a KeyPressMappingSet invokes the command,
|
||||
it will call the command twice, once on key-down and again on key-up.
|
||||
|
||||
@see ApplicationCommandTarget::InvocationInfo
|
||||
*/
|
||||
wantsKeyUpDownCallbacks = 1 << 2,
|
||||
|
||||
/** If this flag is present, then a KeyMappingEditorComponent will not display the
|
||||
command in its list.
|
||||
*/
|
||||
hiddenFromKeyEditor = 1 << 3,
|
||||
|
||||
/** If this flag is present, then a KeyMappingEditorComponent will display the
|
||||
command in its list, but won't allow the assigned keypress to be changed.
|
||||
*/
|
||||
readOnlyInKeyEditor = 1 << 4,
|
||||
|
||||
/** If this flag is present and the command is invoked from a keypress, then any
|
||||
buttons or menus that are also connected to the command will not flash to
|
||||
indicate that they've been triggered.
|
||||
*/
|
||||
dontTriggerVisualFeedback = 1 << 5
|
||||
};
|
||||
|
||||
/** A bitwise-OR of the values specified in the CommandFlags enum.
|
||||
|
||||
You can use the setInfo() method to quickly set this and some of the command's
|
||||
other properties.
|
||||
*/
|
||||
int flags;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ApplicationCommandManager::ApplicationCommandManager()
|
||||
{
|
||||
keyMappings.reset (new KeyPressMappingSet (*this));
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
ApplicationCommandManager::~ApplicationCommandManager()
|
||||
{
|
||||
Desktop::getInstance().removeFocusChangeListener (this);
|
||||
keyMappings.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ApplicationCommandManager::clearCommands()
|
||||
{
|
||||
commands.clear();
|
||||
keyMappings->clearAllKeyPresses();
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& newCommand)
|
||||
{
|
||||
// zero isn't a valid command ID!
|
||||
jassert (newCommand.commandID != 0);
|
||||
|
||||
// the name isn't optional!
|
||||
jassert (newCommand.shortName.isNotEmpty());
|
||||
|
||||
if (auto* command = getMutableCommandForID (newCommand.commandID))
|
||||
{
|
||||
// Trying to re-register the same command ID with different parameters can often indicate a typo.
|
||||
// This assertion is here because I've found it useful catching some mistakes, but it may also cause
|
||||
// false alarms if you're deliberately updating some flags for a command.
|
||||
jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName
|
||||
&& newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName
|
||||
&& newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses
|
||||
&& (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))
|
||||
== (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)));
|
||||
|
||||
*command = newCommand;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* newInfo = new ApplicationCommandInfo (newCommand);
|
||||
newInfo->flags &= ~ApplicationCommandInfo::isTicked;
|
||||
commands.add (newInfo);
|
||||
|
||||
keyMappings->resetToDefaultMapping (newCommand.commandID);
|
||||
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
for (int i = 0; i < commandIDs.size(); ++i)
|
||||
{
|
||||
ApplicationCommandInfo info (commandIDs.getUnchecked (i));
|
||||
target->getCommandInfo (info.commandID, info);
|
||||
|
||||
registerCommand (info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::removeCommand (const CommandID commandID)
|
||||
{
|
||||
for (int i = commands.size(); --i >= 0;)
|
||||
{
|
||||
if (commands.getUnchecked (i)->commandID == commandID)
|
||||
{
|
||||
commands.remove (i);
|
||||
triggerAsyncUpdate();
|
||||
|
||||
const Array<KeyPress> keys (keyMappings->getKeyPressesAssignedToCommand (commandID));
|
||||
|
||||
for (int j = keys.size(); --j >= 0;)
|
||||
keyMappings->removeKeyPress (keys.getReference (j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::commandStatusChanged()
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept
|
||||
{
|
||||
for (int i = commands.size(); --i >= 0;)
|
||||
if (commands.getUnchecked (i)->commandID == commandID)
|
||||
return commands.getUnchecked (i);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (CommandID commandID) const noexcept
|
||||
{
|
||||
return getMutableCommandForID (commandID);
|
||||
}
|
||||
|
||||
String ApplicationCommandManager::getNameOfCommand (CommandID commandID) const noexcept
|
||||
{
|
||||
if (auto* ci = getCommandForID (commandID))
|
||||
return ci->shortName;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
String ApplicationCommandManager::getDescriptionOfCommand (CommandID commandID) const noexcept
|
||||
{
|
||||
if (auto* ci = getCommandForID (commandID))
|
||||
return ci->description.isNotEmpty() ? ci->description
|
||||
: ci->shortName;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
StringArray ApplicationCommandManager::getCommandCategories() const
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (int i = 0; i < commands.size(); ++i)
|
||||
s.addIfNotAlreadyThere (commands.getUnchecked (i)->categoryName, false);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Array<CommandID> ApplicationCommandManager::getCommandsInCategory (const String& categoryName) const
|
||||
{
|
||||
Array<CommandID> results;
|
||||
|
||||
for (int i = 0; i < commands.size(); ++i)
|
||||
if (commands.getUnchecked (i)->categoryName == categoryName)
|
||||
results.add (commands.getUnchecked (i)->commandID);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandManager::invokeDirectly (CommandID commandID, bool asynchronously)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
|
||||
|
||||
return invoke (info, asynchronously);
|
||||
}
|
||||
|
||||
bool ApplicationCommandManager::invoke (const ApplicationCommandTarget::InvocationInfo& inf, bool asynchronously)
|
||||
{
|
||||
// This call isn't thread-safe for use from a non-UI thread without locking the message
|
||||
// manager first..
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
bool ok = false;
|
||||
ApplicationCommandInfo commandInfo (0);
|
||||
|
||||
if (auto* target = getTargetForCommand (inf.commandID, commandInfo))
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (inf);
|
||||
info.commandFlags = commandInfo.flags;
|
||||
|
||||
sendListenerInvokeCallback (info);
|
||||
ok = target->invoke (info, asynchronously);
|
||||
commandStatusChanged();
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* ApplicationCommandManager::getFirstCommandTarget (CommandID)
|
||||
{
|
||||
return firstTarget != nullptr ? firstTarget
|
||||
: findDefaultComponentTarget();
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept
|
||||
{
|
||||
firstTarget = newTarget;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (CommandID commandID,
|
||||
ApplicationCommandInfo& upToDateInfo)
|
||||
{
|
||||
auto* target = getFirstCommandTarget (commandID);
|
||||
|
||||
if (target == nullptr)
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
target = target->getTargetForCommand (commandID);
|
||||
|
||||
if (target != nullptr)
|
||||
{
|
||||
upToDateInfo.commandID = commandID;
|
||||
target->getCommandInfo (commandID, upToDateInfo);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c)
|
||||
{
|
||||
auto* target = dynamic_cast<ApplicationCommandTarget*> (c);
|
||||
|
||||
if (target == nullptr && c != nullptr)
|
||||
target = c->findParentComponentOfClass<ApplicationCommandTarget>();
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget()
|
||||
{
|
||||
auto* c = Component::getCurrentlyFocusedComponent();
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
if (auto* activeWindow = TopLevelWindow::getActiveTopLevelWindow())
|
||||
{
|
||||
if (auto* peer = activeWindow->getPeer())
|
||||
{
|
||||
c = peer->getLastFocusedSubcomponent();
|
||||
|
||||
if (c == nullptr)
|
||||
c = activeWindow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr)
|
||||
{
|
||||
auto& desktop = Desktop::getInstance();
|
||||
|
||||
// getting a bit desperate now: try all desktop comps..
|
||||
for (int i = desktop.getNumComponents(); --i >= 0;)
|
||||
if (auto* component = desktop.getComponent (i))
|
||||
if (detail::WindowingHelpers::isForegroundOrEmbeddedProcess (component))
|
||||
if (auto* peer = component->getPeer())
|
||||
if (auto* target = findTargetForComponent (peer->getLastFocusedSubcomponent()))
|
||||
return target;
|
||||
}
|
||||
|
||||
if (c != nullptr)
|
||||
{
|
||||
// if we're focused on a ResizableWindow, chances are that it's the content
|
||||
// component that really should get the event. And if not, the event will
|
||||
// still be passed up to the top level window anyway, so let's send it to the
|
||||
// content comp.
|
||||
if (auto* resizableWindow = dynamic_cast<ResizableWindow*> (c))
|
||||
if (auto* content = resizableWindow->getContentComponent())
|
||||
c = content;
|
||||
|
||||
if (auto* target = findTargetForComponent (c))
|
||||
return target;
|
||||
}
|
||||
|
||||
return JUCEApplication::getInstance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void ApplicationCommandManager::addListener (ApplicationCommandManagerListener* listener)
|
||||
{
|
||||
listeners.add (listener);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::removeListener (ApplicationCommandManagerListener* listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo& info)
|
||||
{
|
||||
listeners.call ([&] (ApplicationCommandManagerListener& l) { l.applicationCommandInvoked (info); });
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::handleAsyncUpdate()
|
||||
{
|
||||
listeners.call ([] (ApplicationCommandManagerListener& l) { l.applicationCommandListChanged(); });
|
||||
}
|
||||
|
||||
void ApplicationCommandManager::globalFocusChanged (Component*)
|
||||
{
|
||||
commandStatusChanged();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
One of these objects holds a list of all the commands your app can perform,
|
||||
and despatches these commands when needed.
|
||||
|
||||
Application commands are a good way to trigger actions in your app, e.g. "Quit",
|
||||
"Copy", "Paste", etc. Menus, buttons and keypresses can all be given commands
|
||||
to invoke automatically, which means you don't have to handle the result of a menu
|
||||
or button click manually. Commands are despatched to ApplicationCommandTarget objects
|
||||
which can choose which events they want to handle.
|
||||
|
||||
This architecture also allows for nested ApplicationCommandTargets, so that for example
|
||||
you could have two different objects, one inside the other, both of which can respond to
|
||||
a "delete" command. Depending on which one has focus, the command will be sent to the
|
||||
appropriate place, regardless of whether it was triggered by a menu, keypress or some other
|
||||
method.
|
||||
|
||||
To set up your app to use commands, you'll need to do the following:
|
||||
|
||||
- Create a global ApplicationCommandManager to hold the list of all possible
|
||||
commands. (This will also manage a set of key-mappings for them).
|
||||
|
||||
- Make some of your UI components (or other objects) inherit from ApplicationCommandTarget.
|
||||
This allows the object to provide a list of commands that it can perform, and
|
||||
to handle them.
|
||||
|
||||
- Register each type of command using ApplicationCommandManager::registerAllCommandsForTarget(),
|
||||
or ApplicationCommandManager::registerCommand().
|
||||
|
||||
- If you want key-presses to trigger your commands, use the ApplicationCommandManager::getKeyMappings()
|
||||
method to access the key-mapper object, which you will need to register as a key-listener
|
||||
in whatever top-level component you're using. See the KeyPressMappingSet class for more help
|
||||
about setting this up.
|
||||
|
||||
- Use methods such as PopupMenu::addCommandItem() or Button::setCommandToTrigger() to
|
||||
cause these commands to be invoked automatically.
|
||||
|
||||
- Commands can be invoked directly by your code using ApplicationCommandManager::invokeDirectly().
|
||||
|
||||
When a command is invoked, the ApplicationCommandManager will try to choose the best
|
||||
ApplicationCommandTarget to receive the specified command. To do this it will use the
|
||||
current keyboard focus to see which component might be interested, and will search the
|
||||
component hierarchy for those that also implement the ApplicationCommandTarget interface.
|
||||
If an ApplicationCommandTarget isn't interested in the command that is being invoked, then
|
||||
the next one in line will be tried (see the ApplicationCommandTarget::getNextCommandTarget()
|
||||
method), and so on until ApplicationCommandTarget::getNextCommandTarget() returns nullptr.
|
||||
At this point if the command still hasn't been performed, it will be passed to the current
|
||||
JUCEApplication object (which is itself an ApplicationCommandTarget).
|
||||
|
||||
To exert some custom control over which ApplicationCommandTarget is chosen to invoke a command,
|
||||
you can override the ApplicationCommandManager::getFirstCommandTarget() method and choose
|
||||
the object yourself.
|
||||
|
||||
@see ApplicationCommandTarget, ApplicationCommandInfo
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ApplicationCommandManager : private AsyncUpdater,
|
||||
private FocusChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an ApplicationCommandManager.
|
||||
|
||||
Once created, you'll need to register all your app's commands with it, using
|
||||
ApplicationCommandManager::registerAllCommandsForTarget() or
|
||||
ApplicationCommandManager::registerCommand().
|
||||
*/
|
||||
ApplicationCommandManager();
|
||||
|
||||
/** Destructor.
|
||||
|
||||
Make sure that you don't delete this if pointers to it are still being used by
|
||||
objects such as PopupMenus or Buttons.
|
||||
*/
|
||||
~ApplicationCommandManager() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Clears the current list of all commands.
|
||||
Note that this will also clear the contents of the KeyPressMappingSet.
|
||||
*/
|
||||
void clearCommands();
|
||||
|
||||
/** Adds a command to the list of registered commands.
|
||||
@see registerAllCommandsForTarget
|
||||
*/
|
||||
void registerCommand (const ApplicationCommandInfo& newCommand);
|
||||
|
||||
/** Adds all the commands that this target publishes to the manager's list.
|
||||
|
||||
This will use ApplicationCommandTarget::getAllCommands() and ApplicationCommandTarget::getCommandInfo()
|
||||
to get details about all the commands that this target can do, and will call
|
||||
registerCommand() to add each one to the manger's list.
|
||||
|
||||
@see registerCommand
|
||||
*/
|
||||
void registerAllCommandsForTarget (ApplicationCommandTarget* target);
|
||||
|
||||
/** Removes the command with a specified ID.
|
||||
Note that this will also remove any key mappings that are mapped to the command.
|
||||
*/
|
||||
void removeCommand (CommandID commandID);
|
||||
|
||||
/** This should be called to tell the manager that one of its registered commands may have changed
|
||||
its active status.
|
||||
|
||||
Because the command manager only finds out whether a command is active or inactive by querying
|
||||
the current ApplicationCommandTarget, this is used to tell it that things may have changed. It
|
||||
allows things like buttons to update their enablement, etc.
|
||||
|
||||
This method will cause an asynchronous call to ApplicationCommandManagerListener::applicationCommandListChanged()
|
||||
for any registered listeners.
|
||||
*/
|
||||
void commandStatusChanged();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of commands that have been registered.
|
||||
@see registerCommand
|
||||
*/
|
||||
int getNumCommands() const noexcept { return commands.size(); }
|
||||
|
||||
/** Returns the details about one of the registered commands.
|
||||
The index is between 0 and (getNumCommands() - 1).
|
||||
*/
|
||||
const ApplicationCommandInfo* getCommandForIndex (int index) const noexcept { return commands [index]; }
|
||||
|
||||
/** Returns the details about a given command ID.
|
||||
|
||||
This will search the list of registered commands for one with the given command
|
||||
ID number, and return its associated info. If no matching command is found, this
|
||||
will return nullptr.
|
||||
*/
|
||||
const ApplicationCommandInfo* getCommandForID (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the name field for a command.
|
||||
|
||||
An empty string is returned if no command with this ID has been registered.
|
||||
@see getDescriptionOfCommand
|
||||
*/
|
||||
String getNameOfCommand (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the description field for a command.
|
||||
|
||||
An empty string is returned if no command with this ID has been registered. If the
|
||||
command has no description, this will return its short name field instead.
|
||||
|
||||
@see getNameOfCommand
|
||||
*/
|
||||
String getDescriptionOfCommand (CommandID commandID) const noexcept;
|
||||
|
||||
/** Returns the list of categories.
|
||||
|
||||
This will go through all registered commands, and return a list of all the distinct
|
||||
categoryName values from their ApplicationCommandInfo structure.
|
||||
|
||||
@see getCommandsInCategory()
|
||||
*/
|
||||
StringArray getCommandCategories() const;
|
||||
|
||||
/** Returns a list of all the command UIDs in a particular category.
|
||||
@see getCommandCategories()
|
||||
*/
|
||||
Array<CommandID> getCommandsInCategory (const String& categoryName) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the manager's internal set of key mappings.
|
||||
|
||||
This object can be used to edit the keypresses. To actually link this object up
|
||||
to invoke commands when a key is pressed, see the comments for the KeyPressMappingSet
|
||||
class.
|
||||
|
||||
@see KeyPressMappingSet
|
||||
*/
|
||||
KeyPressMappingSet* getKeyMappings() const noexcept { return keyMappings.get(); }
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Invokes the given command directly, sending it to the default target.
|
||||
This is just an easy way to call invoke() without having to fill out the InvocationInfo
|
||||
structure.
|
||||
*/
|
||||
bool invokeDirectly (CommandID commandID, bool asynchronously);
|
||||
|
||||
/** Sends a command to the default target.
|
||||
|
||||
This will choose a target using getFirstCommandTarget(), and send the specified command
|
||||
to it using the ApplicationCommandTarget::invoke() method. This means that if the
|
||||
first target can't handle the command, it will be passed on to targets further down the
|
||||
chain (see ApplicationCommandTarget::invoke() for more info).
|
||||
|
||||
@param invocationInfo this must be correctly filled-in, describing the context for
|
||||
the invocation.
|
||||
@param asynchronously if false, the command will be performed before this method returns.
|
||||
If true, a message will be posted so that the command will be performed
|
||||
later on the message thread, and this method will return immediately.
|
||||
|
||||
@see ApplicationCommandTarget::invoke
|
||||
*/
|
||||
bool invoke (const ApplicationCommandTarget::InvocationInfo& invocationInfo,
|
||||
bool asynchronously);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Chooses the ApplicationCommandTarget to which a command should be sent.
|
||||
|
||||
Whenever the manager needs to know which target a command should be sent to, it calls
|
||||
this method to determine the first one to try.
|
||||
|
||||
By default, this method will return the target that was set by calling setFirstCommandTarget().
|
||||
If no target is set, it will return the result of findDefaultComponentTarget().
|
||||
|
||||
If you need to make sure all commands go via your own custom target, then you can
|
||||
either use setFirstCommandTarget() to specify a single target, or override this method
|
||||
if you need more complex logic to choose one.
|
||||
|
||||
It may return nullptr if no targets are available.
|
||||
|
||||
@see getTargetForCommand, invoke, invokeDirectly
|
||||
*/
|
||||
virtual ApplicationCommandTarget* getFirstCommandTarget (CommandID commandID);
|
||||
|
||||
/** Sets a target to be returned by getFirstCommandTarget().
|
||||
|
||||
If this is set to nullptr, then getFirstCommandTarget() will by default return the
|
||||
result of findDefaultComponentTarget().
|
||||
|
||||
If you use this to set a target, make sure you call setFirstCommandTarget (nullptr)
|
||||
before deleting the target object.
|
||||
*/
|
||||
void setFirstCommandTarget (ApplicationCommandTarget* newTarget) noexcept;
|
||||
|
||||
/** Tries to find the best target to use to perform a given command.
|
||||
|
||||
This will call getFirstCommandTarget() to find the preferred target, and will
|
||||
check whether that target can handle the given command. If it can't, then it'll use
|
||||
ApplicationCommandTarget::getNextCommandTarget() to find the next one to try, and
|
||||
so on until no more are available.
|
||||
|
||||
If no targets are found that can perform the command, this method will return nullptr.
|
||||
|
||||
If a target is found, then it will get the target to fill-in the upToDateInfo
|
||||
structure with the latest info about that command, so that the caller can see
|
||||
whether the command is disabled, ticked, etc.
|
||||
*/
|
||||
ApplicationCommandTarget* getTargetForCommand (CommandID commandID,
|
||||
ApplicationCommandInfo& upToDateInfo);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a listener that will be called when various events occur. */
|
||||
void addListener (ApplicationCommandManagerListener* listener);
|
||||
|
||||
/** Deregisters a previously-added listener. */
|
||||
void removeListener (ApplicationCommandManagerListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Looks for a suitable command target based on which Components have the keyboard focus.
|
||||
|
||||
This is used by the default implementation of ApplicationCommandTarget::getFirstCommandTarget(),
|
||||
but is exposed here in case it's useful.
|
||||
|
||||
It tries to pick the best ApplicationCommandTarget by looking at focused components, top level
|
||||
windows, etc., and using the findTargetForComponent() method.
|
||||
*/
|
||||
static ApplicationCommandTarget* findDefaultComponentTarget();
|
||||
|
||||
/** Examines this component and all its parents in turn, looking for the first one
|
||||
which is an ApplicationCommandTarget.
|
||||
|
||||
Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them
|
||||
implement that class.
|
||||
*/
|
||||
static ApplicationCommandTarget* findTargetForComponent (Component*);
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
OwnedArray<ApplicationCommandInfo> commands;
|
||||
ListenerList<ApplicationCommandManagerListener> listeners;
|
||||
std::unique_ptr<KeyPressMappingSet> keyMappings;
|
||||
ApplicationCommandTarget* firstTarget = nullptr;
|
||||
|
||||
void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&);
|
||||
void handleAsyncUpdate() override;
|
||||
void globalFocusChanged (Component*) override;
|
||||
ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandManager)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A listener that receives callbacks from an ApplicationCommandManager when
|
||||
commands are invoked or the command list is changed.
|
||||
|
||||
@see ApplicationCommandManager::addListener, ApplicationCommandManager::removeListener
|
||||
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ApplicationCommandManagerListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~ApplicationCommandManagerListener() = default;
|
||||
|
||||
/** Called when an app command is about to be invoked. */
|
||||
virtual void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) = 0;
|
||||
|
||||
/** Called when commands are registered or deregistered from the
|
||||
command manager, or when commands are made active or inactive.
|
||||
|
||||
Note that if you're using this to watch for changes to whether a command is disabled,
|
||||
you'll need to make sure that ApplicationCommandManager::commandStatusChanged() is called
|
||||
whenever the status of your command might have changed.
|
||||
*/
|
||||
virtual void applicationCommandListChanged() = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 ApplicationCommandTarget::CommandMessage final : public MessageManager::MessageBase
|
||||
{
|
||||
public:
|
||||
CommandMessage (ApplicationCommandTarget* const target, const InvocationInfo& inf)
|
||||
: owner (target), info (inf)
|
||||
{
|
||||
}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (ApplicationCommandTarget* const target = owner)
|
||||
target->tryToInvoke (info, false);
|
||||
}
|
||||
|
||||
private:
|
||||
WeakReference<ApplicationCommandTarget> owner;
|
||||
const InvocationInfo info;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CommandMessage)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget::ApplicationCommandTarget() {}
|
||||
ApplicationCommandTarget::~ApplicationCommandTarget() {}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandTarget::tryToInvoke (const InvocationInfo& info, const bool async)
|
||||
{
|
||||
if (isCommandActive (info.commandID))
|
||||
{
|
||||
if (async)
|
||||
{
|
||||
(new CommandMessage (this, info))->post();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (perform (info))
|
||||
return true;
|
||||
|
||||
// Hmm.. your target claimed that it could perform this command, but failed to do so.
|
||||
// If it can't do it at the moment for some reason, it should clear the 'isActive' flag
|
||||
// when it returns the command's info.
|
||||
jassertfalse;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandTarget::findFirstTargetParentComponent()
|
||||
{
|
||||
if (Component* const c = dynamic_cast<Component*> (this))
|
||||
return c->findParentComponentOfClass<ApplicationCommandTarget>();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const CommandID commandID)
|
||||
{
|
||||
ApplicationCommandTarget* target = this;
|
||||
int depth = 0;
|
||||
|
||||
while (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
if (commandIDs.contains (commandID))
|
||||
return target;
|
||||
|
||||
target = target->getNextCommandTarget();
|
||||
|
||||
++depth;
|
||||
jassert (depth < 100); // could be a recursive command chain??
|
||||
jassert (target != this); // definitely a recursive command chain!
|
||||
|
||||
if (depth > 100 || target == this)
|
||||
break;
|
||||
}
|
||||
|
||||
if (target == nullptr)
|
||||
{
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
{
|
||||
Array<CommandID> commandIDs;
|
||||
target->getAllCommands (commandIDs);
|
||||
|
||||
if (commandIDs.contains (commandID))
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ApplicationCommandTarget::isCommandActive (const CommandID commandID)
|
||||
{
|
||||
ApplicationCommandInfo info (commandID);
|
||||
info.flags = ApplicationCommandInfo::isDisabled;
|
||||
|
||||
getCommandInfo (commandID, info);
|
||||
|
||||
return (info.flags & ApplicationCommandInfo::isDisabled) == 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool ApplicationCommandTarget::invoke (const InvocationInfo& info, const bool async)
|
||||
{
|
||||
ApplicationCommandTarget* target = this;
|
||||
int depth = 0;
|
||||
|
||||
while (target != nullptr)
|
||||
{
|
||||
if (target->tryToInvoke (info, async))
|
||||
return true;
|
||||
|
||||
target = target->getNextCommandTarget();
|
||||
|
||||
++depth;
|
||||
jassert (depth < 100); // could be a recursive command chain??
|
||||
jassert (target != this); // definitely a recursive command chain!
|
||||
|
||||
if (depth > 100 || target == this)
|
||||
break;
|
||||
}
|
||||
|
||||
if (target == nullptr)
|
||||
{
|
||||
target = JUCEApplication::getInstance();
|
||||
|
||||
if (target != nullptr)
|
||||
return target->tryToInvoke (info, async);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ApplicationCommandTarget::invokeDirectly (const CommandID commandID, const bool asynchronously)
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::direct;
|
||||
|
||||
return invoke (info, asynchronously);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandTarget::InvocationInfo::InvocationInfo (const CommandID command)
|
||||
: commandID (command),
|
||||
commandFlags (0),
|
||||
invocationMethod (direct),
|
||||
originatingComponent (nullptr),
|
||||
isKeyDown (false),
|
||||
millisecsSinceKeyPressed (0)
|
||||
{
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 command target publishes a list of command IDs that it can perform.
|
||||
|
||||
An ApplicationCommandManager despatches commands to targets, which must be
|
||||
able to provide information about what commands they can handle.
|
||||
|
||||
To create a target, you'll need to inherit from this class, implementing all of
|
||||
its pure virtual methods.
|
||||
|
||||
For info about how a target is chosen to receive a command, see
|
||||
ApplicationCommandManager::getFirstCommandTarget().
|
||||
|
||||
@see ApplicationCommandManager, ApplicationCommandInfo
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ApplicationCommandTarget
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a command target. */
|
||||
ApplicationCommandTarget();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ApplicationCommandTarget();
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Contains contextual details about the invocation of a command.
|
||||
*/
|
||||
struct JUCE_API InvocationInfo
|
||||
{
|
||||
//==============================================================================
|
||||
InvocationInfo (CommandID commandID);
|
||||
|
||||
//==============================================================================
|
||||
/** The UID of the command that should be performed. */
|
||||
CommandID commandID;
|
||||
|
||||
/** The command's flags.
|
||||
See ApplicationCommandInfo for a description of these flag values.
|
||||
*/
|
||||
int commandFlags;
|
||||
|
||||
//==============================================================================
|
||||
/** The types of context in which the command might be called. */
|
||||
enum InvocationMethod
|
||||
{
|
||||
direct = 0, /**< The command is being invoked directly by a piece of code. */
|
||||
fromKeyPress, /**< The command is being invoked by a key-press. */
|
||||
fromMenu, /**< The command is being invoked by a menu selection. */
|
||||
fromButton /**< The command is being invoked by a button click. */
|
||||
};
|
||||
|
||||
/** The type of event that triggered this command. */
|
||||
InvocationMethod invocationMethod;
|
||||
|
||||
//==============================================================================
|
||||
/** If triggered by a keypress or menu, this will be the component that had the
|
||||
keyboard focus at the time.
|
||||
|
||||
If triggered by a button, it may be set to that component, or it may be null.
|
||||
*/
|
||||
Component* originatingComponent;
|
||||
|
||||
//==============================================================================
|
||||
/** The keypress that was used to invoke it.
|
||||
|
||||
Note that this will be an invalid keypress if the command was invoked
|
||||
by some other means than a keyboard shortcut.
|
||||
*/
|
||||
KeyPress keyPress;
|
||||
|
||||
/** True if the callback is being invoked when the key is pressed,
|
||||
false if the key is being released.
|
||||
|
||||
@see KeyPressMappingSet::addCommand()
|
||||
*/
|
||||
bool isKeyDown;
|
||||
|
||||
/** If the key is being released, this indicates how long it had been held
|
||||
down for.
|
||||
|
||||
(Only relevant if isKeyDown is false.)
|
||||
*/
|
||||
int millisecsSinceKeyPressed;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** This must return the next target to try after this one.
|
||||
|
||||
When a command is being sent, and the first target can't handle
|
||||
that command, this method is used to determine the next target that should
|
||||
be tried.
|
||||
|
||||
It may return nullptr if it doesn't know of another target.
|
||||
|
||||
If your target is a Component, you would usually use the findFirstTargetParentComponent()
|
||||
method to return a parent component that might want to handle it.
|
||||
|
||||
@see invoke
|
||||
*/
|
||||
virtual ApplicationCommandTarget* getNextCommandTarget() = 0;
|
||||
|
||||
/** This must return a complete list of commands that this target can handle.
|
||||
|
||||
Your target should add all the command IDs that it handles to the array that is
|
||||
passed-in.
|
||||
*/
|
||||
virtual void getAllCommands (Array<CommandID>& commands) = 0;
|
||||
|
||||
/** This must provide details about one of the commands that this target can perform.
|
||||
|
||||
This will be called with one of the command IDs that the target provided in its
|
||||
getAllCommands() methods.
|
||||
|
||||
It should fill-in all appropriate fields of the ApplicationCommandInfo structure with
|
||||
suitable information about the command. (The commandID field will already have been filled-in
|
||||
by the caller).
|
||||
|
||||
The easiest way to set the info is using the ApplicationCommandInfo::setInfo() method to
|
||||
set all the fields at once.
|
||||
|
||||
If the command is currently inactive for some reason, this method must use
|
||||
ApplicationCommandInfo::setActive() to make that clear, (or it should set the isDisabled
|
||||
bit of the ApplicationCommandInfo::flags field).
|
||||
|
||||
Any default key-presses for the command should be appended to the
|
||||
ApplicationCommandInfo::defaultKeypresses field.
|
||||
|
||||
Note that if you change something that affects the status of the commands
|
||||
that would be returned by this method (e.g. something that makes some commands
|
||||
active or inactive), you should call ApplicationCommandManager::commandStatusChanged()
|
||||
to cause the manager to refresh its status.
|
||||
*/
|
||||
virtual void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) = 0;
|
||||
|
||||
/** This must actually perform the specified command.
|
||||
|
||||
If this target is able to perform the command specified by the commandID field of the
|
||||
InvocationInfo structure, then it should do so, and must return true.
|
||||
|
||||
If it can't handle this command, it should return false, which tells the caller to pass
|
||||
the command on to the next target in line.
|
||||
|
||||
@see invoke, ApplicationCommandManager::invoke
|
||||
*/
|
||||
virtual bool perform (const InvocationInfo& info) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes this target invoke a command.
|
||||
|
||||
Your code can call this method to invoke a command on this target, but normally
|
||||
you'd call it indirectly via ApplicationCommandManager::invoke() or
|
||||
ApplicationCommandManager::invokeDirectly().
|
||||
|
||||
If this target can perform the given command, it will call its perform() method to
|
||||
do so. If not, then getNextCommandTarget() will be used to determine the next target
|
||||
to try, and the command will be passed along to it.
|
||||
|
||||
@param invocationInfo this must be correctly filled-in, describing the context for
|
||||
the invocation.
|
||||
@param asynchronously if false, the command will be performed before this method returns.
|
||||
If true, a message will be posted so that the command will be performed
|
||||
later on the message thread, and this method will return immediately.
|
||||
@see perform, ApplicationCommandManager::invoke
|
||||
*/
|
||||
bool invoke (const InvocationInfo& invocationInfo,
|
||||
bool asynchronously);
|
||||
|
||||
/** Invokes a given command directly on this target.
|
||||
|
||||
This is just an easy way to call invoke() without having to fill out the InvocationInfo
|
||||
structure.
|
||||
*/
|
||||
bool invokeDirectly (CommandID commandID,
|
||||
bool asynchronously);
|
||||
|
||||
//==============================================================================
|
||||
/** Searches this target and all subsequent ones for the first one that can handle
|
||||
the specified command.
|
||||
|
||||
This will use getNextCommandTarget() to determine the chain of targets to try
|
||||
after this one.
|
||||
*/
|
||||
ApplicationCommandTarget* getTargetForCommand (CommandID commandID);
|
||||
|
||||
/** Checks whether this command can currently be performed by this target.
|
||||
|
||||
This will return true only if a call to getCommandInfo() doesn't set the
|
||||
isDisabled flag to indicate that the command is inactive.
|
||||
*/
|
||||
bool isCommandActive (CommandID commandID);
|
||||
|
||||
/** If this object is a Component, this method will search upwards in its current
|
||||
UI hierarchy for the next parent component that implements the
|
||||
ApplicationCommandTarget class.
|
||||
|
||||
If your target is a Component, this is a very handy method to use in your
|
||||
getNextCommandTarget() implementation.
|
||||
*/
|
||||
ApplicationCommandTarget* findFirstTargetParentComponent();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
class CommandMessage;
|
||||
friend class CommandMessage;
|
||||
|
||||
bool tryToInvoke (const InvocationInfo&, bool async);
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (ApplicationCommandTarget)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ApplicationCommandTarget)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
KeyPressMappingSet::KeyPressMappingSet (ApplicationCommandManager& cm)
|
||||
: commandManager (cm)
|
||||
{
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
KeyPressMappingSet::KeyPressMappingSet (const KeyPressMappingSet& other)
|
||||
: KeyListener(), ChangeBroadcaster(), FocusChangeListener(), commandManager (other.commandManager)
|
||||
{
|
||||
Desktop::getInstance().addFocusChangeListener (this);
|
||||
}
|
||||
|
||||
KeyPressMappingSet::~KeyPressMappingSet()
|
||||
{
|
||||
Desktop::getInstance().removeFocusChangeListener (this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Array<KeyPress> KeyPressMappingSet::getKeyPressesAssignedToCommand (const CommandID commandID) const
|
||||
{
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
if (mappings.getUnchecked (i)->commandID == commandID)
|
||||
return mappings.getUnchecked (i)->keypresses;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::addKeyPress (const CommandID commandID, const KeyPress& newKeyPress, int insertIndex)
|
||||
{
|
||||
// If you specify an upper-case letter but no shift key, how is the user supposed to press it!?
|
||||
// Stick to lower-case letters when defining a keypress, to avoid ambiguity.
|
||||
jassert (! (CharacterFunctions::isUpperCase (newKeyPress.getTextCharacter())
|
||||
&& ! newKeyPress.getModifiers().isShiftDown()));
|
||||
|
||||
if (findCommandForKeyPress (newKeyPress) != commandID)
|
||||
{
|
||||
if (newKeyPress.isValid())
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked (i)->commandID == commandID)
|
||||
{
|
||||
mappings.getUnchecked (i)->keypresses.insert (insertIndex, newKeyPress);
|
||||
|
||||
sendChangeMessage();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
|
||||
{
|
||||
CommandMapping* const cm = new CommandMapping();
|
||||
cm->commandID = commandID;
|
||||
cm->keypresses.add (newKeyPress);
|
||||
cm->wantsKeyUpDownCallbacks = (ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) != 0;
|
||||
|
||||
mappings.add (cm);
|
||||
sendChangeMessage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If you hit this, you're trying to attach a keypress to a command ID that
|
||||
// doesn't exist, so the key is not being attached.
|
||||
jassertfalse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addKeyPresses (KeyPressMappingSet& set, const ApplicationCommandInfo* const ci)
|
||||
{
|
||||
for (int j = 0; j < ci->defaultKeypresses.size(); ++j)
|
||||
set.addKeyPress (ci->commandID, ci->defaultKeypresses.getReference (j));
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::resetToDefaultMappings()
|
||||
{
|
||||
mappings.clear();
|
||||
|
||||
for (int i = 0; i < commandManager.getNumCommands(); ++i)
|
||||
addKeyPresses (*this, commandManager.getCommandForIndex (i));
|
||||
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::resetToDefaultMapping (const CommandID commandID)
|
||||
{
|
||||
clearAllKeyPresses (commandID);
|
||||
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (commandID))
|
||||
addKeyPresses (*this, ci);
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::clearAllKeyPresses()
|
||||
{
|
||||
if (mappings.size() > 0)
|
||||
{
|
||||
sendChangeMessage();
|
||||
mappings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::clearAllKeyPresses (const CommandID commandID)
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked (i)->commandID == commandID)
|
||||
{
|
||||
mappings.remove (i);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::removeKeyPress (const KeyPress& keypress)
|
||||
{
|
||||
if (keypress.isValid())
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked (i);
|
||||
|
||||
for (int j = cm.keypresses.size(); --j >= 0;)
|
||||
{
|
||||
if (keypress == cm.keypresses [j])
|
||||
{
|
||||
cm.keypresses.remove (j);
|
||||
sendChangeMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::removeKeyPress (const CommandID commandID, const int keyPressIndex)
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
if (mappings.getUnchecked (i)->commandID == commandID)
|
||||
{
|
||||
mappings.getUnchecked (i)->keypresses.remove (keyPressIndex);
|
||||
sendChangeMessage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
CommandID KeyPressMappingSet::findCommandForKeyPress (const KeyPress& keyPress) const noexcept
|
||||
{
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
if (mappings.getUnchecked (i)->keypresses.contains (keyPress))
|
||||
return mappings.getUnchecked (i)->commandID;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool KeyPressMappingSet::containsMapping (const CommandID commandID, const KeyPress& keyPress) const noexcept
|
||||
{
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
if (mappings.getUnchecked (i)->commandID == commandID)
|
||||
return mappings.getUnchecked (i)->keypresses.contains (keyPress);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::invokeCommand (const CommandID commandID,
|
||||
const KeyPress& key,
|
||||
const bool isKeyDown,
|
||||
const int millisecsSinceKeyPressed,
|
||||
Component* const originatingComponent) const
|
||||
{
|
||||
ApplicationCommandTarget::InvocationInfo info (commandID);
|
||||
|
||||
info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromKeyPress;
|
||||
info.isKeyDown = isKeyDown;
|
||||
info.keyPress = key;
|
||||
info.millisecsSinceKeyPressed = millisecsSinceKeyPressed;
|
||||
info.originatingComponent = originatingComponent;
|
||||
|
||||
commandManager.invoke (info, false);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion)
|
||||
{
|
||||
if (xmlVersion.hasTagName ("KEYMAPPINGS"))
|
||||
{
|
||||
if (xmlVersion.getBoolAttribute ("basedOnDefaults", true))
|
||||
{
|
||||
// if the XML was created as a set of differences from the default mappings,
|
||||
// (i.e. by calling createXml (true)), then we need to first restore the defaults.
|
||||
resetToDefaultMappings();
|
||||
}
|
||||
else
|
||||
{
|
||||
// if the XML was created calling createXml (false), then we need to clear all
|
||||
// the keys and treat the xml as describing the entire set of mappings.
|
||||
clearAllKeyPresses();
|
||||
}
|
||||
|
||||
for (auto* map : xmlVersion.getChildIterator())
|
||||
{
|
||||
const CommandID commandId = map->getStringAttribute ("commandId").getHexValue32();
|
||||
|
||||
if (commandId != 0)
|
||||
{
|
||||
auto key = KeyPress::createFromDescription (map->getStringAttribute ("key"));
|
||||
|
||||
if (map->hasTagName ("MAPPING"))
|
||||
{
|
||||
addKeyPress (commandId, key);
|
||||
}
|
||||
else if (map->hasTagName ("UNMAPPING"))
|
||||
{
|
||||
for (auto& m : mappings)
|
||||
if (m->commandID == commandId)
|
||||
m->keypresses.removeAllInstancesOf (key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<XmlElement> KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const
|
||||
{
|
||||
std::unique_ptr<KeyPressMappingSet> defaultSet;
|
||||
|
||||
if (saveDifferencesFromDefaultSet)
|
||||
{
|
||||
defaultSet = std::make_unique<KeyPressMappingSet> (commandManager);
|
||||
defaultSet->resetToDefaultMappings();
|
||||
}
|
||||
|
||||
auto doc = std::make_unique<XmlElement> ("KEYMAPPINGS");
|
||||
|
||||
doc->setAttribute ("basedOnDefaults", saveDifferencesFromDefaultSet);
|
||||
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
{
|
||||
auto& cm = *mappings.getUnchecked (i);
|
||||
|
||||
for (int j = 0; j < cm.keypresses.size(); ++j)
|
||||
{
|
||||
if (defaultSet == nullptr
|
||||
|| ! defaultSet->containsMapping (cm.commandID, cm.keypresses.getReference (j)))
|
||||
{
|
||||
auto map = doc->createNewChildElement ("MAPPING");
|
||||
|
||||
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
|
||||
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
|
||||
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultSet != nullptr)
|
||||
{
|
||||
for (int i = 0; i < defaultSet->mappings.size(); ++i)
|
||||
{
|
||||
auto& cm = *defaultSet->mappings.getUnchecked (i);
|
||||
|
||||
for (int j = 0; j < cm.keypresses.size(); ++j)
|
||||
{
|
||||
if (! containsMapping (cm.commandID, cm.keypresses.getReference (j)))
|
||||
{
|
||||
auto map = doc->createNewChildElement ("UNMAPPING");
|
||||
|
||||
map->setAttribute ("commandId", String::toHexString ((int) cm.commandID));
|
||||
map->setAttribute ("description", commandManager.getDescriptionOfCommand (cm.commandID));
|
||||
map->setAttribute ("key", cm.keypresses.getReference (j).getTextDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool KeyPressMappingSet::keyPressed (const KeyPress& key, Component* const originatingComponent)
|
||||
{
|
||||
bool commandWasDisabled = false;
|
||||
|
||||
for (int i = 0; i < mappings.size(); ++i)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked (i);
|
||||
|
||||
if (cm.keypresses.contains (key))
|
||||
{
|
||||
if (const ApplicationCommandInfo* const ci = commandManager.getCommandForID (cm.commandID))
|
||||
{
|
||||
if ((ci->flags & ApplicationCommandInfo::wantsKeyUpDownCallbacks) == 0)
|
||||
{
|
||||
ApplicationCommandInfo info (0);
|
||||
|
||||
if (commandManager.getTargetForCommand (cm.commandID, info) != nullptr)
|
||||
{
|
||||
if ((info.flags & ApplicationCommandInfo::isDisabled) == 0)
|
||||
{
|
||||
invokeCommand (cm.commandID, key, true, 0, originatingComponent);
|
||||
return true;
|
||||
}
|
||||
|
||||
commandWasDisabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (originatingComponent != nullptr && commandWasDisabled)
|
||||
originatingComponent->getLookAndFeel().playAlertSound();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyPressMappingSet::keyStateChanged (const bool /*isKeyDown*/, Component* originatingComponent)
|
||||
{
|
||||
bool used = false;
|
||||
const uint32 now = Time::getMillisecondCounter();
|
||||
|
||||
for (int i = mappings.size(); --i >= 0;)
|
||||
{
|
||||
CommandMapping& cm = *mappings.getUnchecked (i);
|
||||
|
||||
if (cm.wantsKeyUpDownCallbacks)
|
||||
{
|
||||
for (int j = cm.keypresses.size(); --j >= 0;)
|
||||
{
|
||||
const KeyPress key (cm.keypresses.getReference (j));
|
||||
const bool isDown = key.isCurrentlyDown();
|
||||
|
||||
int keyPressEntryIndex = 0;
|
||||
bool wasDown = false;
|
||||
|
||||
for (int k = keysDown.size(); --k >= 0;)
|
||||
{
|
||||
if (key == keysDown.getUnchecked (k)->key)
|
||||
{
|
||||
keyPressEntryIndex = k;
|
||||
wasDown = true;
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDown != wasDown)
|
||||
{
|
||||
int millisecs = 0;
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
KeyPressTime* const k = new KeyPressTime();
|
||||
k->key = key;
|
||||
k->timeWhenPressed = now;
|
||||
|
||||
keysDown.add (k);
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint32 pressTime = keysDown.getUnchecked (keyPressEntryIndex)->timeWhenPressed;
|
||||
|
||||
if (now > pressTime)
|
||||
millisecs = (int) (now - pressTime);
|
||||
|
||||
keysDown.remove (keyPressEntryIndex);
|
||||
}
|
||||
|
||||
invokeCommand (cm.commandID, key, isDown, millisecs, originatingComponent);
|
||||
used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return used;
|
||||
}
|
||||
|
||||
void KeyPressMappingSet::globalFocusChanged (Component* focusedComponent)
|
||||
{
|
||||
if (focusedComponent != nullptr)
|
||||
focusedComponent->keyStateChanged (false);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 and edits a list of keypresses, which it uses to invoke the appropriate
|
||||
command in an ApplicationCommandManager.
|
||||
|
||||
Normally, you won't actually create a KeyPressMappingSet directly, because
|
||||
each ApplicationCommandManager contains its own KeyPressMappingSet, so typically
|
||||
you'd create yourself an ApplicationCommandManager, and call its
|
||||
ApplicationCommandManager::getKeyMappings() method to get a pointer to its
|
||||
KeyPressMappingSet.
|
||||
|
||||
For one of these to actually use keypresses, you'll need to add it as a KeyListener
|
||||
to the top-level component for which you want to handle keystrokes. So for example:
|
||||
|
||||
@code
|
||||
class MyMainWindow : public Component
|
||||
{
|
||||
ApplicationCommandManager* myCommandManager;
|
||||
|
||||
public:
|
||||
MyMainWindow()
|
||||
{
|
||||
myCommandManager = new ApplicationCommandManager();
|
||||
|
||||
// first, make sure the command manager has registered all the commands that its
|
||||
// targets can perform..
|
||||
myCommandManager->registerAllCommandsForTarget (myCommandTarget1);
|
||||
myCommandManager->registerAllCommandsForTarget (myCommandTarget2);
|
||||
|
||||
// this will use the command manager to initialise the KeyPressMappingSet with
|
||||
// the default keypresses that were specified when the targets added their commands
|
||||
// to the manager.
|
||||
myCommandManager->getKeyMappings()->resetToDefaultMappings();
|
||||
|
||||
// having set up the default key-mappings, you might now want to load the last set
|
||||
// of mappings that the user configured.
|
||||
myCommandManager->getKeyMappings()->restoreFromXml (lastSavedKeyMappingsXML);
|
||||
|
||||
// Now tell our top-level window to send any keypresses that arrive to the
|
||||
// KeyPressMappingSet, which will use them to invoke the appropriate commands.
|
||||
addKeyListener (myCommandManager->getKeyMappings());
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
@endcode
|
||||
|
||||
KeyPressMappingSet derives from ChangeBroadcaster so that interested parties can
|
||||
register to be told when a command or mapping is added, removed, etc.
|
||||
|
||||
There's also a UI component called KeyMappingEditorComponent that can be used
|
||||
to easily edit the key mappings.
|
||||
|
||||
@see Component::addKeyListener(), KeyMappingEditorComponent, ApplicationCommandManager
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API KeyPressMappingSet : public KeyListener,
|
||||
public ChangeBroadcaster,
|
||||
private FocusChangeListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a KeyPressMappingSet for a given command manager.
|
||||
|
||||
Normally, you won't actually create a KeyPressMappingSet directly, because
|
||||
each ApplicationCommandManager contains its own KeyPressMappingSet, so the
|
||||
best thing to do is to create your ApplicationCommandManager, and use the
|
||||
ApplicationCommandManager::getKeyMappings() method to access its mappings.
|
||||
|
||||
When a suitable keypress happens, the manager's invoke() method will be
|
||||
used to invoke the appropriate command.
|
||||
|
||||
@see ApplicationCommandManager
|
||||
*/
|
||||
explicit KeyPressMappingSet (ApplicationCommandManager&);
|
||||
|
||||
/** Creates an copy of a KeyPressMappingSet. */
|
||||
KeyPressMappingSet (const KeyPressMappingSet&);
|
||||
|
||||
/** Destructor. */
|
||||
~KeyPressMappingSet() override;
|
||||
|
||||
//==============================================================================
|
||||
ApplicationCommandManager& getCommandManager() const noexcept { return commandManager; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns a list of keypresses that are assigned to a particular command.
|
||||
|
||||
@param commandID the command's ID
|
||||
*/
|
||||
Array<KeyPress> getKeyPressesAssignedToCommand (CommandID commandID) const;
|
||||
|
||||
/** Assigns a keypress to a command.
|
||||
|
||||
If the keypress is already assigned to a different command, it will first be
|
||||
removed from that command, to avoid it triggering multiple functions.
|
||||
|
||||
@param commandID the ID of the command that you want to add a keypress to. If
|
||||
this is 0, the keypress will be removed from anything that it
|
||||
was previously assigned to, but not re-assigned
|
||||
@param newKeyPress the new key-press
|
||||
@param insertIndex if this is less than zero, the key will be appended to the
|
||||
end of the list of keypresses; otherwise the new keypress will
|
||||
be inserted into the existing list at this index
|
||||
*/
|
||||
void addKeyPress (CommandID commandID,
|
||||
const KeyPress& newKeyPress,
|
||||
int insertIndex = -1);
|
||||
|
||||
/** Reset all mappings to the defaults, as dictated by the ApplicationCommandManager.
|
||||
@see resetToDefaultMapping
|
||||
*/
|
||||
void resetToDefaultMappings();
|
||||
|
||||
/** Resets all key-mappings to the defaults for a particular command.
|
||||
@see resetToDefaultMappings
|
||||
*/
|
||||
void resetToDefaultMapping (CommandID commandID);
|
||||
|
||||
/** Removes all keypresses that are assigned to any commands. */
|
||||
void clearAllKeyPresses();
|
||||
|
||||
/** Removes all keypresses that are assigned to a particular command. */
|
||||
void clearAllKeyPresses (CommandID commandID);
|
||||
|
||||
/** Removes one of the keypresses that are assigned to a command.
|
||||
See the getKeyPressesAssignedToCommand() for the list of keypresses to
|
||||
which the keyPressIndex refers.
|
||||
*/
|
||||
void removeKeyPress (CommandID commandID, int keyPressIndex);
|
||||
|
||||
/** Removes a keypress from any command that it may be assigned to. */
|
||||
void removeKeyPress (const KeyPress& keypress);
|
||||
|
||||
/** Returns true if the given command is linked to this key. */
|
||||
bool containsMapping (CommandID commandID, const KeyPress& keyPress) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Looks for a command that corresponds to a keypress.
|
||||
@returns the UID of the command or 0 if none was found
|
||||
*/
|
||||
CommandID findCommandForKeyPress (const KeyPress& keyPress) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to recreate the mappings from a previously stored state.
|
||||
|
||||
The XML passed in must have been created by the createXml() method.
|
||||
|
||||
If the stored state makes any reference to commands that aren't
|
||||
currently available, these will be ignored.
|
||||
|
||||
If the set of mappings being loaded was a set of differences (using createXml (true)),
|
||||
then this will call resetToDefaultMappings() and then merge the saved mappings
|
||||
on top. If the saved set was created with createXml (false), then this method
|
||||
will first clear all existing mappings and load the saved ones as a complete set.
|
||||
|
||||
@returns true if it manages to load the XML correctly
|
||||
@see createXml
|
||||
*/
|
||||
bool restoreFromXml (const XmlElement& xmlVersion);
|
||||
|
||||
/** Creates an XML representation of the current mappings.
|
||||
|
||||
This will produce a lump of XML that can be later reloaded using
|
||||
restoreFromXml() to recreate the current mapping state.
|
||||
|
||||
@param saveDifferencesFromDefaultSet if this is false, then all keypresses
|
||||
will be saved into the XML. If it's true, then the XML will
|
||||
only store the differences between the current mappings and
|
||||
the default mappings you'd get from calling resetToDefaultMappings().
|
||||
The advantage of saving a set of differences from the default is that
|
||||
if you change the default mappings (in a new version of your app, for
|
||||
example), then these will be merged into a user's saved preferences.
|
||||
|
||||
@see restoreFromXml
|
||||
*/
|
||||
std::unique_ptr<XmlElement> createXml (bool saveDifferencesFromDefaultSet) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&, Component*) override;
|
||||
/** @internal */
|
||||
bool keyStateChanged (bool isKeyDown, Component*) override;
|
||||
/** @internal */
|
||||
void globalFocusChanged (Component*) override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
ApplicationCommandManager& commandManager;
|
||||
|
||||
struct CommandMapping
|
||||
{
|
||||
CommandID commandID;
|
||||
Array<KeyPress> keypresses;
|
||||
bool wantsKeyUpDownCallbacks;
|
||||
};
|
||||
|
||||
OwnedArray<CommandMapping> mappings;
|
||||
|
||||
struct KeyPressTime
|
||||
{
|
||||
KeyPress key;
|
||||
uint32 timeWhenPressed;
|
||||
};
|
||||
|
||||
OwnedArray<KeyPressTime> keysDown;
|
||||
|
||||
void invokeCommand (CommandID, const KeyPress&, bool isKeyDown,
|
||||
int millisecsSinceKeyPressed, Component* originator) const;
|
||||
|
||||
KeyPressMappingSet& operator= (const KeyPressMappingSet&);
|
||||
JUCE_LEAK_DETECTOR (KeyPressMappingSet)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class used internally for structures that can store cached images of
|
||||
component state.
|
||||
|
||||
Most people are unlikely to ever need to know about this class - it's really
|
||||
only for power-users!
|
||||
|
||||
@see Component::setCachedComponentImage
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API CachedComponentImage
|
||||
{
|
||||
public:
|
||||
CachedComponentImage() = default;
|
||||
virtual ~CachedComponentImage() = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Called as part of the parent component's paint method, this must draw
|
||||
the given component into the target graphics context, using the cached
|
||||
version where possible.
|
||||
*/
|
||||
virtual void paint (Graphics&) = 0;
|
||||
|
||||
/** Invalidates all cached image data.
|
||||
@returns true if the peer should also be repainted, or false if this object
|
||||
handles all repaint work internally.
|
||||
*/
|
||||
virtual bool invalidateAll() = 0;
|
||||
|
||||
/** Invalidates a section of the cached image data.
|
||||
@returns true if the peer should also be repainted, or false if this object
|
||||
handles all repaint work internally.
|
||||
*/
|
||||
virtual bool invalidate (const Rectangle<int>& area) = 0;
|
||||
|
||||
/** Called to indicate that the component is no longer active, so
|
||||
any cached data should be released if possible.
|
||||
*/
|
||||
virtual void releaseResources() = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
void ComponentListener::componentMovedOrResized (Component&, bool, bool) {}
|
||||
void ComponentListener::componentBroughtToFront (Component&) {}
|
||||
void ComponentListener::componentVisibilityChanged (Component&) {}
|
||||
void ComponentListener::componentChildrenChanged (Component&) {}
|
||||
void ComponentListener::componentParentHierarchyChanged (Component&) {}
|
||||
void ComponentListener::componentNameChanged (Component&) {}
|
||||
void ComponentListener::componentBeingDeleted (Component&) {}
|
||||
void ComponentListener::componentEnablementChanged (Component&) {}
|
||||
|
||||
} // namespace juce
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Gets informed about changes to a component's hierarchy or position.
|
||||
|
||||
To monitor a component for changes, register a subclass of ComponentListener
|
||||
with the component using Component::addComponentListener().
|
||||
|
||||
Be sure to deregister listeners before you delete them!
|
||||
|
||||
@see Component::addComponentListener, Component::removeComponentListener
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ComponentListener() = default;
|
||||
|
||||
/** Called when the component's position or size changes.
|
||||
|
||||
@param component the component that was moved or resized
|
||||
@param wasMoved true if the component's top-left corner has just moved
|
||||
@param wasResized true if the component's width or height has just changed
|
||||
@see Component::setBounds, Component::resized, Component::moved
|
||||
*/
|
||||
virtual void componentMovedOrResized (Component& component,
|
||||
bool wasMoved,
|
||||
bool wasResized);
|
||||
|
||||
/** Called when the component is brought to the top of the z-order.
|
||||
|
||||
@param component the component that was moved
|
||||
@see Component::toFront, Component::broughtToFront
|
||||
*/
|
||||
virtual void componentBroughtToFront (Component& component);
|
||||
|
||||
/** Called when the component is made visible or invisible.
|
||||
|
||||
@param component the component that changed
|
||||
@see Component::setVisible
|
||||
*/
|
||||
virtual void componentVisibilityChanged (Component& component);
|
||||
|
||||
/** Called when the component has children added or removed, or their z-order
|
||||
changes.
|
||||
|
||||
@param component the component whose children have changed
|
||||
@see Component::childrenChanged, Component::addChildComponent,
|
||||
Component::removeChildComponent
|
||||
*/
|
||||
virtual void componentChildrenChanged (Component& component);
|
||||
|
||||
/** Called to indicate that the component's parents have changed.
|
||||
|
||||
When a component is added or removed from its parent, all of its children
|
||||
will produce this notification (recursively - so all children of its
|
||||
children will also be called as well).
|
||||
|
||||
@param component the component that this listener is registered with
|
||||
@see Component::parentHierarchyChanged
|
||||
*/
|
||||
virtual void componentParentHierarchyChanged (Component& component);
|
||||
|
||||
/** Called when the component's name is changed.
|
||||
|
||||
@param component the component that had its name changed
|
||||
@see Component::setName, Component::getName
|
||||
*/
|
||||
virtual void componentNameChanged (Component& component);
|
||||
|
||||
/** Called when the component is in the process of being deleted.
|
||||
|
||||
This callback is made from inside the destructor, so be very, very cautious
|
||||
about what you do in here.
|
||||
|
||||
In particular, bear in mind that it's the Component base class's destructor that calls
|
||||
this - so if the object that's being deleted is a subclass of Component, then the
|
||||
subclass layers of the object will already have been destructed when it gets to this
|
||||
point!
|
||||
|
||||
@param component the component that was deleted
|
||||
*/
|
||||
virtual void componentBeingDeleted (Component& component);
|
||||
|
||||
/* Called when the component's enablement is changed.
|
||||
|
||||
@param component the component that had its enablement changed
|
||||
@see Component::setEnabled, Component::isEnabled, Component::enablementChanged
|
||||
*/
|
||||
virtual void componentEnablementChanged (Component& component);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Base class for traversing components.
|
||||
|
||||
If you need custom focus or keyboard focus traversal for a component you can
|
||||
create a subclass of ComponentTraverser and return it from
|
||||
Component::createFocusTraverser() or Component::createKeyboardFocusTraverser().
|
||||
|
||||
@see Component::createFocusTraverser, Component::createKeyboardFocusTraverser
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ComponentTraverser
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~ComponentTraverser() = default;
|
||||
|
||||
/** Returns the component that should be used as the traversal entry point
|
||||
within the given parent component.
|
||||
|
||||
This must return nullptr if there is no default component.
|
||||
*/
|
||||
virtual Component* getDefaultComponent (Component* parentComponent) = 0;
|
||||
|
||||
/** Returns the component that comes after the specified one when moving "forwards".
|
||||
|
||||
This must return nullptr if there is no next component.
|
||||
*/
|
||||
virtual Component* getNextComponent (Component* current) = 0;
|
||||
|
||||
/** Returns the component that comes after the specified one when moving "backwards".
|
||||
|
||||
This must return nullptr if there is no previous component.
|
||||
*/
|
||||
virtual Component* getPreviousComponent (Component* current) = 0;
|
||||
|
||||
/** Returns all of the traversable components within the given parent component in
|
||||
traversal order.
|
||||
*/
|
||||
virtual std::vector<Component*> getAllComponents (Component* parentComponent) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
Component* FocusTraverser::getNextComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
|
||||
return detail::FocusHelpers::navigateFocus (current,
|
||||
current->findFocusContainer(),
|
||||
detail::FocusHelpers::NavigationDirection::forwards,
|
||||
&Component::isFocusContainer);
|
||||
}
|
||||
|
||||
Component* FocusTraverser::getPreviousComponent (Component* current)
|
||||
{
|
||||
jassert (current != nullptr);
|
||||
|
||||
return detail::FocusHelpers::navigateFocus (current,
|
||||
current->findFocusContainer(),
|
||||
detail::FocusHelpers::NavigationDirection::backwards,
|
||||
&Component::isFocusContainer);
|
||||
}
|
||||
|
||||
Component* FocusTraverser::getDefaultComponent (Component* parentComponent)
|
||||
{
|
||||
if (parentComponent != nullptr)
|
||||
{
|
||||
std::vector<Component*> components;
|
||||
detail::FocusHelpers::findAllComponents (parentComponent,
|
||||
components,
|
||||
&Component::isFocusContainer);
|
||||
|
||||
if (! components.empty())
|
||||
return components.front();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<Component*> FocusTraverser::getAllComponents (Component* parentComponent)
|
||||
{
|
||||
std::vector<Component*> components;
|
||||
detail::FocusHelpers::findAllComponents (parentComponent,
|
||||
components,
|
||||
&Component::isFocusContainer);
|
||||
|
||||
return components;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct FocusTraverserTests final : public UnitTest
|
||||
{
|
||||
FocusTraverserTests()
|
||||
: UnitTest ("FocusTraverser", UnitTestCategories::gui)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
ScopedJuceInitialiser_GUI libraryInitialiser;
|
||||
const MessageManagerLock mml;
|
||||
|
||||
beginTest ("Basic traversal");
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
expect (traverser.getDefaultComponent (&parent) == &parent.children.front());
|
||||
|
||||
for (auto iter = parent.children.begin(); iter != parent.children.end(); ++iter)
|
||||
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (parent.children.cend()) ? nullptr
|
||||
: &(*std::next (iter))));
|
||||
|
||||
for (auto iter = parent.children.rbegin(); iter != parent.children.rend(); ++iter)
|
||||
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (parent.children.rend()) ? nullptr
|
||||
: &(*std::next (iter))));
|
||||
auto allComponents = traverser.getAllComponents (&parent);
|
||||
|
||||
expect (std::equal (allComponents.cbegin(), allComponents.cend(), parent.children.cbegin(),
|
||||
[] (const Component* c1, const Component& c2) { return c1 == &c2; }));
|
||||
}
|
||||
|
||||
beginTest ("Disabled components are ignored");
|
||||
{
|
||||
checkIgnored ([] (Component& c) { c.setEnabled (false); });
|
||||
}
|
||||
|
||||
beginTest ("Invisible components are ignored");
|
||||
{
|
||||
checkIgnored ([] (Component& c) { c.setVisible (false); });
|
||||
}
|
||||
|
||||
beginTest ("Explicit focus order comes before unspecified");
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
auto& explicitFocusComponent = parent.children[2];
|
||||
|
||||
explicitFocusComponent.setExplicitFocusOrder (1);
|
||||
expect (traverser.getDefaultComponent (&parent) == &explicitFocusComponent);
|
||||
|
||||
expect (traverser.getAllComponents (&parent).front() == &explicitFocusComponent);
|
||||
}
|
||||
|
||||
beginTest ("Explicit focus order comparison");
|
||||
{
|
||||
checkComponentProperties ([this] (Component& child) { child.setExplicitFocusOrder (getRandom().nextInt ({ 1, 100 })); },
|
||||
[] (const Component& c1, const Component& c2) { return c1.getExplicitFocusOrder()
|
||||
<= c2.getExplicitFocusOrder(); });
|
||||
}
|
||||
|
||||
beginTest ("Left to right");
|
||||
{
|
||||
checkComponentProperties ([this] (Component& child) { child.setTopLeftPosition (getRandom().nextInt ({ 0, 100 }), 0); },
|
||||
[] (const Component& c1, const Component& c2) { return c1.getX() <= c2.getX(); });
|
||||
}
|
||||
|
||||
beginTest ("Top to bottom");
|
||||
{
|
||||
checkComponentProperties ([this] (Component& child) { child.setTopLeftPosition (0, getRandom().nextInt ({ 0, 100 })); },
|
||||
[] (const Component& c1, const Component& c2) { return c1.getY() <= c2.getY(); });
|
||||
}
|
||||
|
||||
beginTest ("Focus containers have their own focus");
|
||||
{
|
||||
Component root;
|
||||
|
||||
TestComponent container;
|
||||
container.setFocusContainerType (Component::FocusContainerType::focusContainer);
|
||||
|
||||
root.addAndMakeVisible (container);
|
||||
|
||||
expect (traverser.getDefaultComponent (&root) == &container);
|
||||
expect (traverser.getNextComponent (&container) == nullptr);
|
||||
expect (traverser.getPreviousComponent (&container) == nullptr);
|
||||
|
||||
expect (traverser.getDefaultComponent (&container) == &container.children.front());
|
||||
|
||||
for (auto iter = container.children.begin(); iter != container.children.end(); ++iter)
|
||||
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (container.children.cend()) ? nullptr
|
||||
: &(*std::next (iter))));
|
||||
|
||||
for (auto iter = container.children.rbegin(); iter != container.children.rend(); ++iter)
|
||||
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (container.children.rend()) ? nullptr
|
||||
: &(*std::next (iter))));
|
||||
|
||||
expect (traverser.getAllComponents (&root).size() == 1);
|
||||
|
||||
auto allContainerComponents = traverser.getAllComponents (&container);
|
||||
|
||||
expect (std::equal (allContainerComponents.cbegin(), allContainerComponents.cend(), container.children.cbegin(),
|
||||
[] (const Component* c1, const Component& c2) { return c1 == &c2; }));
|
||||
}
|
||||
|
||||
beginTest ("Non-focus containers pass-through focus");
|
||||
{
|
||||
Component root;
|
||||
|
||||
TestComponent container;
|
||||
container.setFocusContainerType (Component::FocusContainerType::none);
|
||||
|
||||
root.addAndMakeVisible (container);
|
||||
|
||||
expect (traverser.getDefaultComponent (&root) == &container);
|
||||
expect (traverser.getNextComponent (&container) == &container.children.front());
|
||||
expect (traverser.getPreviousComponent (&container) == nullptr);
|
||||
|
||||
expect (traverser.getDefaultComponent (&container) == &container.children.front());
|
||||
|
||||
for (auto iter = container.children.begin(); iter != container.children.end(); ++iter)
|
||||
expect (traverser.getNextComponent (&(*iter)) == (iter == std::prev (container.children.cend()) ? nullptr
|
||||
: &(*std::next (iter))));
|
||||
|
||||
for (auto iter = container.children.rbegin(); iter != container.children.rend(); ++iter)
|
||||
expect (traverser.getPreviousComponent (&(*iter)) == (iter == std::prev (container.children.rend()) ? &container
|
||||
: &(*std::next (iter))));
|
||||
|
||||
expect (traverser.getAllComponents (&root).size() == container.children.size() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct TestComponent final : public Component
|
||||
{
|
||||
TestComponent()
|
||||
{
|
||||
for (auto& child : children)
|
||||
addAndMakeVisible (child);
|
||||
}
|
||||
|
||||
std::array<Component, 10> children;
|
||||
};
|
||||
|
||||
void checkComponentProperties (std::function<void (Component&)>&& childFn,
|
||||
std::function<bool (const Component&, const Component&)>&& testProperty)
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
for (auto& child : parent.children)
|
||||
childFn (child);
|
||||
|
||||
auto* comp = traverser.getDefaultComponent (&parent);
|
||||
|
||||
for (const auto& child : parent.children)
|
||||
if (&child != comp)
|
||||
expect (testProperty (*comp, child));
|
||||
|
||||
for (;;)
|
||||
{
|
||||
auto* next = traverser.getNextComponent (comp);
|
||||
|
||||
if (next == nullptr)
|
||||
break;
|
||||
|
||||
expect (testProperty (*comp, *next));
|
||||
comp = next;
|
||||
}
|
||||
}
|
||||
|
||||
void checkIgnored (const std::function<void(Component&)>& makeIgnored)
|
||||
{
|
||||
TestComponent parent;
|
||||
|
||||
auto iter = parent.children.begin();
|
||||
|
||||
makeIgnored (*iter);
|
||||
expect (traverser.getDefaultComponent (&parent) == std::addressof (*std::next (iter)));
|
||||
|
||||
iter += 5;
|
||||
makeIgnored (*iter);
|
||||
expect (traverser.getNextComponent (std::addressof (*std::prev (iter))) == std::addressof (*std::next (iter)));
|
||||
expect (traverser.getPreviousComponent (std::addressof (*std::next (iter))) == std::addressof (*std::prev (iter)));
|
||||
|
||||
auto allComponents = traverser.getAllComponents (&parent);
|
||||
|
||||
expect (std::find (allComponents.cbegin(), allComponents.cend(), &parent.children.front()) == allComponents.cend());
|
||||
expect (std::find (allComponents.cbegin(), allComponents.cend(), std::addressof (*iter)) == allComponents.cend());
|
||||
}
|
||||
|
||||
FocusTraverser traverser;
|
||||
};
|
||||
|
||||
static FocusTraverserTests focusTraverserTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Controls the order in which focus moves between components.
|
||||
|
||||
The algorithm used by this class to work out the order of traversal is as
|
||||
follows:
|
||||
- Only visible and enabled components are considered focusable.
|
||||
- If two components both have an explicit focus order specified then the
|
||||
one with the lowest number comes first (see the
|
||||
Component::setExplicitFocusOrder() method).
|
||||
- Any component with an explicit focus order greater than 0 comes before ones
|
||||
that don't have an order specified.
|
||||
- Components with their 'always on top' flag set come before those without.
|
||||
- Any unspecified components are traversed in a left-to-right, then
|
||||
top-to-bottom order.
|
||||
|
||||
If you need focus traversal in a more customised way you can create a
|
||||
ComponentTraverser subclass that uses your own algorithm and return it
|
||||
from Component::createFocusTraverser().
|
||||
|
||||
@see ComponentTraverser, Component::createFocusTraverser
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FocusTraverser : public ComponentTraverser
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
~FocusTraverser() override = default;
|
||||
|
||||
/** Returns the component that should receive focus by default within the given
|
||||
parent component.
|
||||
|
||||
The default implementation will just return the foremost visible and enabled
|
||||
child component, and will return nullptr if there is no suitable component.
|
||||
*/
|
||||
Component* getDefaultComponent (Component* parentComponent) override;
|
||||
|
||||
/** Returns the component that should be given focus after the specified one when
|
||||
moving "forwards".
|
||||
|
||||
The default implementation will return the next visible and enabled component
|
||||
which is to the right of or below this one, and will return nullptr if there
|
||||
is no suitable component.
|
||||
*/
|
||||
Component* getNextComponent (Component* current) override;
|
||||
|
||||
/** Returns the component that should be given focus after the specified one when
|
||||
moving "backwards".
|
||||
|
||||
The default implementation will return the previous visible and enabled component
|
||||
which is to the left of or above this one, and will return nullptr if there
|
||||
is no suitable component.
|
||||
*/
|
||||
Component* getPreviousComponent (Component* current) override;
|
||||
|
||||
/** Returns all of the components that can receive focus within the given parent
|
||||
component in traversal order.
|
||||
|
||||
The default implementation will return all visible and enabled child components.
|
||||
*/
|
||||
std::vector<Component*> getAllComponents (Component* parentComponent) override;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 ModalComponentManager::ModalItem final : public ComponentMovementWatcher
|
||||
{
|
||||
ModalItem (Component* comp, bool shouldAutoDelete)
|
||||
: ComponentMovementWatcher (comp),
|
||||
component (comp), autoDelete (shouldAutoDelete)
|
||||
{
|
||||
jassert (comp != nullptr);
|
||||
}
|
||||
|
||||
~ModalItem() override
|
||||
{
|
||||
if (autoDelete)
|
||||
std::unique_ptr<Component> componentDeleter (component);
|
||||
}
|
||||
|
||||
void componentMovedOrResized (bool, bool) override {}
|
||||
|
||||
using ComponentMovementWatcher::componentMovedOrResized;
|
||||
|
||||
void componentPeerChanged() override
|
||||
{
|
||||
componentVisibilityChanged();
|
||||
}
|
||||
|
||||
void componentVisibilityChanged() override
|
||||
{
|
||||
if (! component->isShowing())
|
||||
cancel();
|
||||
}
|
||||
|
||||
using ComponentMovementWatcher::componentVisibilityChanged;
|
||||
|
||||
void componentBeingDeleted (Component& comp) override
|
||||
{
|
||||
ComponentMovementWatcher::componentBeingDeleted (comp);
|
||||
|
||||
if (component == &comp || comp.isParentOf (component))
|
||||
{
|
||||
autoDelete = false;
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void cancel()
|
||||
{
|
||||
if (isActive)
|
||||
{
|
||||
isActive = false;
|
||||
|
||||
if (auto* mcm = ModalComponentManager::getInstanceWithoutCreating())
|
||||
mcm->triggerAsyncUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
Component* component;
|
||||
OwnedArray<Callback> callbacks;
|
||||
int returnValue = 0;
|
||||
bool isActive = true, autoDelete;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ModalItem)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ModalComponentManager::ModalComponentManager()
|
||||
{
|
||||
}
|
||||
|
||||
ModalComponentManager::~ModalComponentManager()
|
||||
{
|
||||
stack.clear();
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (ModalComponentManager)
|
||||
|
||||
|
||||
//==============================================================================
|
||||
void ModalComponentManager::startModal (Component* component, bool autoDelete)
|
||||
{
|
||||
if (component != nullptr)
|
||||
{
|
||||
stack.add (new ModalItem (component, autoDelete));
|
||||
detail::ComponentHelpers::ModalComponentManagerChangeNotifier::getInstance().modalComponentManagerChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::attachCallback (Component* component, Callback* callback)
|
||||
{
|
||||
if (callback != nullptr)
|
||||
{
|
||||
std::unique_ptr<Callback> callbackDeleter (callback);
|
||||
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
auto* item = stack.getUnchecked (i);
|
||||
|
||||
if (item->component == component)
|
||||
{
|
||||
item->callbacks.add (callback);
|
||||
callbackDeleter.release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::endModal (Component* component)
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
auto* item = stack.getUnchecked (i);
|
||||
|
||||
if (item->component == component)
|
||||
item->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::endModal (Component* component, int returnValue)
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
auto* item = stack.getUnchecked (i);
|
||||
|
||||
if (item->component == component)
|
||||
{
|
||||
item->returnValue = returnValue;
|
||||
item->cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ModalComponentManager::getNumModalComponents() const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (auto* item : stack)
|
||||
if (item->isActive)
|
||||
++n;
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
Component* ModalComponentManager::getModalComponent (int index) const
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
auto* item = stack.getUnchecked (i);
|
||||
|
||||
if (item->isActive)
|
||||
if (n++ == index)
|
||||
return item->component;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ModalComponentManager::isModal (const Component* comp) const
|
||||
{
|
||||
for (auto* item : stack)
|
||||
if (item->isActive && item->component == comp)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModalComponentManager::isFrontModalComponent (const Component* comp) const
|
||||
{
|
||||
return comp == getModalComponent (0);
|
||||
}
|
||||
|
||||
void ModalComponentManager::handleAsyncUpdate()
|
||||
{
|
||||
for (int i = stack.size(); --i >= 0;)
|
||||
{
|
||||
auto* item = stack.getUnchecked (i);
|
||||
|
||||
if (! item->isActive)
|
||||
{
|
||||
std::unique_ptr<ModalItem> deleter (stack.removeAndReturn (i));
|
||||
Component::SafePointer<Component> compToDelete (item->autoDelete ? item->component : nullptr);
|
||||
|
||||
for (int j = item->callbacks.size(); --j >= 0;)
|
||||
item->callbacks.getUnchecked (j)->modalStateFinished (item->returnValue);
|
||||
|
||||
compToDelete.deleteAndZero();
|
||||
|
||||
detail::ComponentHelpers::ModalComponentManagerChangeNotifier::getInstance().modalComponentManagerChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFocus)
|
||||
{
|
||||
ComponentPeer* lastOne = nullptr;
|
||||
|
||||
for (int i = 0; i < getNumModalComponents(); ++i)
|
||||
{
|
||||
auto* c = getModalComponent (i);
|
||||
|
||||
if (c == nullptr)
|
||||
break;
|
||||
|
||||
if (auto* peer = c->getPeer())
|
||||
{
|
||||
if (peer != lastOne)
|
||||
{
|
||||
if (lastOne == nullptr)
|
||||
{
|
||||
peer->toFront (topOneShouldGrabFocus);
|
||||
|
||||
if (topOneShouldGrabFocus)
|
||||
peer->grabFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
peer->toBehind (lastOne);
|
||||
}
|
||||
|
||||
lastOne = peer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ModalComponentManager::cancelAllModalComponents()
|
||||
{
|
||||
auto numModal = getNumModalComponents();
|
||||
|
||||
for (int i = numModal; --i >= 0;)
|
||||
if (auto* c = getModalComponent (i))
|
||||
c->exitModalState (0);
|
||||
|
||||
return numModal > 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
int ModalComponentManager::runEventLoopForCurrentComponent()
|
||||
{
|
||||
// This can only be run from the message thread!
|
||||
JUCE_ASSERT_MESSAGE_THREAD
|
||||
|
||||
int returnValue = 0;
|
||||
|
||||
if (auto* currentlyModal = getModalComponent (0))
|
||||
{
|
||||
detail::FocusRestorer focusRestorer;
|
||||
bool finished = false;
|
||||
|
||||
attachCallback (currentlyModal, ModalCallbackFunction::create ([&] (int r) { returnValue = r; finished = true; }));
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
while (! finished)
|
||||
{
|
||||
if (! MessageManager::getInstance()->runDispatchLoopUntil (20))
|
||||
break;
|
||||
}
|
||||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 the system's stack of modal components.
|
||||
|
||||
Normally you'll just use the Component methods to invoke modal states in components,
|
||||
and won't have to deal with this class directly, but this is the singleton object that's
|
||||
used internally to manage the stack.
|
||||
|
||||
@see Component::enterModalState, Component::exitModalState, Component::isCurrentlyModal,
|
||||
Component::getCurrentlyModalComponent, Component::isCurrentlyBlockedByAnotherModalComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ModalComponentManager : private AsyncUpdater,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Receives callbacks when a modal component is dismissed.
|
||||
|
||||
You can register a callback using Component::enterModalState() or
|
||||
ModalComponentManager::attachCallback().
|
||||
|
||||
For some quick ways of creating callback objects, see the ModalCallbackFunction class.
|
||||
@see ModalCallbackFunction
|
||||
*/
|
||||
class JUCE_API Callback
|
||||
{
|
||||
public:
|
||||
/** */
|
||||
Callback() = default;
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~Callback() = default;
|
||||
|
||||
/** Called to indicate that a modal component has been dismissed.
|
||||
|
||||
You can register a callback using Component::enterModalState() or
|
||||
ModalComponentManager::attachCallback().
|
||||
|
||||
The returnValue parameter is the value that was passed to Component::exitModalState()
|
||||
when the component was dismissed.
|
||||
|
||||
The callback object will be deleted shortly after this method is called.
|
||||
*/
|
||||
virtual void modalStateFinished (int returnValue) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (ModalComponentManager)
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of components currently being shown modally.
|
||||
@see getModalComponent
|
||||
*/
|
||||
int getNumModalComponents() const;
|
||||
|
||||
/** Returns one of the components being shown modally.
|
||||
An index of 0 is the most recently-shown, topmost component.
|
||||
*/
|
||||
Component* getModalComponent (int index) const;
|
||||
|
||||
/** Returns true if the specified component is in a modal state. */
|
||||
bool isModal (const Component* component) const;
|
||||
|
||||
/** Returns true if the specified component is currently the topmost modal component. */
|
||||
bool isFrontModalComponent (const Component* component) const;
|
||||
|
||||
/** Adds a new callback that will be called when the specified modal component is dismissed.
|
||||
|
||||
If the component is modal, then when it is dismissed, either by being hidden, or by calling
|
||||
Component::exitModalState(), then the Callback::modalStateFinished() method will be
|
||||
called.
|
||||
|
||||
Each component can have any number of callbacks associated with it, and this one is added
|
||||
to that list.
|
||||
|
||||
The object that is passed in will be deleted by the manager when it's no longer needed. If
|
||||
the given component is not currently modal, the callback object is deleted immediately and
|
||||
no action is taken.
|
||||
*/
|
||||
void attachCallback (Component* component, Callback* callback);
|
||||
|
||||
/** Brings any modal components to the front. */
|
||||
void bringModalComponentsToFront (bool topOneShouldGrabFocus = true);
|
||||
|
||||
/** Calls exitModalState (0) on any components that are currently modal.
|
||||
@returns true if any components were modal; false if nothing needed cancelling
|
||||
*/
|
||||
bool cancelAllModalComponents();
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Runs the event loop until the currently topmost modal component is dismissed, and
|
||||
returns the exit code for that component.
|
||||
*/
|
||||
int runEventLoopForCurrentComponent();
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/** Creates a ModalComponentManager.
|
||||
You shouldn't ever call the constructor - it's a singleton, so use ModalComponentManager::getInstance()
|
||||
*/
|
||||
ModalComponentManager();
|
||||
|
||||
/** Destructor. */
|
||||
~ModalComponentManager() override;
|
||||
|
||||
/** @internal */
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class Component;
|
||||
|
||||
struct ModalItem;
|
||||
OwnedArray<ModalItem> stack;
|
||||
|
||||
void startModal (Component*, bool autoDelete);
|
||||
void endModal (Component*, int returnValue);
|
||||
void endModal (Component*);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ModalComponentManager)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class provides some handy utility methods for creating ModalComponentManager::Callback
|
||||
objects that will invoke a static function with some parameters when a modal component is dismissed.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ModalCallbackFunction
|
||||
{
|
||||
public:
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a callable object.
|
||||
|
||||
The function that you supply must take an integer parameter, which is the result code that
|
||||
was returned when the modal component was dismissed.
|
||||
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <typename CallbackFn>
|
||||
static ModalComponentManager::Callback* create (CallbackFn&& fn)
|
||||
{
|
||||
struct Callable : public ModalComponentManager::Callback
|
||||
{
|
||||
explicit Callable (CallbackFn&& f) : fn (std::forward<CallbackFn> (f)) {}
|
||||
void modalStateFinished (int result) override { NullCheckedInvocation::invoke (std::move (fn), result); }
|
||||
|
||||
std::remove_reference_t<CallbackFn> fn;
|
||||
};
|
||||
|
||||
return new Callable (std::forward<CallbackFn> (fn));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with a parameter.
|
||||
|
||||
The function that you supply must take two parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the second
|
||||
can be a custom type. Note that this custom value will be copied and stored, so it must
|
||||
be a primitive type or a class that provides copy-by-value semantics.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, double customValue)
|
||||
{
|
||||
if (modalResult == 1)
|
||||
doSomethingWith (customValue);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
...
|
||||
someKindOfComp->enterModalState (true, ModalCallbackFunction::create (myCallbackFunction, 3.0));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <typename ParamType>
|
||||
static ModalComponentManager::Callback* create (void (*functionToCall) (int, ParamType),
|
||||
ParamType parameterValue)
|
||||
{
|
||||
return create ([functionToCall, parameterValue] (int r)
|
||||
{
|
||||
functionToCall (r, parameterValue);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with two custom parameters.
|
||||
|
||||
The function that you supply must take three parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the next two are
|
||||
your custom types. Note that these custom values will be copied and stored, so they must
|
||||
be primitive types or classes that provide copy-by-value semantics.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, double customValue1, String customValue2)
|
||||
{
|
||||
if (modalResult == 1)
|
||||
doSomethingWith (customValue1, customValue2);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
...
|
||||
someKindOfComp->enterModalState (true, ModalCallbackFunction::create (myCallbackFunction, 3.0, String ("xyz")));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <typename ParamType1, typename ParamType2>
|
||||
static ModalComponentManager::Callback* withParam (void (*functionToCall) (int, ParamType1, ParamType2),
|
||||
ParamType1 parameterValue1,
|
||||
ParamType2 parameterValue2)
|
||||
{
|
||||
return create ([functionToCall, parameterValue1, parameterValue2] (int r)
|
||||
{
|
||||
functionToCall (r, parameterValue1, parameterValue2);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** This is a utility function to create a ModalComponentManager::Callback that will
|
||||
call a static function with a component.
|
||||
|
||||
The function that you supply must take two parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, and the second
|
||||
can be a Component class. The component will be stored as a WeakReference, so that if it gets
|
||||
deleted before this callback is invoked, the pointer that is passed to the function will be null.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, Slider* mySlider)
|
||||
{
|
||||
if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..)
|
||||
mySlider->setValue (0.0);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
Slider* mySlider;
|
||||
...
|
||||
someKindOfComp->enterModalState (true, ModalCallbackFunction::forComponent (myCallbackFunction, mySlider));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <class ComponentType>
|
||||
static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*),
|
||||
ComponentType* component)
|
||||
{
|
||||
return create ([functionToCall, comp = WeakReference<Component> { component }] (int r)
|
||||
{
|
||||
functionToCall (r, static_cast<ComponentType*> (comp.get()));
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a ModalComponentManager::Callback that will call a static function with a component.
|
||||
|
||||
The function that you supply must take three parameters - the first being an int, which is
|
||||
the result code that was used when the modal component was dismissed, the second being a Component
|
||||
class, and the third being a custom type (which must be a primitive type or have copy-by-value semantics).
|
||||
The component will be stored as a WeakReference, so that if it gets deleted before this callback is
|
||||
invoked, the pointer that is passed into the function will be null.
|
||||
|
||||
E.g. @code
|
||||
static void myCallbackFunction (int modalResult, Slider* mySlider, String customParam)
|
||||
{
|
||||
if (modalResult == 1 && mySlider != nullptr) // (must check that mySlider isn't null in case it was deleted..)
|
||||
mySlider->setName (customParam);
|
||||
}
|
||||
|
||||
Component* someKindOfComp;
|
||||
Slider* mySlider;
|
||||
...
|
||||
someKindOfComp->enterModalState (true, ModalCallbackFunction::forComponent (myCallbackFunction, mySlider, String ("hello")));
|
||||
@endcode
|
||||
@see ModalComponentManager::Callback
|
||||
*/
|
||||
template <class ComponentType, typename ParamType>
|
||||
static ModalComponentManager::Callback* forComponent (void (*functionToCall) (int, ComponentType*, ParamType),
|
||||
ComponentType* component,
|
||||
ParamType param)
|
||||
{
|
||||
return create ([functionToCall, param, comp = WeakReference<Component> { component }] (int r)
|
||||
{
|
||||
functionToCall (r, static_cast<ComponentType*> (comp.get()), param);
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
ModalCallbackFunction() = delete;
|
||||
~ModalCallbackFunction() = delete;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
Desktop::Desktop()
|
||||
: mouseSources (new detail::MouseInputSourceList()),
|
||||
masterScaleFactor ((float) getDefaultMasterScale()),
|
||||
nativeDarkModeChangeDetectorImpl (createNativeDarkModeChangeDetectorImpl())
|
||||
{
|
||||
displays.reset (new Displays (*this));
|
||||
}
|
||||
|
||||
Desktop::~Desktop()
|
||||
{
|
||||
setScreenSaverEnabled (true);
|
||||
animator.cancelAllAnimations (false);
|
||||
|
||||
jassert (instance == this);
|
||||
instance = nullptr;
|
||||
|
||||
// doh! If you don't delete all your windows before exiting, you're going to
|
||||
// be leaking memory!
|
||||
jassert (desktopComponents.size() == 0);
|
||||
}
|
||||
|
||||
Desktop& JUCE_CALLTYPE Desktop::getInstance()
|
||||
{
|
||||
if (instance == nullptr)
|
||||
instance = new Desktop();
|
||||
|
||||
return *instance;
|
||||
}
|
||||
|
||||
Desktop* Desktop::instance = nullptr;
|
||||
|
||||
//==============================================================================
|
||||
int Desktop::getNumComponents() const noexcept
|
||||
{
|
||||
return desktopComponents.size();
|
||||
}
|
||||
|
||||
Component* Desktop::getComponent (int index) const noexcept
|
||||
{
|
||||
return desktopComponents [index];
|
||||
}
|
||||
|
||||
Component* Desktop::findComponentAt (Point<int> screenPosition) const
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
for (int i = desktopComponents.size(); --i >= 0;)
|
||||
{
|
||||
auto* c = desktopComponents.getUnchecked (i);
|
||||
|
||||
if (c->isVisible())
|
||||
{
|
||||
auto relative = c->getLocalPoint (nullptr, screenPosition);
|
||||
|
||||
if (c->contains (relative))
|
||||
return c->getComponentAt (relative);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
LookAndFeel& Desktop::getDefaultLookAndFeel() noexcept
|
||||
{
|
||||
if (auto lf = currentLookAndFeel.get())
|
||||
return *lf;
|
||||
|
||||
if (defaultLookAndFeel == nullptr)
|
||||
defaultLookAndFeel.reset (new LookAndFeel_V4());
|
||||
|
||||
auto lf = defaultLookAndFeel.get();
|
||||
jassert (lf != nullptr);
|
||||
currentLookAndFeel = lf;
|
||||
return *lf;
|
||||
}
|
||||
|
||||
void Desktop::setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
currentLookAndFeel = newDefaultLookAndFeel;
|
||||
|
||||
for (int i = getNumComponents(); --i >= 0;)
|
||||
if (auto* c = getComponent (i))
|
||||
c->sendLookAndFeelChange();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::addDesktopComponent (Component* c)
|
||||
{
|
||||
jassert (c != nullptr);
|
||||
jassert (! desktopComponents.contains (c));
|
||||
desktopComponents.addIfNotAlreadyThere (c);
|
||||
}
|
||||
|
||||
void Desktop::removeDesktopComponent (Component* c)
|
||||
{
|
||||
desktopComponents.removeFirstMatchingValue (c);
|
||||
}
|
||||
|
||||
void Desktop::componentBroughtToFront (Component* c)
|
||||
{
|
||||
auto index = desktopComponents.indexOf (c);
|
||||
jassert (index >= 0);
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
int newIndex = -1;
|
||||
|
||||
if (! c->isAlwaysOnTop())
|
||||
{
|
||||
newIndex = desktopComponents.size();
|
||||
|
||||
while (newIndex > 0 && desktopComponents.getUnchecked (newIndex - 1)->isAlwaysOnTop())
|
||||
--newIndex;
|
||||
|
||||
--newIndex;
|
||||
}
|
||||
|
||||
desktopComponents.move (index, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Point<int> Desktop::getMousePosition()
|
||||
{
|
||||
return getMousePositionFloat().roundToInt();
|
||||
}
|
||||
|
||||
Point<float> Desktop::getMousePositionFloat()
|
||||
{
|
||||
return getInstance().getMainMouseSource().getScreenPosition();
|
||||
}
|
||||
|
||||
void Desktop::setMousePosition (Point<int> newPosition)
|
||||
{
|
||||
getInstance().getMainMouseSource().setScreenPosition (newPosition.toFloat());
|
||||
}
|
||||
|
||||
Point<int> Desktop::getLastMouseDownPosition()
|
||||
{
|
||||
return getInstance().getMainMouseSource().getLastMouseDownPosition().roundToInt();
|
||||
}
|
||||
|
||||
int Desktop::getMouseButtonClickCounter() const noexcept { return mouseClickCounter; }
|
||||
int Desktop::getMouseWheelMoveCounter() const noexcept { return mouseWheelCounter; }
|
||||
|
||||
void Desktop::incrementMouseClickCounter() noexcept { ++mouseClickCounter; }
|
||||
void Desktop::incrementMouseWheelCounter() noexcept { ++mouseWheelCounter; }
|
||||
|
||||
const Array<MouseInputSource>& Desktop::getMouseSources() const noexcept { return mouseSources->sourceArray; }
|
||||
int Desktop::getNumMouseSources() const noexcept { return mouseSources->sources.size(); }
|
||||
int Desktop::getNumDraggingMouseSources() const noexcept { return mouseSources->getNumDraggingMouseSources(); }
|
||||
MouseInputSource* Desktop::getMouseSource (int index) const noexcept { return mouseSources->getMouseSource (index); }
|
||||
MouseInputSource* Desktop::getDraggingMouseSource (int index) const noexcept { return mouseSources->getDraggingMouseSource (index); }
|
||||
MouseInputSource Desktop::getMainMouseSource() const noexcept { return MouseInputSource (mouseSources->sources.getUnchecked (0)); }
|
||||
void Desktop::beginDragAutoRepeat (int interval) { mouseSources->beginDragAutoRepeat (interval); }
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::addFocusChangeListener (FocusChangeListener* l) { focusListeners.add (l); }
|
||||
void Desktop::removeFocusChangeListener (FocusChangeListener* l) { focusListeners.remove (l); }
|
||||
void Desktop::triggerFocusCallback() { triggerAsyncUpdate(); }
|
||||
|
||||
void Desktop::updateFocusOutline()
|
||||
{
|
||||
if (auto* currentFocus = Component::getCurrentlyFocusedComponent())
|
||||
{
|
||||
if (currentFocus->hasFocusOutline())
|
||||
{
|
||||
focusOutline = currentFocus->getLookAndFeel().createFocusOutlineForComponent (*currentFocus);
|
||||
|
||||
if (focusOutline != nullptr)
|
||||
focusOutline->setOwner (currentFocus);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
focusOutline = nullptr;
|
||||
}
|
||||
|
||||
void Desktop::handleAsyncUpdate()
|
||||
{
|
||||
// The component may be deleted during this operation, but we'll use a SafePointer rather than a
|
||||
// BailOutChecker so that any remaining listeners will still get a callback (with a null pointer).
|
||||
focusListeners.call ([currentFocus = WeakReference<Component> { Component::getCurrentlyFocusedComponent() }] (FocusChangeListener& l)
|
||||
{
|
||||
l.globalFocusChanged (currentFocus.get());
|
||||
});
|
||||
|
||||
updateFocusOutline();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::addDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.add (l); }
|
||||
void Desktop::removeDarkModeSettingListener (DarkModeSettingListener* l) { darkModeSettingListeners.remove (l); }
|
||||
|
||||
void Desktop::darkModeChanged() { darkModeSettingListeners.call ([] (auto& l) { l.darkModeSettingChanged(); }); }
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::resetTimer()
|
||||
{
|
||||
if (mouseListeners.size() == 0)
|
||||
stopTimer();
|
||||
else
|
||||
startTimer (100);
|
||||
|
||||
lastFakeMouseMove = getMousePositionFloat();
|
||||
}
|
||||
|
||||
ListenerList<MouseListener>& Desktop::getMouseListeners()
|
||||
{
|
||||
resetTimer();
|
||||
return mouseListeners;
|
||||
}
|
||||
|
||||
void Desktop::addGlobalMouseListener (MouseListener* listener)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
mouseListeners.add (listener);
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
void Desktop::removeGlobalMouseListener (MouseListener* listener)
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
mouseListeners.remove (listener);
|
||||
resetTimer();
|
||||
}
|
||||
|
||||
void Desktop::timerCallback()
|
||||
{
|
||||
if (lastFakeMouseMove != getMousePositionFloat())
|
||||
sendMouseMove();
|
||||
}
|
||||
|
||||
void Desktop::sendMouseMove()
|
||||
{
|
||||
if (! mouseListeners.isEmpty())
|
||||
{
|
||||
startTimer (20);
|
||||
|
||||
lastFakeMouseMove = getMousePositionFloat();
|
||||
|
||||
if (auto* target = findComponentAt (lastFakeMouseMove.roundToInt()))
|
||||
{
|
||||
Component::BailOutChecker checker (target);
|
||||
auto pos = target->getLocalPoint (nullptr, lastFakeMouseMove);
|
||||
auto now = Time::getCurrentTime();
|
||||
|
||||
const MouseEvent me (getMainMouseSource(), pos, ModifierKeys::currentModifiers, MouseInputSource::defaultPressure,
|
||||
MouseInputSource::defaultOrientation, MouseInputSource::defaultRotation,
|
||||
MouseInputSource::defaultTiltX, MouseInputSource::defaultTiltY,
|
||||
target, target, now, pos, now, 0, false);
|
||||
|
||||
if (me.mods.isAnyMouseButtonDown())
|
||||
mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseDrag (me); });
|
||||
else
|
||||
mouseListeners.callChecked (checker, [&] (MouseListener& l) { l.mouseMove (me); });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setKioskModeComponent (Component* componentToUse, bool allowMenusAndBars)
|
||||
{
|
||||
if (kioskModeReentrant)
|
||||
return;
|
||||
|
||||
const ScopedValueSetter<bool> setter (kioskModeReentrant, true, false);
|
||||
|
||||
if (kioskModeComponent != componentToUse)
|
||||
{
|
||||
// agh! Don't delete or remove a component from the desktop while it's still the kiosk component!
|
||||
jassert (kioskModeComponent == nullptr || ComponentPeer::getPeerFor (kioskModeComponent) != nullptr);
|
||||
|
||||
if (auto* oldKioskComp = kioskModeComponent)
|
||||
{
|
||||
kioskModeComponent = nullptr; // (to make sure that isKioskMode() returns false when resizing the old one)
|
||||
setKioskComponent (oldKioskComp, false, allowMenusAndBars);
|
||||
oldKioskComp->setBounds (kioskComponentOriginalBounds);
|
||||
}
|
||||
|
||||
kioskModeComponent = componentToUse;
|
||||
|
||||
if (kioskModeComponent != nullptr)
|
||||
{
|
||||
// Only components that are already on the desktop can be put into kiosk mode!
|
||||
jassert (ComponentPeer::getPeerFor (kioskModeComponent) != nullptr);
|
||||
|
||||
kioskComponentOriginalBounds = kioskModeComponent->getBounds();
|
||||
setKioskComponent (kioskModeComponent, true, allowMenusAndBars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Desktop::setOrientationsEnabled (int newOrientations)
|
||||
{
|
||||
if (allowedOrientations != newOrientations)
|
||||
{
|
||||
// Dodgy set of flags being passed here! Make sure you specify at least one permitted orientation.
|
||||
jassert (newOrientations != 0 && (newOrientations & ~allOrientations) == 0);
|
||||
|
||||
allowedOrientations = newOrientations;
|
||||
allowedOrientationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int Desktop::getOrientationsEnabled() const noexcept
|
||||
{
|
||||
return allowedOrientations;
|
||||
}
|
||||
|
||||
bool Desktop::isOrientationEnabled (DisplayOrientation orientation) const noexcept
|
||||
{
|
||||
// Make sure you only pass one valid flag in here...
|
||||
jassert (orientation == upright || orientation == upsideDown
|
||||
|| orientation == rotatedClockwise || orientation == rotatedAntiClockwise);
|
||||
|
||||
return (allowedOrientations & orientation) != 0;
|
||||
}
|
||||
|
||||
void Desktop::setGlobalScaleFactor (float newScaleFactor) noexcept
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
if (! approximatelyEqual (masterScaleFactor, newScaleFactor))
|
||||
{
|
||||
masterScaleFactor = newScaleFactor;
|
||||
displays->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool Desktop::isHeadless() const noexcept
|
||||
{
|
||||
return displays->displays.isEmpty();
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Classes can implement this interface and register themselves with the Desktop class
|
||||
to receive callbacks when the currently focused component changes.
|
||||
|
||||
@see Desktop::addFocusChangeListener, Desktop::removeFocusChangeListener
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FocusChangeListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~FocusChangeListener() = default;
|
||||
|
||||
/** Callback to indicate that the currently focused component has changed. */
|
||||
virtual void globalFocusChanged (Component* focusedComponent) = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Classes can implement this interface and register themselves with the Desktop class
|
||||
to receive callbacks when the operating system dark mode setting changes. The
|
||||
Desktop::isDarkModeActive() method can then be used to query the current setting.
|
||||
|
||||
@see Desktop::addDarkModeSettingListener, Desktop::removeDarkModeSettingListener,
|
||||
Desktop::isDarkModeActive
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DarkModeSettingListener
|
||||
{
|
||||
public:
|
||||
/** Destructor. */
|
||||
virtual ~DarkModeSettingListener() = default;
|
||||
|
||||
/** Callback to indicate that the dark mode setting has changed. */
|
||||
virtual void darkModeSettingChanged() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Describes and controls aspects of the computer's desktop.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Desktop : private DeletedAtShutdown,
|
||||
private Timer,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** There's only one desktop object, and this method will return it. */
|
||||
static Desktop& JUCE_CALLTYPE getInstance();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the mouse position.
|
||||
|
||||
The coordinates are relative to the top-left of the main monitor.
|
||||
|
||||
Note that this is just a shortcut for calling getMainMouseSource().getScreenPosition(), and
|
||||
you should only resort to grabbing the global mouse position if there's really no
|
||||
way to get the coordinates via a mouse event callback instead.
|
||||
*/
|
||||
static Point<int> getMousePosition();
|
||||
|
||||
/** Makes the mouse pointer jump to a given location.
|
||||
The coordinates are relative to the top-left of the main monitor.
|
||||
Note that this is a pretty old method, kept around mainly for backwards-compatibility,
|
||||
and you should use the MouseInputSource class directly in new code.
|
||||
*/
|
||||
static void setMousePosition (Point<int> newPosition);
|
||||
|
||||
/** Returns the last position at which a mouse button was pressed.
|
||||
|
||||
Note that this is just a shortcut for calling getMainMouseSource().getLastMouseDownPosition(),
|
||||
and in a multi-touch environment, it doesn't make much sense. ALWAYS prefer to
|
||||
get this information via other means, such as MouseEvent::getMouseDownScreenPosition()
|
||||
if possible, and only ever call this as a last resort.
|
||||
*/
|
||||
static Point<int> getLastMouseDownPosition();
|
||||
|
||||
/** Returns the number of times the mouse button has been clicked since the app started.
|
||||
Each mouse-down event increments this number by 1.
|
||||
@see getMouseWheelMoveCounter
|
||||
*/
|
||||
int getMouseButtonClickCounter() const noexcept;
|
||||
|
||||
/** Returns the number of times the mouse wheel has been moved since the app started.
|
||||
Each mouse-wheel event increments this number by 1.
|
||||
@see getMouseButtonClickCounter
|
||||
*/
|
||||
int getMouseWheelMoveCounter() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** This lets you prevent the screensaver from becoming active.
|
||||
|
||||
Handy if you're running some sort of presentation app where having a screensaver
|
||||
appear would be annoying.
|
||||
|
||||
Pass false to disable the screensaver, and true to re-enable it. (Note that this
|
||||
won't enable a screensaver unless the user has actually set one up).
|
||||
|
||||
The disablement will only happen while the JUCE application is the foreground
|
||||
process - if another task is running in front of it, then the screensaver will
|
||||
be unaffected.
|
||||
|
||||
@see isScreenSaverEnabled
|
||||
*/
|
||||
static void setScreenSaverEnabled (bool isEnabled);
|
||||
|
||||
/** Returns true if the screensaver has not been turned off.
|
||||
|
||||
This will return the last value passed into setScreenSaverEnabled(). Note that
|
||||
it won't tell you whether the user is actually using a screen saver, just
|
||||
whether this app is deliberately preventing one from running.
|
||||
|
||||
@see setScreenSaverEnabled
|
||||
*/
|
||||
static bool isScreenSaverEnabled();
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a MouseListener that will receive all mouse events that occur on
|
||||
any component.
|
||||
|
||||
@see removeGlobalMouseListener
|
||||
*/
|
||||
void addGlobalMouseListener (MouseListener* listener);
|
||||
|
||||
/** Unregisters a MouseListener that was added with addGlobalMouseListener().
|
||||
|
||||
@see addGlobalMouseListener
|
||||
*/
|
||||
void removeGlobalMouseListener (MouseListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a FocusChangeListener that will receive a callback whenever the focused
|
||||
component changes.
|
||||
|
||||
@see removeFocusChangeListener
|
||||
*/
|
||||
void addFocusChangeListener (FocusChangeListener* listener);
|
||||
|
||||
/** Unregisters a FocusChangeListener that was added with addFocusChangeListener().
|
||||
|
||||
@see addFocusChangeListener
|
||||
*/
|
||||
void removeFocusChangeListener (FocusChangeListener* listener);
|
||||
|
||||
//==============================================================================
|
||||
/** Registers a DarkModeSettingListener that will receive a callback when the
|
||||
operating system dark mode setting changes. To query whether dark mode is on
|
||||
use the isDarkModeActive() method.
|
||||
|
||||
@see isDarkModeActive, removeDarkModeSettingListener
|
||||
*/
|
||||
void addDarkModeSettingListener (DarkModeSettingListener* listener);
|
||||
|
||||
/** Unregisters a DarkModeSettingListener that was added with addDarkModeSettingListener().
|
||||
|
||||
@see addDarkModeSettingListener
|
||||
*/
|
||||
void removeDarkModeSettingListener (DarkModeSettingListener* listener);
|
||||
|
||||
/** True if the operating system "dark mode" is active.
|
||||
|
||||
To receive a callback when this setting changes implement the DarkModeSettingListener
|
||||
interface and use the addDarkModeSettingListener() to register a listener.
|
||||
|
||||
@see addDarkModeSettingListener, removeDarkModeSettingListener
|
||||
*/
|
||||
bool isDarkModeActive() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Takes a component and makes it full-screen, removing the taskbar, dock, etc.
|
||||
|
||||
The component must already be on the desktop for this method to work. It will
|
||||
be resized to completely fill the screen and any extraneous taskbars, menu bars,
|
||||
etc will be hidden.
|
||||
|
||||
To exit kiosk mode, just call setKioskModeComponent (nullptr). When this is called,
|
||||
the component that's currently being used will be resized back to the size
|
||||
and position it was in before being put into this mode.
|
||||
|
||||
If allowMenusAndBars is true, things like the menu and dock (on mac) are still
|
||||
allowed to pop up when the mouse moves onto them. If this is false, it'll try
|
||||
to hide as much on-screen paraphernalia as possible.
|
||||
*/
|
||||
void setKioskModeComponent (Component* componentToUse,
|
||||
bool allowMenusAndBars = true);
|
||||
|
||||
/** Returns the component that is currently being used in kiosk-mode.
|
||||
|
||||
This is the component that was last set by setKioskModeComponent(). If none
|
||||
has been set, this returns nullptr.
|
||||
*/
|
||||
Component* getKioskModeComponent() const noexcept { return kioskModeComponent; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of components that are currently active as top-level
|
||||
desktop windows.
|
||||
|
||||
@see getComponent, Component::addToDesktop
|
||||
*/
|
||||
int getNumComponents() const noexcept;
|
||||
|
||||
/** Returns one of the top-level desktop window components.
|
||||
|
||||
The index is from 0 to getNumComponents() - 1. This could return 0 if the
|
||||
index is out-of-range.
|
||||
|
||||
@see getNumComponents, Component::addToDesktop
|
||||
*/
|
||||
Component* getComponent (int index) const noexcept;
|
||||
|
||||
/** Finds the component at a given screen location.
|
||||
|
||||
This will drill down into top-level windows to find the child component at
|
||||
the given position.
|
||||
|
||||
Returns nullptr if the coordinates are inside a non-JUCE window.
|
||||
*/
|
||||
Component* findComponentAt (Point<int> screenPosition) const;
|
||||
|
||||
/** The Desktop object has a ComponentAnimator instance which can be used for performing
|
||||
your animations.
|
||||
|
||||
Having a single shared ComponentAnimator object makes it more efficient when multiple
|
||||
components are being moved around simultaneously. It's also more convenient than having
|
||||
to manage your own instance of one.
|
||||
|
||||
@see ComponentAnimator
|
||||
*/
|
||||
ComponentAnimator& getAnimator() noexcept { return animator; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the current default look-and-feel for components which don't have one
|
||||
explicitly set.
|
||||
@see setDefaultLookAndFeel
|
||||
*/
|
||||
LookAndFeel& getDefaultLookAndFeel() noexcept;
|
||||
|
||||
/** Changes the default look-and-feel.
|
||||
@param newDefaultLookAndFeel the new look-and-feel object to use - if this is
|
||||
set to nullptr, it will revert to using the system's
|
||||
default one. The object passed-in must be deleted by the
|
||||
caller when it's no longer needed.
|
||||
@see getDefaultLookAndFeel
|
||||
*/
|
||||
void setDefaultLookAndFeel (LookAndFeel* newDefaultLookAndFeel);
|
||||
|
||||
//==============================================================================
|
||||
/** Provides access to the array of mouse sources, for iteration.
|
||||
In a traditional single-mouse system, there might be only one MouseInputSource. On a
|
||||
multi-touch system, there could be one input source per potential finger. The number
|
||||
of mouse sources returned here may increase dynamically as the program runs.
|
||||
To find out how many mouse events are currently happening, use getNumDraggingMouseSources().
|
||||
*/
|
||||
const Array<MouseInputSource>& getMouseSources() const noexcept;
|
||||
|
||||
/** Returns the number of MouseInputSource objects the system has at its disposal.
|
||||
In a traditional single-mouse system, there might be only one MouseInputSource. On a
|
||||
multi-touch system, there could be one input source per potential finger. The number
|
||||
of mouse sources returned here may increase dynamically as the program runs.
|
||||
To find out how many mouse events are currently happening, use getNumDraggingMouseSources().
|
||||
@see getMouseSource
|
||||
*/
|
||||
int getNumMouseSources() const noexcept;
|
||||
|
||||
/** Returns one of the system's MouseInputSource objects.
|
||||
The index should be from 0 to getNumMouseSources() - 1. Out-of-range indexes will return
|
||||
a null pointer.
|
||||
In a traditional single-mouse system, there might be only one object. On a multi-touch
|
||||
system, there could be one input source per potential finger.
|
||||
*/
|
||||
MouseInputSource* getMouseSource (int index) const noexcept;
|
||||
|
||||
/** Returns the main mouse input device that the system is using.
|
||||
@see getNumMouseSources()
|
||||
*/
|
||||
MouseInputSource getMainMouseSource() const noexcept;
|
||||
|
||||
/** Returns the number of mouse-sources that are currently being dragged.
|
||||
In a traditional single-mouse system, this will be 0 or 1, depending on whether a
|
||||
JUCE component has the button down on it. In a multi-touch system, this could
|
||||
be any number from 0 to the number of simultaneous touches that can be detected.
|
||||
*/
|
||||
int getNumDraggingMouseSources() const noexcept;
|
||||
|
||||
/** Returns one of the mouse sources that's currently being dragged.
|
||||
The index should be between 0 and getNumDraggingMouseSources() - 1. If the index is
|
||||
out of range, or if no mice or fingers are down, this will return a null pointer.
|
||||
*/
|
||||
MouseInputSource* getDraggingMouseSource (int index) const noexcept;
|
||||
|
||||
/** Ensures that a non-stop stream of mouse-drag events will be sent during the
|
||||
current mouse-drag operation.
|
||||
|
||||
This allows you to make sure that mouseDrag() events are sent continuously, even
|
||||
when the mouse isn't moving. This can be useful for things like auto-scrolling
|
||||
components when the mouse is near an edge.
|
||||
|
||||
Call this method during a mouseDown() or mouseDrag() callback, specifying the
|
||||
minimum interval between consecutive mouse drag callbacks. The callbacks
|
||||
will continue until the mouse is released, and then the interval will be reset,
|
||||
so you need to make sure it's called every time you begin a drag event.
|
||||
Passing an interval of 0 or less will cancel the auto-repeat.
|
||||
|
||||
@see mouseDrag
|
||||
*/
|
||||
void beginDragAutoRepeat (int millisecondsBetweenCallbacks);
|
||||
|
||||
//==============================================================================
|
||||
/** In a tablet/mobile device which can be turned around, this is used to indicate the orientation. */
|
||||
enum DisplayOrientation
|
||||
{
|
||||
upright = 1, /**< Indicates that the device is the normal way up. */
|
||||
upsideDown = 2, /**< Indicates that the device is upside-down. */
|
||||
rotatedClockwise = 4, /**< Indicates that the device is turned 90 degrees clockwise from its upright position. */
|
||||
rotatedAntiClockwise = 8, /**< Indicates that the device is turned 90 degrees anti-clockwise from its upright position. */
|
||||
|
||||
allOrientations = 1 + 2 + 4 + 8 /**< A combination of all the orientation values */
|
||||
};
|
||||
|
||||
/** In a tablet device which can be turned around, this returns the current orientation. */
|
||||
DisplayOrientation getCurrentOrientation() const;
|
||||
|
||||
/** Sets which orientations the display is allowed to auto-rotate to.
|
||||
|
||||
For devices that support rotating desktops, this lets you specify which of the orientations your app can use.
|
||||
|
||||
The parameter is a bitwise or-ed combination of the values in DisplayOrientation, and must contain at least one
|
||||
set bit.
|
||||
*/
|
||||
void setOrientationsEnabled (int allowedOrientations);
|
||||
|
||||
/** Returns the set of orientations the display is allowed to rotate to.
|
||||
@see setOrientationsEnabled
|
||||
*/
|
||||
int getOrientationsEnabled() const noexcept;
|
||||
|
||||
/** Returns whether the display is allowed to auto-rotate to the given orientation.
|
||||
Each orientation can be enabled using setOrientationEnabled(). By default, all orientations are allowed.
|
||||
*/
|
||||
bool isOrientationEnabled (DisplayOrientation orientation) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the Displays object representing the connected displays.
|
||||
|
||||
@see Displays
|
||||
*/
|
||||
const Displays& getDisplays() const noexcept { return *displays; }
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a global scale factor to be used for all desktop windows.
|
||||
Setting this will also scale the monitor sizes that are returned by getDisplays().
|
||||
*/
|
||||
void setGlobalScaleFactor (float newScaleFactor) noexcept;
|
||||
|
||||
/** Returns the current global scale factor, as set by setGlobalScaleFactor().
|
||||
@see setGlobalScaleFactor
|
||||
*/
|
||||
float getGlobalScaleFactor() const noexcept { return masterScaleFactor; }
|
||||
|
||||
//==============================================================================
|
||||
/** True if the OS supports semitransparent windows */
|
||||
static bool canUseSemiTransparentWindows() noexcept;
|
||||
|
||||
#if JUCE_MAC && ! defined (DOXYGEN)
|
||||
[[deprecated ("This macOS-specific method has been deprecated in favour of the cross-platform "
|
||||
" isDarkModeActive() method.")]]
|
||||
static bool isOSXDarkModeActive() { return Desktop::getInstance().isDarkModeActive(); }
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/** Returns true on a headless system where there are no connected displays. */
|
||||
bool isHeadless() const noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static Desktop* instance;
|
||||
|
||||
friend class Component;
|
||||
friend class ComponentPeer;
|
||||
friend class detail::MouseInputSourceImpl;
|
||||
friend class DeletedAtShutdown;
|
||||
friend class detail::TopLevelWindowManager;
|
||||
friend class Displays;
|
||||
|
||||
std::unique_ptr<detail::MouseInputSourceList> mouseSources;
|
||||
|
||||
ListenerList<MouseListener> mouseListeners;
|
||||
ListenerList<FocusChangeListener> focusListeners;
|
||||
ListenerList<DarkModeSettingListener> darkModeSettingListeners;
|
||||
|
||||
Array<Component*> desktopComponents;
|
||||
Array<ComponentPeer*> peers;
|
||||
|
||||
std::unique_ptr<Displays> displays;
|
||||
|
||||
Point<float> lastFakeMouseMove;
|
||||
void sendMouseMove();
|
||||
|
||||
int mouseClickCounter = 0, mouseWheelCounter = 0;
|
||||
void incrementMouseClickCounter() noexcept;
|
||||
void incrementMouseWheelCounter() noexcept;
|
||||
|
||||
std::unique_ptr<LookAndFeel> defaultLookAndFeel;
|
||||
WeakReference<LookAndFeel> currentLookAndFeel;
|
||||
|
||||
std::unique_ptr<FocusOutline> focusOutline;
|
||||
|
||||
Component* kioskModeComponent = nullptr;
|
||||
Rectangle<int> kioskComponentOriginalBounds;
|
||||
bool kioskModeReentrant = false;
|
||||
|
||||
int allowedOrientations = allOrientations;
|
||||
void allowedOrientationsChanged();
|
||||
|
||||
float masterScaleFactor;
|
||||
|
||||
ComponentAnimator animator;
|
||||
|
||||
void timerCallback() override;
|
||||
void resetTimer();
|
||||
ListenerList<MouseListener>& getMouseListeners();
|
||||
|
||||
void addDesktopComponent (Component*);
|
||||
void removeDesktopComponent (Component*);
|
||||
void componentBroughtToFront (Component*);
|
||||
|
||||
void setKioskComponent (Component*, bool shouldBeEnabled, bool allowMenusAndBars);
|
||||
|
||||
void triggerFocusCallback();
|
||||
void updateFocusOutline();
|
||||
void handleAsyncUpdate() override;
|
||||
|
||||
static Point<float> getMousePositionFloat();
|
||||
|
||||
static double getDefaultMasterScale();
|
||||
|
||||
Desktop();
|
||||
~Desktop() override;
|
||||
|
||||
//==============================================================================
|
||||
class NativeDarkModeChangeDetectorImpl;
|
||||
std::unique_ptr<NativeDarkModeChangeDetectorImpl> nativeDarkModeChangeDetectorImpl;
|
||||
|
||||
static std::unique_ptr<NativeDarkModeChangeDetectorImpl> createNativeDarkModeChangeDetectorImpl();
|
||||
void darkModeChanged();
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Desktop)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
Displays::Displays (Desktop& desktop)
|
||||
{
|
||||
init (desktop);
|
||||
}
|
||||
|
||||
void Displays::init (Desktop& desktop)
|
||||
{
|
||||
findDisplays (desktop.getGlobalScaleFactor());
|
||||
}
|
||||
|
||||
const Displays::Display* Displays::getDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
|
||||
{
|
||||
int maxArea = -1;
|
||||
const Display* foundDisplay = nullptr;
|
||||
|
||||
for (auto& display : displays)
|
||||
{
|
||||
auto displayArea = display.totalArea;
|
||||
|
||||
if (isPhysical)
|
||||
displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
|
||||
|
||||
displayArea = displayArea.getIntersection (rect);
|
||||
auto area = displayArea.getWidth() * displayArea.getHeight();
|
||||
|
||||
if (area >= maxArea)
|
||||
{
|
||||
maxArea = area;
|
||||
foundDisplay = &display;
|
||||
}
|
||||
}
|
||||
|
||||
return foundDisplay;
|
||||
}
|
||||
|
||||
const Displays::Display* Displays::getDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
|
||||
{
|
||||
auto minDistance = std::numeric_limits<int>::max();
|
||||
const Display* foundDisplay = nullptr;
|
||||
|
||||
for (auto& display : displays)
|
||||
{
|
||||
auto displayArea = display.totalArea;
|
||||
|
||||
if (isPhysical)
|
||||
displayArea = (displayArea.withZeroOrigin() * display.scale) + display.topLeftPhysical;
|
||||
|
||||
if (displayArea.contains (point))
|
||||
return &display;
|
||||
|
||||
auto distance = displayArea.getCentre().getDistanceFrom (point);
|
||||
|
||||
if (distance <= minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
foundDisplay = &display;
|
||||
}
|
||||
}
|
||||
|
||||
return foundDisplay;
|
||||
}
|
||||
|
||||
Rectangle<int> Displays::physicalToLogical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
return physicalToLogical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
|
||||
}
|
||||
|
||||
Rectangle<float> Displays::physicalToLogical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
|
||||
: getDisplayForRect (rect.toNearestInt(), true);
|
||||
|
||||
if (display == nullptr)
|
||||
return rect;
|
||||
|
||||
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
|
||||
|
||||
return ((rect - display->topLeftPhysical.toFloat()) / (display->scale / globalScale))
|
||||
+ (display->totalArea.getTopLeft().toFloat() * globalScale);
|
||||
}
|
||||
|
||||
Rectangle<int> Displays::logicalToPhysical (Rectangle<int> rect, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
return logicalToPhysical (rect.toFloat(), useScaleFactorOfDisplay).toNearestInt();
|
||||
}
|
||||
|
||||
Rectangle<float> Displays::logicalToPhysical (Rectangle<float> rect, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
|
||||
: getDisplayForRect (rect.toNearestInt(), false);
|
||||
|
||||
if (display == nullptr)
|
||||
return rect;
|
||||
|
||||
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
|
||||
|
||||
return ((rect.toFloat() - (display->totalArea.getTopLeft().toFloat() * globalScale)) * (display->scale / globalScale))
|
||||
+ display->topLeftPhysical.toFloat();
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
Point<ValueType> Displays::physicalToLogical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
|
||||
: getDisplayForPoint (point.roundToInt(), true);
|
||||
|
||||
if (display == nullptr)
|
||||
return point;
|
||||
|
||||
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
|
||||
|
||||
Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
|
||||
Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
|
||||
|
||||
return ((point - physicalTopLeft) / (display->scale / globalScale)) + (logicalTopLeft * globalScale);
|
||||
}
|
||||
|
||||
template <typename ValueType>
|
||||
Point<ValueType> Displays::logicalToPhysical (Point<ValueType> point, const Display* useScaleFactorOfDisplay) const noexcept
|
||||
{
|
||||
const auto* display = useScaleFactorOfDisplay != nullptr ? useScaleFactorOfDisplay
|
||||
: getDisplayForPoint (point.roundToInt(), false);
|
||||
|
||||
if (display == nullptr)
|
||||
return point;
|
||||
|
||||
auto globalScale = Desktop::getInstance().getGlobalScaleFactor();
|
||||
|
||||
Point<ValueType> logicalTopLeft (static_cast<ValueType> (display->totalArea.getX()), static_cast<ValueType> (display->totalArea.getY()));
|
||||
Point<ValueType> physicalTopLeft (static_cast<ValueType> (display->topLeftPhysical.getX()), static_cast<ValueType> (display->topLeftPhysical.getY()));
|
||||
|
||||
return ((point - (logicalTopLeft * globalScale)) * (display->scale / globalScale)) + physicalTopLeft;
|
||||
}
|
||||
|
||||
const Displays::Display* Displays::getPrimaryDisplay() const noexcept
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
|
||||
const auto iter = std::find_if (displays.begin(), displays.end(), [] (auto& d) { return d.isMain; });
|
||||
return iter != displays.end() ? iter : nullptr;
|
||||
}
|
||||
|
||||
RectangleList<int> Displays::getRectangleList (bool userAreasOnly) const
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
RectangleList<int> rl;
|
||||
|
||||
for (auto& d : displays)
|
||||
rl.addWithoutMerging (userAreasOnly ? d.userArea : d.totalArea);
|
||||
|
||||
return rl;
|
||||
}
|
||||
|
||||
Rectangle<int> Displays::getTotalBounds (bool userAreasOnly) const
|
||||
{
|
||||
return getRectangleList (userAreasOnly).getBounds();
|
||||
}
|
||||
|
||||
void Displays::refresh()
|
||||
{
|
||||
Array<Display> oldDisplays;
|
||||
oldDisplays.swapWith (displays);
|
||||
|
||||
init (Desktop::getInstance());
|
||||
|
||||
if (oldDisplays != displays)
|
||||
{
|
||||
for (auto i = ComponentPeer::getNumPeers(); --i >= 0;)
|
||||
if (auto* peer = ComponentPeer::getPeer (i))
|
||||
peer->handleScreenSizeChange();
|
||||
}
|
||||
}
|
||||
|
||||
static auto tie (const Displays::Display& d)
|
||||
{
|
||||
return std::tie (d.dpi,
|
||||
d.isMain,
|
||||
d.keyboardInsets,
|
||||
d.safeAreaInsets,
|
||||
d.scale,
|
||||
d.topLeftPhysical,
|
||||
d.totalArea,
|
||||
d.userArea);
|
||||
}
|
||||
|
||||
static bool operator== (const Displays::Display& d1, const Displays::Display& d2) noexcept
|
||||
{
|
||||
return tie (d1) == tie (d2);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// These methods are used for converting the totalArea and userArea Rectangles in Display from physical to logical
|
||||
// pixels. We do this by constructing a graph of connected displays where the root node has position (0, 0); this can be
|
||||
// safely converted to logical pixels using its scale factor and we can then traverse the graph and work out the logical pixels
|
||||
// for all the other connected displays. We need to do this as the logical bounds of a display depend not only on its scale
|
||||
// factor but also the scale factor of the displays connected to it.
|
||||
|
||||
/**
|
||||
Represents a node in our graph of displays.
|
||||
*/
|
||||
struct DisplayNode
|
||||
{
|
||||
/** The Display object that this represents. */
|
||||
Displays::Display* display;
|
||||
|
||||
/** True if this represents the 'root' display with position (0, 0). */
|
||||
bool isRoot = false;
|
||||
|
||||
/** The parent node of this node in our display graph. This will have a correct logicalArea. */
|
||||
DisplayNode* parent = nullptr;
|
||||
|
||||
/** The logical area to be calculated. This will be valid after processDisplay() has
|
||||
been called on this node.
|
||||
*/
|
||||
Rectangle<double> logicalArea;
|
||||
};
|
||||
|
||||
/** Recursive - will calculate and set the logicalArea member of current. */
|
||||
static void processDisplay (DisplayNode* currentNode, Array<DisplayNode>& allNodes)
|
||||
{
|
||||
const auto physicalArea = currentNode->display->totalArea.toDouble();
|
||||
const auto scale = currentNode->display->scale;
|
||||
|
||||
if (! currentNode->isRoot)
|
||||
{
|
||||
const auto logicalWidth = physicalArea.getWidth() / scale;
|
||||
const auto logicalHeight = physicalArea.getHeight() / scale;
|
||||
|
||||
const auto physicalParentArea = currentNode->parent->display->totalArea.toDouble();
|
||||
const auto logicalParentArea = currentNode->parent->logicalArea; // logical area of parent has already been calculated
|
||||
const auto parentScale = currentNode->parent->display->scale;
|
||||
|
||||
Rectangle<double> logicalArea (0.0, 0.0, logicalWidth, logicalHeight);
|
||||
|
||||
if (approximatelyEqual (physicalArea.getRight(), physicalParentArea.getX())) logicalArea.setPosition ({ logicalParentArea.getX() - logicalWidth, physicalArea.getY() / parentScale }); // on left
|
||||
else if (approximatelyEqual (physicalArea.getX(), physicalParentArea.getRight())) logicalArea.setPosition ({ logicalParentArea.getRight(), physicalArea.getY() / parentScale }); // on right
|
||||
else if (approximatelyEqual (physicalArea.getBottom(), physicalParentArea.getY())) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getY() - logicalHeight }); // on top
|
||||
else if (approximatelyEqual (physicalArea.getY(), physicalParentArea.getBottom())) logicalArea.setPosition ({ physicalArea.getX() / parentScale, logicalParentArea.getBottom() }); // on bottom
|
||||
else jassertfalse;
|
||||
|
||||
currentNode->logicalArea = logicalArea;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If currentNode is the root (position (0, 0)) then we can just scale the physical area
|
||||
currentNode->logicalArea = physicalArea / scale;
|
||||
currentNode->parent = currentNode;
|
||||
}
|
||||
|
||||
// Find child nodes
|
||||
Array<DisplayNode*> children;
|
||||
for (auto& node : allNodes)
|
||||
{
|
||||
// Already calculated
|
||||
if (node.parent != nullptr)
|
||||
continue;
|
||||
|
||||
const auto otherPhysicalArea = node.display->totalArea.toDouble();
|
||||
|
||||
// If the displays are touching on any side
|
||||
if (approximatelyEqual (otherPhysicalArea.getX(), physicalArea.getRight()) || approximatelyEqual (otherPhysicalArea.getRight(), physicalArea.getX())
|
||||
|| approximatelyEqual (otherPhysicalArea.getY(), physicalArea.getBottom()) || approximatelyEqual (otherPhysicalArea.getBottom(), physicalArea.getY()))
|
||||
{
|
||||
node.parent = currentNode;
|
||||
children.add (&node);
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively process all child nodes
|
||||
for (auto child : children)
|
||||
processDisplay (child, allNodes);
|
||||
}
|
||||
|
||||
/** This is called when the displays Array has been filled out with the info for all connected displays and the
|
||||
totalArea and userArea Rectangles need to be converted from physical to logical coordinates.
|
||||
*/
|
||||
void Displays::updateToLogical()
|
||||
{
|
||||
if (displays.size() == 1)
|
||||
{
|
||||
auto& display = displays.getReference (0);
|
||||
|
||||
display.totalArea = (display.totalArea.toDouble() / display.scale).toNearestInt();
|
||||
display.userArea = (display.userArea.toDouble() / display.scale).toNearestInt();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Array<DisplayNode> displayNodes;
|
||||
|
||||
for (auto& d : displays)
|
||||
{
|
||||
DisplayNode node;
|
||||
|
||||
node.display = &d;
|
||||
|
||||
if (d.totalArea.getTopLeft() == Point<int>())
|
||||
node.isRoot = true;
|
||||
|
||||
displayNodes.add (node);
|
||||
}
|
||||
|
||||
auto* root = [&displayNodes]() -> DisplayNode*
|
||||
{
|
||||
for (auto& node : displayNodes)
|
||||
if (node.isRoot)
|
||||
return &node;
|
||||
|
||||
auto minDistance = std::numeric_limits<int>::max();
|
||||
DisplayNode* retVal = nullptr;
|
||||
|
||||
for (auto& node : displayNodes)
|
||||
{
|
||||
auto distance = node.display->totalArea.getTopLeft().getDistanceFrom ({});
|
||||
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
retVal = &node;
|
||||
}
|
||||
}
|
||||
|
||||
if (retVal != nullptr)
|
||||
retVal->isRoot = true;
|
||||
|
||||
return retVal;
|
||||
}();
|
||||
|
||||
// Must have a root node!
|
||||
jassert (root != nullptr);
|
||||
|
||||
// Recursively traverse the display graph from the root and work out logical bounds
|
||||
processDisplay (root, displayNodes);
|
||||
|
||||
for (auto& node : displayNodes)
|
||||
{
|
||||
// All of the nodes should have a parent
|
||||
jassert (node.parent != nullptr);
|
||||
|
||||
auto relativeUserArea = (node.display->userArea.toDouble() - node.display->totalArea.toDouble().getTopLeft()) / node.display->scale;
|
||||
|
||||
// Now set Display::totalArea and ::userArea using the logical area that we have calculated
|
||||
node.display->topLeftPhysical = node.display->totalArea.getTopLeft();
|
||||
node.display->totalArea = node.logicalArea.toNearestInt();
|
||||
node.display->userArea = (relativeUserArea + node.logicalArea.getTopLeft()).toNearestInt();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef DOXYGEN
|
||||
// explicit template instantiations
|
||||
template Point<int> Displays::physicalToLogical (Point<int>, const Display*) const noexcept;
|
||||
template Point<float> Displays::physicalToLogical (Point<float>, const Display*) const noexcept;
|
||||
|
||||
template Point<int> Displays::logicalToPhysical (Point<int>, const Display*) const noexcept;
|
||||
template Point<float> Displays::logicalToPhysical (Point<float>, const Display*) const noexcept;
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
// Deprecated methods
|
||||
const Displays::Display& Displays::getDisplayContaining (Point<int> position) const noexcept
|
||||
{
|
||||
JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED
|
||||
const auto* best = &displays.getReference (0);
|
||||
auto bestDistance = std::numeric_limits<int>::max();
|
||||
|
||||
for (auto& d : displays)
|
||||
{
|
||||
if (d.totalArea.contains (position))
|
||||
{
|
||||
best = &d;
|
||||
break;
|
||||
}
|
||||
|
||||
auto distance = d.totalArea.getCentre().getDistanceFrom (position);
|
||||
|
||||
if (distance < bestDistance)
|
||||
{
|
||||
bestDistance = distance;
|
||||
best = &d;
|
||||
}
|
||||
}
|
||||
|
||||
return *best;
|
||||
}
|
||||
|
||||
const Displays::Display& Displays::findDisplayForRect (Rectangle<int> rect, bool isPhysical) const noexcept
|
||||
{
|
||||
if (auto* display = getDisplayForRect (rect, isPhysical))
|
||||
return *display;
|
||||
|
||||
return emptyDisplay;
|
||||
}
|
||||
|
||||
const Displays::Display& Displays::findDisplayForPoint (Point<int> point, bool isPhysical) const noexcept
|
||||
{
|
||||
if (auto* display = getDisplayForPoint (point, isPhysical))
|
||||
return *display;
|
||||
|
||||
return emptyDisplay;
|
||||
}
|
||||
|
||||
const Displays::Display& Displays::getMainDisplay() const noexcept
|
||||
{
|
||||
if (auto* display = getPrimaryDisplay())
|
||||
return *display;
|
||||
|
||||
return emptyDisplay;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 details about connected display devices.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Displays
|
||||
{
|
||||
private:
|
||||
Displays (Desktop&);
|
||||
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Represents a connected display device. */
|
||||
struct JUCE_API Display
|
||||
{
|
||||
/** This will be true if this is the user's main display device. */
|
||||
bool isMain;
|
||||
|
||||
/** The total area of this display in logical pixels including any OS-dependent objects
|
||||
like the taskbar, menu bar, etc.
|
||||
*/
|
||||
Rectangle<int> totalArea;
|
||||
|
||||
/** The total area of this display in logical pixels which isn't covered by OS-dependent
|
||||
objects like the taskbar, menu bar, etc.
|
||||
*/
|
||||
Rectangle<int> userArea;
|
||||
|
||||
/** Represents the area of this display in logical pixels that is not functional for
|
||||
displaying content.
|
||||
|
||||
On mobile devices this may be the area covered by display cutouts and notches, where
|
||||
you still want to draw a background but should not position important content.
|
||||
*/
|
||||
BorderSize<int> safeAreaInsets;
|
||||
|
||||
/** Represents the area of this display in logical pixels that is obscured by an
|
||||
onscreen keyboard.
|
||||
|
||||
This is currently only supported on iOS, and on Android 11+.
|
||||
|
||||
This will only return the bounds of the keyboard when it is in 'docked' mode.
|
||||
If the keyboard is floating (e.g. on an iPad using the split keyboard mode),
|
||||
no insets will be reported.
|
||||
*/
|
||||
BorderSize<int> keyboardInsets;
|
||||
|
||||
/** The top-left of this display in physical coordinates. */
|
||||
Point<int> topLeftPhysical;
|
||||
|
||||
/** The scale factor of this display.
|
||||
|
||||
For higher-resolution displays, or displays with a user-defined scale factor set,
|
||||
this may be a value other than 1.0.
|
||||
|
||||
This value is used to convert between physical and logical pixels. For example, a Component
|
||||
with size 10x10 will use 20x20 physical pixels on a display with a scale factor of 2.0.
|
||||
*/
|
||||
double scale;
|
||||
|
||||
/** The DPI of the display.
|
||||
|
||||
This is the number of physical pixels per inch. To get the number of logical
|
||||
pixels per inch, divide this by the Display::scale value.
|
||||
*/
|
||||
double dpi;
|
||||
|
||||
/** The vertical refresh rate of the display if applicable.
|
||||
|
||||
Currently this is only used on Linux for display rate repainting.
|
||||
*/
|
||||
std::optional<double> verticalFrequencyHz;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Converts an integer Rectangle from physical to logical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Rectangle to be converted is on.
|
||||
*/
|
||||
Rectangle<int> physicalToLogical (Rectangle<int> physicalRect,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Converts a floating-point Rectangle from physical to logical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Rectangle to be converted is on.
|
||||
*/
|
||||
Rectangle<float> physicalToLogical (Rectangle<float> physicalRect,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Converts an integer Rectangle from logical to physical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Rectangle to be converted is on.
|
||||
*/
|
||||
Rectangle<int> logicalToPhysical (Rectangle<int> logicalRect,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Converts a floating-point Rectangle from logical to physical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Rectangle to be converted is on.
|
||||
*/
|
||||
Rectangle<float> logicalToPhysical (Rectangle<float> logicalRect,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Converts a Point from physical to logical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Point to be converted is on.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
Point<ValueType> physicalToLogical (Point<ValueType> physicalPoint,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Converts a Point from logical to physical pixels.
|
||||
|
||||
If useScaleFactorOfDisplay is not null then its scale factor will be used for the conversion
|
||||
regardless of the display that the Point to be converted is on.
|
||||
*/
|
||||
template <typename ValueType>
|
||||
Point<ValueType> logicalToPhysical (Point<ValueType> logicalPoint,
|
||||
const Display* useScaleFactorOfDisplay = nullptr) const noexcept;
|
||||
|
||||
/** Returns the Display object representing the display containing a given Rectangle (either
|
||||
in logical or physical pixels), or nullptr if there are no connected displays.
|
||||
|
||||
If the Rectangle lies outside all the displays then the nearest one will be returned.
|
||||
*/
|
||||
const Display* getDisplayForRect (Rectangle<int> rect, bool isPhysical = false) const noexcept;
|
||||
|
||||
/** Returns the Display object representing the display containing a given Point (either
|
||||
in logical or physical pixels), or nullptr if there are no connected displays.
|
||||
|
||||
If the Point lies outside all the displays then the nearest one will be returned.
|
||||
*/
|
||||
const Display* getDisplayForPoint (Point<int> point, bool isPhysical = false) const noexcept;
|
||||
|
||||
/** Returns the Display object representing the display acting as the user's main screen, or nullptr
|
||||
if there are no connected displays.
|
||||
*/
|
||||
const Display* getPrimaryDisplay() const noexcept;
|
||||
|
||||
/** Returns a RectangleList made up of all the displays in LOGICAL pixels. */
|
||||
RectangleList<int> getRectangleList (bool userAreasOnly) const;
|
||||
|
||||
/** Returns the smallest bounding box which contains all the displays in LOGICAL pixels. */
|
||||
Rectangle<int> getTotalBounds (bool userAreasOnly) const;
|
||||
|
||||
/** An Array containing the Display objects for all of the connected displays. */
|
||||
Array<Display> displays;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
/** @internal */
|
||||
void refresh();
|
||||
|
||||
[[deprecated ("Use the getDisplayForPoint or getDisplayForRect methods instead "
|
||||
"as they can deal with converting between logical and physical pixels.")]]
|
||||
const Display& getDisplayContaining (Point<int> position) const noexcept;
|
||||
|
||||
// These methods have been deprecated - use the methods which return a Display* instead as they will return
|
||||
// nullptr on headless systems with no connected displays
|
||||
[[deprecated]] const Display& findDisplayForRect (Rectangle<int>, bool isPhysical = false) const noexcept;
|
||||
[[deprecated]] const Display& findDisplayForPoint (Point<int>, bool isPhysical = false) const noexcept;
|
||||
[[deprecated]] const Display& getMainDisplay() const noexcept;
|
||||
#endif
|
||||
|
||||
private:
|
||||
friend class Desktop;
|
||||
|
||||
void init (Desktop&);
|
||||
void findDisplays (float masterScale);
|
||||
|
||||
void updateToLogical();
|
||||
|
||||
Display emptyDisplay;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
#if ! JUCE_NATIVE_ACCESSIBILITY_INCLUDED
|
||||
void AccessibilityHelpers::notifyAccessibilityEvent (const AccessibilityHandler&, Event) {}
|
||||
#endif
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct AccessibilityHelpers
|
||||
{
|
||||
AccessibilityHelpers() = delete;
|
||||
|
||||
enum class Event
|
||||
{
|
||||
elementCreated,
|
||||
elementDestroyed,
|
||||
elementMovedOrResized,
|
||||
focusChanged,
|
||||
windowOpened,
|
||||
windowClosed
|
||||
};
|
||||
|
||||
static void notifyAccessibilityEvent (const AccessibilityHandler&, Event);
|
||||
|
||||
static String getApplicationOrPluginName()
|
||||
{
|
||||
#if defined (JucePlugin_Name)
|
||||
return JucePlugin_Name;
|
||||
#else
|
||||
if (auto* app = JUCEApplicationBase::getInstance())
|
||||
return app->getApplicationName();
|
||||
|
||||
return "JUCE Application";
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename MemberFn>
|
||||
static const AccessibilityHandler* getEnclosingHandlerWithInterface (const AccessibilityHandler* handler, MemberFn fn)
|
||||
{
|
||||
if (handler == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if ((handler->*fn)() != nullptr)
|
||||
return handler;
|
||||
|
||||
return getEnclosingHandlerWithInterface (handler->getParent(), fn);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct AlertWindowHelpers
|
||||
{
|
||||
AlertWindowHelpers() = delete;
|
||||
|
||||
static std::unique_ptr<ScopedMessageBoxInterface> create (const MessageBoxOptions& opts)
|
||||
{
|
||||
class AlertWindowImpl : public detail::ScopedMessageBoxInterface
|
||||
{
|
||||
public:
|
||||
explicit AlertWindowImpl (const MessageBoxOptions& opts) : options (opts) {}
|
||||
|
||||
void runAsync (std::function<void (int)> recipient) override
|
||||
{
|
||||
if (auto* comp = setUpAlert())
|
||||
comp->enterModalState (true, ModalCallbackFunction::create (std::move (recipient)), true);
|
||||
else
|
||||
NullCheckedInvocation::invoke (recipient, 0);
|
||||
}
|
||||
|
||||
int runSync() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (auto comp = rawToUniquePtr (setUpAlert()))
|
||||
return comp->runModalLoop();
|
||||
#endif
|
||||
|
||||
jassertfalse;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (alert != nullptr)
|
||||
if (alert->isCurrentlyModal())
|
||||
alert->exitModalState();
|
||||
|
||||
alert = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Component* setUpAlert()
|
||||
{
|
||||
auto* component = options.getAssociatedComponent();
|
||||
|
||||
auto& lf = component != nullptr ? component->getLookAndFeel()
|
||||
: LookAndFeel::getDefaultLookAndFeel();
|
||||
|
||||
alert = lf.createAlertWindow (options.getTitle(),
|
||||
options.getMessage(),
|
||||
options.getButtonText (0),
|
||||
options.getButtonText (1),
|
||||
options.getButtonText (2),
|
||||
options.getIconType(),
|
||||
options.getNumButtons(),
|
||||
component);
|
||||
|
||||
if (alert == nullptr)
|
||||
{
|
||||
// You have to return an alert box!
|
||||
jassertfalse;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (auto* parent = options.getParentComponent())
|
||||
{
|
||||
parent->addAndMakeVisible (alert);
|
||||
|
||||
if (options.getAssociatedComponent() == nullptr)
|
||||
alert->setCentrePosition (parent->getLocalBounds().getCentre());
|
||||
}
|
||||
|
||||
alert->setAlwaysOnTop (WindowUtils::areThereAnyAlwaysOnTopWindows());
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
const MessageBoxOptions options;
|
||||
Component::SafePointer<AlertWindow> alert;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlertWindowImpl)
|
||||
};
|
||||
|
||||
return std::make_unique<AlertWindowImpl> (opts);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class ButtonAccessibilityHandler : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
ButtonAccessibilityHandler (Button& buttonToWrap, AccessibilityRole roleIn)
|
||||
: AccessibilityHandler (buttonToWrap,
|
||||
isRadioButton (buttonToWrap) ? AccessibilityRole::radioButton : roleIn,
|
||||
getAccessibilityActions (buttonToWrap),
|
||||
getAccessibilityInterfaces (buttonToWrap)),
|
||||
button (buttonToWrap)
|
||||
{}
|
||||
|
||||
|
||||
AccessibleState getCurrentState() const override
|
||||
{
|
||||
auto state = AccessibilityHandler::getCurrentState();
|
||||
|
||||
if (button.isToggleable())
|
||||
{
|
||||
state = state.withCheckable();
|
||||
|
||||
if (button.getToggleState())
|
||||
state = state.withChecked();
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
String getTitle() const override
|
||||
{
|
||||
auto title = AccessibilityHandler::getTitle();
|
||||
|
||||
if (title.isEmpty())
|
||||
return button.getButtonText();
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
String getHelp() const override
|
||||
{
|
||||
return button.getTooltip();
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
class ButtonValueInterface : public AccessibilityTextValueInterface
|
||||
{
|
||||
public:
|
||||
explicit ButtonValueInterface (Button& buttonToWrap)
|
||||
: button (buttonToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
bool isReadOnly() const override { return true; }
|
||||
String getCurrentValueAsString() const override { return button.getToggleState() ? "On" : "Off"; }
|
||||
void setValueAsString (const String&) override {}
|
||||
|
||||
private:
|
||||
Button& button;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonValueInterface)
|
||||
};
|
||||
|
||||
static bool isRadioButton (const Button& button) noexcept
|
||||
{
|
||||
return button.getRadioGroupId() != 0;
|
||||
}
|
||||
|
||||
static AccessibilityActions getAccessibilityActions (Button& button)
|
||||
{
|
||||
auto actions = AccessibilityActions().addAction (AccessibilityActionType::press,
|
||||
[&button] { button.triggerClick(); });
|
||||
|
||||
if (button.isToggleable())
|
||||
actions = actions.addAction (AccessibilityActionType::toggle,
|
||||
[&button] { button.setToggleState (! button.getToggleState(), sendNotification); });
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
static Interfaces getAccessibilityInterfaces (Button& button)
|
||||
{
|
||||
if (button.isToggleable())
|
||||
return { std::make_unique<ButtonValueInterface> (button) };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Button& button;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAccessibilityHandler)
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
constexpr char colourPropertyPrefix[] = "jcclr_";
|
||||
|
||||
//==============================================================================
|
||||
struct ComponentHelpers
|
||||
{
|
||||
using SH = ScalingHelpers;
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
static void* runModalLoopCallback (void* userData)
|
||||
{
|
||||
return (void*) (pointer_sized_int) static_cast<Component*> (userData)->runModalLoop();
|
||||
}
|
||||
#endif
|
||||
|
||||
static Identifier getColourPropertyID (int colourID)
|
||||
{
|
||||
char buffer[32];
|
||||
auto* end = buffer + numElementsInArray (buffer) - 1;
|
||||
auto* t = end;
|
||||
*t = 0;
|
||||
|
||||
for (auto v = (uint32) colourID;;)
|
||||
{
|
||||
*--t = "0123456789abcdef" [v & 15];
|
||||
v >>= 4;
|
||||
|
||||
if (v == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = (int) sizeof (colourPropertyPrefix) - 1; --i >= 0;)
|
||||
*--t = colourPropertyPrefix[i];
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool hitTest (Component& comp, Point<float> localPoint)
|
||||
{
|
||||
const auto intPoint = localPoint.roundToInt();
|
||||
return Rectangle<int> { comp.getWidth(), comp.getHeight() }.contains (intPoint)
|
||||
&& comp.hitTest (intPoint.x, intPoint.y);
|
||||
}
|
||||
|
||||
// converts an unscaled position within a peer to the local position within that peer's component
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect rawPeerPositionToLocal (const Component& comp, PointOrRect pos) noexcept
|
||||
{
|
||||
if (comp.isTransformed())
|
||||
pos = pos.transformedBy (comp.getTransform().inverted());
|
||||
|
||||
return SH::unscaledScreenPosToScaled (comp, pos);
|
||||
}
|
||||
|
||||
// converts a position within a peer's component to the unscaled position within the peer
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect localPositionToRawPeerPos (const Component& comp, PointOrRect pos) noexcept
|
||||
{
|
||||
if (comp.isTransformed())
|
||||
pos = pos.transformedBy (comp.getTransform());
|
||||
|
||||
return SH::scaledScreenPosToUnscaled (comp, pos);
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect convertFromParentSpace (const Component& comp, const PointOrRect pointInParentSpace)
|
||||
{
|
||||
const auto transformed = comp.affineTransform != nullptr ? pointInParentSpace.transformedBy (comp.affineTransform->inverted())
|
||||
: pointInParentSpace;
|
||||
|
||||
if (comp.isOnDesktop())
|
||||
{
|
||||
if (auto* peer = comp.getPeer())
|
||||
return SH::unscaledScreenPosToScaled (comp, peer->globalToLocal (SH::scaledScreenPosToUnscaled (transformed)));
|
||||
|
||||
jassertfalse;
|
||||
return transformed;
|
||||
}
|
||||
|
||||
if (comp.getParentComponent() == nullptr)
|
||||
return SH::subtractPosition (SH::unscaledScreenPosToScaled (comp, SH::scaledScreenPosToUnscaled (transformed)), comp);
|
||||
|
||||
return SH::subtractPosition (transformed, comp);
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect convertToParentSpace (const Component& comp, const PointOrRect pointInLocalSpace)
|
||||
{
|
||||
const auto preTransform = [&]
|
||||
{
|
||||
if (comp.isOnDesktop())
|
||||
{
|
||||
if (auto* peer = comp.getPeer())
|
||||
return SH::unscaledScreenPosToScaled (peer->localToGlobal (SH::scaledScreenPosToUnscaled (comp, pointInLocalSpace)));
|
||||
|
||||
jassertfalse;
|
||||
return pointInLocalSpace;
|
||||
}
|
||||
|
||||
if (comp.getParentComponent() == nullptr)
|
||||
return SH::unscaledScreenPosToScaled (SH::scaledScreenPosToUnscaled (comp, SH::addPosition (pointInLocalSpace, comp)));
|
||||
|
||||
return SH::addPosition (pointInLocalSpace, comp);
|
||||
}();
|
||||
|
||||
return comp.affineTransform != nullptr ? preTransform.transformedBy (*comp.affineTransform)
|
||||
: preTransform;
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect convertFromDistantParentSpace (const Component* parent, const Component& target, PointOrRect coordInParent)
|
||||
{
|
||||
auto* directParent = target.getParentComponent();
|
||||
jassert (directParent != nullptr);
|
||||
|
||||
if (directParent == parent)
|
||||
return convertFromParentSpace (target, coordInParent);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
|
||||
return convertFromParentSpace (target, convertFromDistantParentSpace (parent, *directParent, coordInParent));
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect convertCoordinate (const Component* target, const Component* source, PointOrRect p)
|
||||
{
|
||||
while (source != nullptr)
|
||||
{
|
||||
if (source == target)
|
||||
return p;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
|
||||
|
||||
if (source->isParentOf (target))
|
||||
return convertFromDistantParentSpace (source, *target, p);
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_MSVC
|
||||
|
||||
p = convertToParentSpace (*source, p);
|
||||
source = source->getParentComponent();
|
||||
}
|
||||
|
||||
jassert (source == nullptr);
|
||||
if (target == nullptr)
|
||||
return p;
|
||||
|
||||
auto* topLevelComp = target->getTopLevelComponent();
|
||||
|
||||
p = convertFromParentSpace (*topLevelComp, p);
|
||||
|
||||
if (topLevelComp == target)
|
||||
return p;
|
||||
|
||||
return convertFromDistantParentSpace (topLevelComp, *target, p);
|
||||
}
|
||||
|
||||
static bool clipObscuredRegions (const Component& comp, Graphics& g,
|
||||
const Rectangle<int> clipRect, Point<int> delta)
|
||||
{
|
||||
bool wasClipped = false;
|
||||
|
||||
for (int i = comp.childComponentList.size(); --i >= 0;)
|
||||
{
|
||||
auto& child = *comp.childComponentList.getUnchecked (i);
|
||||
|
||||
if (child.isVisible() && ! child.isTransformed())
|
||||
{
|
||||
auto newClip = clipRect.getIntersection (child.boundsRelativeToParent);
|
||||
|
||||
if (! newClip.isEmpty())
|
||||
{
|
||||
if (child.isOpaque() && child.componentTransparency == 0)
|
||||
{
|
||||
g.excludeClipRegion (newClip + delta);
|
||||
wasClipped = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto childPos = child.getPosition();
|
||||
|
||||
if (clipObscuredRegions (child, g, newClip - childPos, childPos + delta))
|
||||
wasClipped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wasClipped;
|
||||
}
|
||||
|
||||
static Rectangle<int> getParentOrMainMonitorBounds (const Component& comp)
|
||||
{
|
||||
if (auto* p = comp.getParentComponent())
|
||||
return p->getLocalBounds();
|
||||
|
||||
return Desktop::getInstance().getDisplays().getPrimaryDisplay()->userArea;
|
||||
}
|
||||
|
||||
static void releaseAllCachedImageResources (Component& c)
|
||||
{
|
||||
if (auto* cached = c.getCachedComponentImage())
|
||||
cached->releaseResources();
|
||||
|
||||
for (auto* child : c.childComponentList)
|
||||
releaseAllCachedImageResources (*child);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool modalWouldBlockComponent (const Component& maybeBlocked, Component* modal)
|
||||
{
|
||||
return modal != nullptr
|
||||
&& modal != &maybeBlocked
|
||||
&& ! modal->isParentOf (&maybeBlocked)
|
||||
&& ! modal->canModalEventBeSentToComponent (&maybeBlocked);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void sendMouseEventToComponentsThatAreBlockedByModal (Component& modal, Function&& function)
|
||||
{
|
||||
for (auto& ms : Desktop::getInstance().getMouseSources())
|
||||
if (auto* c = ms.getComponentUnderMouse())
|
||||
if (modalWouldBlockComponent (*c, &modal))
|
||||
(c->*function) (ms, SH::screenPosToLocalPos (*c, ms.getScreenPosition()), Time::getCurrentTime());
|
||||
}
|
||||
|
||||
class ModalComponentManagerChangeNotifier
|
||||
{
|
||||
public:
|
||||
static auto& getInstance()
|
||||
{
|
||||
static ModalComponentManagerChangeNotifier instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ErasedScopeGuard addListener (std::function<void()> l)
|
||||
{
|
||||
return listeners.addListener (std::move (l));
|
||||
}
|
||||
|
||||
void modalComponentManagerChanged()
|
||||
{
|
||||
listeners.call();
|
||||
}
|
||||
|
||||
private:
|
||||
ModalComponentManagerChangeNotifier() = default;
|
||||
|
||||
detail::CallbackListenerList<> listeners;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct CustomMouseCursorInfo
|
||||
{
|
||||
ScaledImage image;
|
||||
Point<int> hotspot;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct FocusHelpers
|
||||
{
|
||||
FocusHelpers() = delete;
|
||||
|
||||
static int getOrder (const Component* c)
|
||||
{
|
||||
auto order = c->getExplicitFocusOrder();
|
||||
return order > 0 ? order : std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
template <typename FocusContainerFn>
|
||||
static void findAllComponents (Component* parent,
|
||||
std::vector<Component*>& components,
|
||||
FocusContainerFn isFocusContainer)
|
||||
{
|
||||
if (parent == nullptr || parent->getNumChildComponents() == 0)
|
||||
return;
|
||||
|
||||
std::vector<Component*> localComponents;
|
||||
|
||||
for (auto* c : parent->getChildren())
|
||||
if (c->isVisible() && c->isEnabled())
|
||||
localComponents.push_back (c);
|
||||
|
||||
const auto compareComponents = [&] (const Component* a, const Component* b)
|
||||
{
|
||||
const auto getComponentOrderAttributes = [] (const Component* c)
|
||||
{
|
||||
return std::make_tuple (getOrder (c),
|
||||
c->isAlwaysOnTop() ? 0 : 1,
|
||||
c->getY(),
|
||||
c->getX());
|
||||
};
|
||||
|
||||
return getComponentOrderAttributes (a) < getComponentOrderAttributes (b);
|
||||
};
|
||||
|
||||
// This will sort so that they are ordered in terms of explicit focus,
|
||||
// always on top, left-to-right, and then top-to-bottom.
|
||||
std::stable_sort (localComponents.begin(), localComponents.end(), compareComponents);
|
||||
|
||||
for (auto* c : localComponents)
|
||||
{
|
||||
components.push_back (c);
|
||||
|
||||
if (! (c->*isFocusContainer)())
|
||||
findAllComponents (c, components, isFocusContainer);
|
||||
}
|
||||
}
|
||||
|
||||
enum class NavigationDirection { forwards, backwards };
|
||||
|
||||
template <typename FocusContainerFn>
|
||||
static Component* navigateFocus (Component* current,
|
||||
Component* focusContainer,
|
||||
NavigationDirection direction,
|
||||
FocusContainerFn isFocusContainer)
|
||||
{
|
||||
if (focusContainer != nullptr)
|
||||
{
|
||||
std::vector<Component*> components;
|
||||
findAllComponents (focusContainer, components, isFocusContainer);
|
||||
|
||||
const auto iter = std::find (components.cbegin(), components.cend(), current);
|
||||
|
||||
if (iter == components.cend())
|
||||
return nullptr;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case NavigationDirection::forwards:
|
||||
if (iter != std::prev (components.cend()))
|
||||
return *std::next (iter);
|
||||
|
||||
break;
|
||||
|
||||
case NavigationDirection::backwards:
|
||||
if (iter != components.cbegin())
|
||||
return *std::prev (iter);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct FocusRestorer
|
||||
{
|
||||
FocusRestorer() : lastFocus (Component::getCurrentlyFocusedComponent()) {}
|
||||
|
||||
~FocusRestorer()
|
||||
{
|
||||
if (lastFocus != nullptr
|
||||
&& lastFocus->isShowing()
|
||||
&& ! lastFocus->isCurrentlyBlockedByAnotherModalComponent())
|
||||
lastFocus->grabKeyboardFocus();
|
||||
}
|
||||
|
||||
WeakReference<Component> lastFocus;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (FocusRestorer)
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct LookAndFeelHelpers
|
||||
{
|
||||
LookAndFeelHelpers() = delete;
|
||||
|
||||
static Colour createBaseColour (Colour buttonColour,
|
||||
bool hasKeyboardFocus,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown) noexcept
|
||||
{
|
||||
const float sat = hasKeyboardFocus ? 1.3f : 0.9f;
|
||||
const Colour baseColour (buttonColour.withMultipliedSaturation (sat));
|
||||
|
||||
if (shouldDrawButtonAsDown) return baseColour.contrasting (0.2f);
|
||||
if (shouldDrawButtonAsHighlighted) return baseColour.contrasting (0.1f);
|
||||
|
||||
return baseColour;
|
||||
}
|
||||
|
||||
static TextLayout layoutTooltipText (const String& text, Colour colour) noexcept
|
||||
{
|
||||
const float tooltipFontSize = 13.0f;
|
||||
const int maxToolTipWidth = 400;
|
||||
|
||||
AttributedString s;
|
||||
s.setJustification (Justification::centred);
|
||||
s.append (text, Font (tooltipFontSize, Font::bold), colour);
|
||||
|
||||
TextLayout tl;
|
||||
tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth);
|
||||
return tl;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,588 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
class MouseInputSourceImpl : private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
using SH = ScalingHelpers;
|
||||
|
||||
MouseInputSourceImpl (int i, MouseInputSource::InputSourceType type)
|
||||
: index (i),
|
||||
inputType (type)
|
||||
{}
|
||||
|
||||
//==============================================================================
|
||||
bool isDragging() const noexcept
|
||||
{
|
||||
return buttonState.isAnyMouseButtonDown();
|
||||
}
|
||||
|
||||
Component* getComponentUnderMouse() const noexcept
|
||||
{
|
||||
return componentUnderMouse.get();
|
||||
}
|
||||
|
||||
ModifierKeys getCurrentModifiers() const noexcept
|
||||
{
|
||||
return ModifierKeys::currentModifiers
|
||||
.withoutMouseButtons()
|
||||
.withFlags (buttonState.getRawFlags());
|
||||
}
|
||||
|
||||
ComponentPeer* getPeer() noexcept
|
||||
{
|
||||
if (! ComponentPeer::isValidPeer (lastPeer))
|
||||
lastPeer = nullptr;
|
||||
|
||||
return lastPeer;
|
||||
}
|
||||
|
||||
static Component* findComponentAt (Point<float> screenPos, ComponentPeer* peer)
|
||||
{
|
||||
if (! ComponentPeer::isValidPeer (peer))
|
||||
return nullptr;
|
||||
|
||||
auto relativePos = SH::unscaledScreenPosToScaled (peer->getComponent(),
|
||||
peer->globalToLocal (screenPos));
|
||||
auto& comp = peer->getComponent();
|
||||
|
||||
// (the contains() call is needed to test for overlapping desktop windows)
|
||||
if (comp.contains (relativePos))
|
||||
return comp.getComponentAt (relativePos);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Point<float> getScreenPosition() const noexcept
|
||||
{
|
||||
// This needs to return the live position if possible, but it mustn't update the lastScreenPos
|
||||
// value, because that can cause continuity problems.
|
||||
return SH::unscaledScreenPosToScaled (getRawScreenPosition());
|
||||
}
|
||||
|
||||
Point<float> getRawScreenPosition() const noexcept
|
||||
{
|
||||
return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition()
|
||||
: lastPointerState.position);
|
||||
}
|
||||
|
||||
void setScreenPosition (Point<float> p)
|
||||
{
|
||||
MouseInputSource::setRawMousePosition (SH::scaledScreenPosToUnscaled (p));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_DUMP_MOUSE_EVENTS
|
||||
#define JUCE_MOUSE_EVENT_DBG(desc, screenPos) DBG ("Mouse " << desc << " #" << index \
|
||||
<< ": " << SH::screenPosToLocalPos (comp, screenPos).toString() \
|
||||
<< " - Comp: " << String::toHexString ((pointer_sized_int) &comp));
|
||||
#else
|
||||
#define JUCE_MOUSE_EVENT_DBG(desc, screenPos)
|
||||
#endif
|
||||
|
||||
void sendMouseEnter (Component& comp, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("enter", pointerState.position)
|
||||
comp.internalMouseEnter (MouseInputSource (this),
|
||||
SH::screenPosToLocalPos (comp, pointerState.position),
|
||||
time);
|
||||
}
|
||||
|
||||
void sendMouseExit (Component& comp, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("exit", pointerState.position)
|
||||
comp.internalMouseExit (MouseInputSource (this),
|
||||
SH::screenPosToLocalPos (comp, pointerState.position),
|
||||
time);
|
||||
}
|
||||
|
||||
void sendMouseMove (Component& comp, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("move", pointerState.position)
|
||||
comp.internalMouseMove (MouseInputSource (this),
|
||||
SH::screenPosToLocalPos (comp, pointerState.position),
|
||||
time);
|
||||
}
|
||||
|
||||
void sendMouseDown (Component& comp, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("down", pointerState.position)
|
||||
comp.internalMouseDown (MouseInputSource (this),
|
||||
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
|
||||
time);
|
||||
}
|
||||
|
||||
void sendMouseDrag (Component& comp, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("drag", pointerState.position)
|
||||
comp.internalMouseDrag (MouseInputSource (this),
|
||||
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
|
||||
time);
|
||||
}
|
||||
|
||||
void sendMouseUp (Component& comp, const detail::PointerState& pointerState, Time time, ModifierKeys oldMods)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("up", pointerState.position)
|
||||
comp.internalMouseUp (MouseInputSource (this),
|
||||
pointerState.withPosition (SH::screenPosToLocalPos (comp, pointerState.position)),
|
||||
time,
|
||||
oldMods);
|
||||
}
|
||||
|
||||
void sendMouseWheel (Component& comp, Point<float> screenPos, Time time, const MouseWheelDetails& wheel)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("wheel", screenPos)
|
||||
comp.internalMouseWheel (MouseInputSource (this),
|
||||
SH::screenPosToLocalPos (comp, screenPos),
|
||||
time,
|
||||
wheel);
|
||||
}
|
||||
|
||||
void sendMagnifyGesture (Component& comp, Point<float> screenPos, Time time, float amount)
|
||||
{
|
||||
JUCE_MOUSE_EVENT_DBG ("magnify", screenPos)
|
||||
comp.internalMagnifyGesture (MouseInputSource (this),
|
||||
SH::screenPosToLocalPos (comp, screenPos),
|
||||
time,
|
||||
amount);
|
||||
}
|
||||
|
||||
#undef JUCE_MOUSE_EVENT_DBG
|
||||
|
||||
//==============================================================================
|
||||
// (returns true if the button change caused a modal event loop)
|
||||
bool setButtons (const detail::PointerState& pointerState, Time time, ModifierKeys newButtonState)
|
||||
{
|
||||
if (buttonState == newButtonState)
|
||||
return false;
|
||||
|
||||
// (avoid sending a spurious mouse-drag when we receive a mouse-up)
|
||||
if (! (isDragging() && ! newButtonState.isAnyMouseButtonDown()))
|
||||
setPointerState (pointerState, time, false);
|
||||
|
||||
// (ignore secondary clicks when there's already a button down)
|
||||
if (buttonState.isAnyMouseButtonDown() == newButtonState.isAnyMouseButtonDown())
|
||||
{
|
||||
buttonState = newButtonState;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto lastCounter = mouseEventCounter;
|
||||
|
||||
if (buttonState.isAnyMouseButtonDown())
|
||||
{
|
||||
if (auto* current = getComponentUnderMouse())
|
||||
{
|
||||
auto oldMods = getCurrentModifiers();
|
||||
buttonState = newButtonState; // must change this before calling sendMouseUp, in case it runs a modal loop
|
||||
|
||||
sendMouseUp (*current, pointerState.withPositionOffset (unboundedMouseOffset), time, oldMods);
|
||||
|
||||
if (lastCounter != mouseEventCounter)
|
||||
return true; // if a modal loop happened, then newButtonState is no longer valid.
|
||||
}
|
||||
|
||||
enableUnboundedMouseMovement (false, false);
|
||||
}
|
||||
|
||||
buttonState = newButtonState;
|
||||
|
||||
if (buttonState.isAnyMouseButtonDown())
|
||||
{
|
||||
Desktop::getInstance().incrementMouseClickCounter();
|
||||
|
||||
if (auto* current = getComponentUnderMouse())
|
||||
{
|
||||
registerMouseDown (pointerState.position, time, *current, buttonState,
|
||||
inputType == MouseInputSource::InputSourceType::touch);
|
||||
sendMouseDown (*current, pointerState, time);
|
||||
}
|
||||
}
|
||||
|
||||
return lastCounter != mouseEventCounter;
|
||||
}
|
||||
|
||||
void setComponentUnderMouse (Component* newComponent, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
auto* current = getComponentUnderMouse();
|
||||
|
||||
if (newComponent != current)
|
||||
{
|
||||
WeakReference<Component> safeNewComp (newComponent);
|
||||
auto originalButtonState = buttonState;
|
||||
|
||||
if (current != nullptr)
|
||||
{
|
||||
WeakReference<Component> safeOldComp (current);
|
||||
setButtons (pointerState, time, ModifierKeys());
|
||||
|
||||
if (auto oldComp = safeOldComp.get())
|
||||
{
|
||||
componentUnderMouse = safeNewComp;
|
||||
sendMouseExit (*oldComp, pointerState, time);
|
||||
}
|
||||
|
||||
buttonState = originalButtonState;
|
||||
}
|
||||
|
||||
componentUnderMouse = safeNewComp.get();
|
||||
current = safeNewComp.get();
|
||||
|
||||
if (current != nullptr)
|
||||
sendMouseEnter (*current, pointerState, time);
|
||||
|
||||
revealCursor (false);
|
||||
setButtons (pointerState, time, originalButtonState);
|
||||
}
|
||||
}
|
||||
|
||||
void setPeer (ComponentPeer& newPeer, const detail::PointerState& pointerState, Time time)
|
||||
{
|
||||
if (&newPeer != lastPeer && ( findComponentAt (pointerState.position, &newPeer) != nullptr
|
||||
|| findComponentAt (pointerState.position, lastPeer) == nullptr))
|
||||
{
|
||||
setComponentUnderMouse (nullptr, pointerState, time);
|
||||
lastPeer = &newPeer;
|
||||
setComponentUnderMouse (findComponentAt (pointerState.position, getPeer()), pointerState, time);
|
||||
}
|
||||
}
|
||||
|
||||
void setPointerState (const detail::PointerState& newPointerState, Time time, bool forceUpdate)
|
||||
{
|
||||
const auto& newScreenPos = newPointerState.position;
|
||||
|
||||
if (! isDragging())
|
||||
setComponentUnderMouse (findComponentAt (newScreenPos, getPeer()), newPointerState, time);
|
||||
|
||||
if ((newPointerState != lastPointerState) || forceUpdate)
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
|
||||
lastPointerState = newPointerState;
|
||||
|
||||
if (auto* current = getComponentUnderMouse())
|
||||
{
|
||||
if (isDragging())
|
||||
{
|
||||
registerMouseDrag (newScreenPos);
|
||||
sendMouseDrag (*current, newPointerState.withPositionOffset (unboundedMouseOffset), time);
|
||||
|
||||
if (isUnboundedMouseModeOn)
|
||||
handleUnboundedDrag (*current);
|
||||
}
|
||||
else
|
||||
{
|
||||
sendMouseMove (*current, newPointerState, time);
|
||||
}
|
||||
}
|
||||
|
||||
revealCursor (false);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void handleEvent (ComponentPeer& newPeer, Point<float> positionWithinPeer, Time time,
|
||||
const ModifierKeys newMods, float newPressure, float newOrientation, PenDetails pen)
|
||||
{
|
||||
lastTime = time;
|
||||
++mouseEventCounter;
|
||||
const auto pointerState = detail::PointerState().withPosition (newPeer.localToGlobal (positionWithinPeer))
|
||||
.withPressure (newPressure)
|
||||
.withOrientation (newOrientation)
|
||||
.withRotation (MouseInputSource::defaultRotation)
|
||||
.withTiltX (pen.tiltX)
|
||||
.withTiltY (pen.tiltY);
|
||||
|
||||
if (isDragging() && newMods.isAnyMouseButtonDown())
|
||||
{
|
||||
setPointerState (pointerState, time, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
setPeer (newPeer, pointerState, time);
|
||||
|
||||
if (auto* peer = getPeer())
|
||||
{
|
||||
if (setButtons (pointerState, time, newMods))
|
||||
return; // some modal events have been dispatched, so the current event is now out-of-date
|
||||
|
||||
peer = getPeer();
|
||||
|
||||
if (peer != nullptr)
|
||||
setPointerState (pointerState, time, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component* getTargetForGesture (ComponentPeer& peer, Point<float> positionWithinPeer,
|
||||
Time time, Point<float>& screenPos)
|
||||
{
|
||||
lastTime = time;
|
||||
++mouseEventCounter;
|
||||
|
||||
screenPos = peer.localToGlobal (positionWithinPeer);
|
||||
const auto pointerState = lastPointerState.withPosition (screenPos);
|
||||
setPeer (peer, pointerState, time);
|
||||
setPointerState (pointerState, time, false);
|
||||
triggerFakeMove();
|
||||
|
||||
return getComponentUnderMouse();
|
||||
}
|
||||
|
||||
void handleWheel (ComponentPeer& peer, Point<float> positionWithinPeer,
|
||||
Time time, const MouseWheelDetails& wheel)
|
||||
{
|
||||
Desktop::getInstance().incrementMouseWheelCounter();
|
||||
Point<float> screenPos;
|
||||
|
||||
// This will make sure that when the wheel spins in its inertial phase, any events
|
||||
// continue to be sent to the last component that the mouse was over when it was being
|
||||
// actively controlled by the user. This avoids confusion when scrolling through nested
|
||||
// scrollable components.
|
||||
if (lastNonInertialWheelTarget == nullptr || ! wheel.isInertial)
|
||||
lastNonInertialWheelTarget = getTargetForGesture (peer, positionWithinPeer, time, screenPos);
|
||||
else
|
||||
screenPos = peer.localToGlobal (positionWithinPeer);
|
||||
|
||||
if (auto target = lastNonInertialWheelTarget.get())
|
||||
sendMouseWheel (*target, screenPos, time, wheel);
|
||||
}
|
||||
|
||||
void handleMagnifyGesture (ComponentPeer& peer, Point<float> positionWithinPeer,
|
||||
Time time, const float scaleFactor)
|
||||
{
|
||||
Point<float> screenPos;
|
||||
|
||||
if (auto* current = getTargetForGesture (peer, positionWithinPeer, time, screenPos))
|
||||
sendMagnifyGesture (*current, screenPos, time, scaleFactor);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Time getLastMouseDownTime() const noexcept
|
||||
{
|
||||
return mouseDowns[0].time;
|
||||
}
|
||||
|
||||
Point<float> getLastMouseDownPosition() const noexcept
|
||||
{
|
||||
return SH::unscaledScreenPosToScaled (mouseDowns[0].position);
|
||||
}
|
||||
|
||||
int getNumberOfMultipleClicks() const noexcept
|
||||
{
|
||||
int numClicks = 1;
|
||||
|
||||
if (! isLongPressOrDrag())
|
||||
{
|
||||
for (int i = 1; i < numElementsInArray (mouseDowns); ++i)
|
||||
{
|
||||
if (mouseDowns[0].canBePartOfMultipleClickWith (mouseDowns[i], MouseEvent::getDoubleClickTimeout() * jmin (i, 2)))
|
||||
++numClicks;
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return numClicks;
|
||||
}
|
||||
|
||||
bool isLongPressOrDrag() const noexcept
|
||||
{
|
||||
return movedSignificantly ||
|
||||
lastTime > (mouseDowns[0].time + RelativeTime::milliseconds (300));
|
||||
}
|
||||
|
||||
bool hasMovedSignificantlySincePressed() const noexcept
|
||||
{
|
||||
return movedSignificantly;
|
||||
}
|
||||
|
||||
// Deprecated method
|
||||
bool hasMouseMovedSignificantlySincePressed() const noexcept
|
||||
{
|
||||
return isLongPressOrDrag();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void triggerFakeMove()
|
||||
{
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
setPointerState (lastPointerState,
|
||||
jmax (lastTime, Time::getCurrentTime()), true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void enableUnboundedMouseMovement (bool enable, bool keepCursorVisibleUntilOffscreen)
|
||||
{
|
||||
enable = enable && isDragging();
|
||||
isCursorVisibleUntilOffscreen = keepCursorVisibleUntilOffscreen;
|
||||
|
||||
if (enable != isUnboundedMouseModeOn)
|
||||
{
|
||||
if ((! enable) && ((! isCursorVisibleUntilOffscreen) || ! unboundedMouseOffset.isOrigin()))
|
||||
{
|
||||
// when released, return the mouse to within the component's bounds
|
||||
if (auto* current = getComponentUnderMouse())
|
||||
setScreenPosition (current->getScreenBounds().toFloat()
|
||||
.getConstrainedPoint (SH::unscaledScreenPosToScaled (lastPointerState.position)));
|
||||
}
|
||||
|
||||
isUnboundedMouseModeOn = enable;
|
||||
unboundedMouseOffset = {};
|
||||
|
||||
revealCursor (true);
|
||||
}
|
||||
}
|
||||
|
||||
void handleUnboundedDrag (Component& current)
|
||||
{
|
||||
auto componentScreenBounds = SH::scaledScreenPosToUnscaled (current.getParentMonitorArea()
|
||||
.reduced (2, 2)
|
||||
.toFloat());
|
||||
|
||||
if (! componentScreenBounds.contains (lastPointerState.position))
|
||||
{
|
||||
auto componentCentre = current.getScreenBounds().toFloat().getCentre();
|
||||
unboundedMouseOffset += (lastPointerState.position - SH::scaledScreenPosToUnscaled (componentCentre));
|
||||
setScreenPosition (componentCentre);
|
||||
}
|
||||
else if (isCursorVisibleUntilOffscreen
|
||||
&& (! unboundedMouseOffset.isOrigin())
|
||||
&& componentScreenBounds.contains (lastPointerState.position + unboundedMouseOffset))
|
||||
{
|
||||
MouseInputSource::setRawMousePosition (lastPointerState.position + unboundedMouseOffset);
|
||||
unboundedMouseOffset = {};
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void showMouseCursor (MouseCursor cursor, bool forcedUpdate)
|
||||
{
|
||||
if (isUnboundedMouseModeOn && ((! unboundedMouseOffset.isOrigin()) || ! isCursorVisibleUntilOffscreen))
|
||||
{
|
||||
cursor = MouseCursor::NoCursor;
|
||||
forcedUpdate = true;
|
||||
}
|
||||
|
||||
if (forcedUpdate || cursor.getHandle() != currentCursorHandle)
|
||||
{
|
||||
currentCursorHandle = cursor.getHandle();
|
||||
cursor.showInWindow (getPeer());
|
||||
}
|
||||
}
|
||||
|
||||
void hideCursor()
|
||||
{
|
||||
showMouseCursor (MouseCursor::NoCursor, true);
|
||||
}
|
||||
|
||||
void revealCursor (bool forcedUpdate)
|
||||
{
|
||||
MouseCursor mc (MouseCursor::NormalCursor);
|
||||
|
||||
if (auto* current = getComponentUnderMouse())
|
||||
mc = current->getLookAndFeel().getMouseCursorFor (*current);
|
||||
|
||||
showMouseCursor (mc, forcedUpdate);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const int index;
|
||||
const MouseInputSource::InputSourceType inputType;
|
||||
Point<float> unboundedMouseOffset; // NB: these are unscaled coords
|
||||
detail::PointerState lastPointerState;
|
||||
ModifierKeys buttonState;
|
||||
|
||||
bool isUnboundedMouseModeOn = false, isCursorVisibleUntilOffscreen = false;
|
||||
|
||||
private:
|
||||
WeakReference<Component> componentUnderMouse, lastNonInertialWheelTarget;
|
||||
ComponentPeer* lastPeer = nullptr;
|
||||
|
||||
void* currentCursorHandle = nullptr;
|
||||
int mouseEventCounter = 0;
|
||||
|
||||
struct RecentMouseDown
|
||||
{
|
||||
RecentMouseDown() = default;
|
||||
|
||||
Point<float> position;
|
||||
Time time;
|
||||
ModifierKeys buttons;
|
||||
uint32 peerID = 0;
|
||||
bool isTouch = false;
|
||||
|
||||
bool canBePartOfMultipleClickWith (const RecentMouseDown& other, int maxTimeBetweenMs) const noexcept
|
||||
{
|
||||
return time - other.time < RelativeTime::milliseconds (maxTimeBetweenMs)
|
||||
&& std::abs (position.x - other.position.x) < (float) getPositionToleranceForInputType()
|
||||
&& std::abs (position.y - other.position.y) < (float) getPositionToleranceForInputType()
|
||||
&& buttons == other.buttons
|
||||
&& peerID == other.peerID;
|
||||
}
|
||||
|
||||
int getPositionToleranceForInputType() const noexcept { return isTouch ? 25 : 8; }
|
||||
};
|
||||
|
||||
RecentMouseDown mouseDowns[4];
|
||||
Time lastTime;
|
||||
bool movedSignificantly = false;
|
||||
|
||||
void registerMouseDown (Point<float> screenPos, Time time, Component& component,
|
||||
const ModifierKeys modifiers, bool isTouchSource) noexcept
|
||||
{
|
||||
for (int i = numElementsInArray (mouseDowns); --i > 0;)
|
||||
mouseDowns[i] = mouseDowns[i - 1];
|
||||
|
||||
mouseDowns[0].position = screenPos;
|
||||
mouseDowns[0].time = time;
|
||||
mouseDowns[0].buttons = modifiers.withOnlyMouseButtons();
|
||||
mouseDowns[0].isTouch = isTouchSource;
|
||||
|
||||
if (auto* peer = component.getPeer())
|
||||
mouseDowns[0].peerID = peer->getUniqueID();
|
||||
else
|
||||
mouseDowns[0].peerID = 0;
|
||||
|
||||
movedSignificantly = false;
|
||||
lastNonInertialWheelTarget = nullptr;
|
||||
}
|
||||
|
||||
void registerMouseDrag (Point<float> screenPos) noexcept
|
||||
{
|
||||
movedSignificantly = movedSignificantly || mouseDowns[0].position.getDistanceFrom (screenPos) >= 4;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MouseInputSourceImpl)
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
class MouseInputSourceList : public Timer
|
||||
{
|
||||
public:
|
||||
MouseInputSourceList()
|
||||
{
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
auto mainMouseInputType = MouseInputSource::InputSourceType::touch;
|
||||
#else
|
||||
auto mainMouseInputType = MouseInputSource::InputSourceType::mouse;
|
||||
#endif
|
||||
|
||||
addSource (0, mainMouseInputType);
|
||||
}
|
||||
|
||||
MouseInputSource* addSource (int index, MouseInputSource::InputSourceType type)
|
||||
{
|
||||
auto* s = new MouseInputSourceImpl (index, type);
|
||||
sources.add (s);
|
||||
sourceArray.add (MouseInputSource (s));
|
||||
|
||||
return &sourceArray.getReference (sourceArray.size() - 1);
|
||||
}
|
||||
|
||||
MouseInputSource* getMouseSource (int index) noexcept
|
||||
{
|
||||
return isPositiveAndBelow (index, sourceArray.size()) ? &sourceArray.getReference (index)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
MouseInputSource* getOrCreateMouseInputSource (MouseInputSource::InputSourceType type, int touchIndex = 0)
|
||||
{
|
||||
if (type == MouseInputSource::InputSourceType::mouse
|
||||
|| type == MouseInputSource::InputSourceType::pen)
|
||||
{
|
||||
for (auto& m : sourceArray)
|
||||
if (type == m.getType())
|
||||
return &m;
|
||||
|
||||
addSource (0, type);
|
||||
}
|
||||
else if (type == MouseInputSource::InputSourceType::touch)
|
||||
{
|
||||
jassert (0 <= touchIndex && touchIndex < 100); // sanity-check on number of fingers
|
||||
|
||||
for (auto& m : sourceArray)
|
||||
if (type == m.getType() && touchIndex == m.getIndex())
|
||||
return &m;
|
||||
|
||||
if (canUseTouch())
|
||||
return addSource (touchIndex, type);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int getNumDraggingMouseSources() const noexcept
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
for (auto* s : sources)
|
||||
if (s->isDragging())
|
||||
++num;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
MouseInputSource* getDraggingMouseSource (int index) noexcept
|
||||
{
|
||||
int num = 0;
|
||||
|
||||
for (auto& s : sourceArray)
|
||||
{
|
||||
if (s.isDragging())
|
||||
{
|
||||
if (index == num)
|
||||
return &s;
|
||||
|
||||
++num;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void beginDragAutoRepeat (int interval)
|
||||
{
|
||||
if (interval > 0)
|
||||
{
|
||||
if (getTimerInterval() != interval)
|
||||
startTimer (interval);
|
||||
}
|
||||
else
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
bool anyDragging = false;
|
||||
|
||||
for (auto* s : sources)
|
||||
{
|
||||
// NB: when doing auto-repeat, we need to force an update of the current position and button state,
|
||||
// because on some OSes the queue can get overloaded with messages so that mouse-events don't get through..
|
||||
if (s->isDragging() && ComponentPeer::getCurrentModifiersRealtime().isAnyMouseButtonDown())
|
||||
{
|
||||
s->lastPointerState.position = s->getRawScreenPosition();
|
||||
s->triggerFakeMove();
|
||||
anyDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (! anyDragging)
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
OwnedArray<MouseInputSourceImpl> sources;
|
||||
Array<MouseInputSource> sourceArray;
|
||||
|
||||
private:
|
||||
bool addSource();
|
||||
bool canUseTouch() const;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
class PointerState
|
||||
{
|
||||
auto tie() const noexcept
|
||||
{
|
||||
return std::tie (position, pressure, orientation, rotation, tiltX, tiltY);
|
||||
}
|
||||
|
||||
public:
|
||||
PointerState() = default;
|
||||
|
||||
bool operator== (const PointerState& other) const noexcept { return tie() == other.tie(); }
|
||||
bool operator!= (const PointerState& other) const noexcept { return tie() != other.tie(); }
|
||||
|
||||
[[nodiscard]] PointerState withPositionOffset (Point<float> x) const noexcept { return with (&PointerState::position, position + x); }
|
||||
[[nodiscard]] PointerState withPosition (Point<float> x) const noexcept { return with (&PointerState::position, x); }
|
||||
[[nodiscard]] PointerState withPressure (float x) const noexcept { return with (&PointerState::pressure, x); }
|
||||
[[nodiscard]] PointerState withOrientation (float x) const noexcept { return with (&PointerState::orientation, x); }
|
||||
[[nodiscard]] PointerState withRotation (float x) const noexcept { return with (&PointerState::rotation, x); }
|
||||
[[nodiscard]] PointerState withTiltX (float x) const noexcept { return with (&PointerState::tiltX, x); }
|
||||
[[nodiscard]] PointerState withTiltY (float x) const noexcept { return with (&PointerState::tiltY, x); }
|
||||
|
||||
Point<float> position;
|
||||
float pressure = MouseInputSource::defaultPressure;
|
||||
float orientation = MouseInputSource::defaultOrientation;
|
||||
float rotation = MouseInputSource::defaultRotation;
|
||||
float tiltX = MouseInputSource::defaultTiltX;
|
||||
float tiltY = MouseInputSource::defaultTiltY;
|
||||
|
||||
bool isPressureValid() const noexcept { return 0.0f <= pressure && pressure <= 1.0f; }
|
||||
bool isOrientationValid() const noexcept { return 0.0f <= orientation && orientation <= MathConstants<float>::twoPi; }
|
||||
bool isRotationValid() const noexcept { return 0.0f <= rotation && rotation <= MathConstants<float>::twoPi; }
|
||||
bool isTiltValid (bool isX) const noexcept
|
||||
{
|
||||
return isX ? (-1.0f <= tiltX && tiltX <= 1.0f)
|
||||
: (-1.0f <= tiltY && tiltY <= 1.0f);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Value>
|
||||
PointerState with (Value PointerState::* member, Value item) const
|
||||
{
|
||||
auto copy = *this;
|
||||
copy.*member = std::move (item);
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
inline auto makeMouseEvent (MouseInputSource source,
|
||||
const PointerState& ps,
|
||||
ModifierKeys modifiers,
|
||||
Component* eventComponent,
|
||||
Component* originator,
|
||||
Time eventTime,
|
||||
Point<float> mouseDownPos,
|
||||
Time mouseDownTime,
|
||||
int numberOfClicks,
|
||||
bool mouseWasDragged)
|
||||
{
|
||||
return MouseEvent (source,
|
||||
ps.position,
|
||||
modifiers,
|
||||
ps.pressure,
|
||||
ps.orientation,
|
||||
ps.rotation,
|
||||
ps.tiltX,
|
||||
ps.tiltY,
|
||||
eventComponent,
|
||||
originator,
|
||||
eventTime,
|
||||
mouseDownPos,
|
||||
mouseDownTime,
|
||||
numberOfClicks,
|
||||
mouseWasDragged);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct ScalingHelpers
|
||||
{
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect unscaledScreenPosToScaled (float scale, PointOrRect pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? pos / scale : pos;
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect scaledScreenPosToUnscaled (float scale, PointOrRect pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? pos * scale : pos;
|
||||
}
|
||||
|
||||
// For these, we need to avoid getSmallestIntegerContainer being used, which causes
|
||||
// judder when moving windows
|
||||
static Rectangle<int> unscaledScreenPosToScaled (float scale, Rectangle<int> pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<int> (roundToInt ((float) pos.getX() / scale),
|
||||
roundToInt ((float) pos.getY() / scale),
|
||||
roundToInt ((float) pos.getWidth() / scale),
|
||||
roundToInt ((float) pos.getHeight() / scale)) : pos;
|
||||
}
|
||||
|
||||
static Rectangle<int> scaledScreenPosToUnscaled (float scale, Rectangle<int> pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<int> (roundToInt ((float) pos.getX() * scale),
|
||||
roundToInt ((float) pos.getY() * scale),
|
||||
roundToInt ((float) pos.getWidth() * scale),
|
||||
roundToInt ((float) pos.getHeight() * scale)) : pos;
|
||||
}
|
||||
|
||||
static Rectangle<float> unscaledScreenPosToScaled (float scale, Rectangle<float> pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<float> (pos.getX() / scale,
|
||||
pos.getY() / scale,
|
||||
pos.getWidth() / scale,
|
||||
pos.getHeight() / scale) : pos;
|
||||
}
|
||||
|
||||
static Rectangle<float> scaledScreenPosToUnscaled (float scale, Rectangle<float> pos) noexcept
|
||||
{
|
||||
return ! approximatelyEqual (scale, 1.0f) ? Rectangle<float> (pos.getX() * scale,
|
||||
pos.getY() * scale,
|
||||
pos.getWidth() * scale,
|
||||
pos.getHeight() * scale) : pos;
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept
|
||||
{
|
||||
return unscaledScreenPosToScaled (Desktop::getInstance().getGlobalScaleFactor(), pos);
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect scaledScreenPosToUnscaled (PointOrRect pos) noexcept
|
||||
{
|
||||
return scaledScreenPosToUnscaled (Desktop::getInstance().getGlobalScaleFactor(), pos);
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect unscaledScreenPosToScaled (const Component& comp, PointOrRect pos) noexcept
|
||||
{
|
||||
return unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), pos);
|
||||
}
|
||||
|
||||
template <typename PointOrRect>
|
||||
static PointOrRect scaledScreenPosToUnscaled (const Component& comp, PointOrRect pos) noexcept
|
||||
{
|
||||
return scaledScreenPosToUnscaled (comp.getDesktopScaleFactor(), pos);
|
||||
}
|
||||
|
||||
static Point<int> addPosition (Point<int> p, const Component& c) noexcept { return p + c.getPosition(); }
|
||||
static Rectangle<int> addPosition (Rectangle<int> p, const Component& c) noexcept { return p + c.getPosition(); }
|
||||
static Point<float> addPosition (Point<float> p, const Component& c) noexcept { return p + c.getPosition().toFloat(); }
|
||||
static Rectangle<float> addPosition (Rectangle<float> p, const Component& c) noexcept { return p + c.getPosition().toFloat(); }
|
||||
static Point<int> subtractPosition (Point<int> p, const Component& c) noexcept { return p - c.getPosition(); }
|
||||
static Rectangle<int> subtractPosition (Rectangle<int> p, const Component& c) noexcept { return p - c.getPosition(); }
|
||||
static Point<float> subtractPosition (Point<float> p, const Component& c) noexcept { return p - c.getPosition().toFloat(); }
|
||||
static Rectangle<float> subtractPosition (Rectangle<float> p, const Component& c) noexcept { return p - c.getPosition().toFloat(); }
|
||||
|
||||
static Point<float> screenPosToLocalPos (Component& comp, Point<float> pos)
|
||||
{
|
||||
if (auto* peer = comp.getPeer())
|
||||
{
|
||||
pos = peer->globalToLocal (pos);
|
||||
auto& peerComp = peer->getComponent();
|
||||
return comp.getLocalPoint (&peerComp, unscaledScreenPosToScaled (peerComp, pos));
|
||||
}
|
||||
|
||||
return comp.getLocalPoint (nullptr, unscaledScreenPosToScaled (comp, pos));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
class ConcreteScopedContentSharerImpl : public ScopedMessageBoxImpl,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
static ScopedMessageBox show (std::unique_ptr<ScopedContentSharerInterface>&& native,
|
||||
ContentSharer::Callback callback)
|
||||
{
|
||||
return ScopedMessageBox (runAsync (std::move (native), std::move (callback)));
|
||||
}
|
||||
|
||||
~ConcreteScopedContentSharerImpl() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
nativeImplementation->close();
|
||||
self.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<ConcreteScopedContentSharerImpl> runAsync (std::unique_ptr<ScopedContentSharerInterface>&& p,
|
||||
ContentSharer::Callback&& c)
|
||||
{
|
||||
std::shared_ptr<ConcreteScopedContentSharerImpl> result (new ConcreteScopedContentSharerImpl (std::move (p), std::move (c)));
|
||||
result->self = result;
|
||||
result->triggerAsyncUpdate();
|
||||
return result;
|
||||
}
|
||||
|
||||
ConcreteScopedContentSharerImpl (std::unique_ptr<ScopedContentSharerInterface>&& p,
|
||||
ContentSharer::Callback&& c)
|
||||
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedContentSharerImpl> (self)] (bool result, const String& error)
|
||||
{
|
||||
const auto notifyRecipient = [result, error, weakRecipient]
|
||||
{
|
||||
if (const auto locked = weakRecipient.lock())
|
||||
{
|
||||
NullCheckedInvocation::invoke (locked->callback, result, error);
|
||||
locked->self.reset();
|
||||
}
|
||||
};
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
notifyRecipient();
|
||||
else
|
||||
MessageManager::callAsync (notifyRecipient);
|
||||
});
|
||||
}
|
||||
|
||||
ContentSharer::Callback callback;
|
||||
std::unique_ptr<ScopedContentSharerInterface> nativeImplementation;
|
||||
|
||||
/* The 'old' native message box API doesn't have a concept of content sharer owners.
|
||||
Instead, content sharers have to clean up after themselves, once they're done displaying.
|
||||
To allow this mode of usage, the implementation keeps an owning reference to itself,
|
||||
which is cleared once the content sharer is closed or asked to quit. To display a content
|
||||
sharer box without a scoped lifetime, just create a Pimpl instance without using
|
||||
the ScopedContentSharer wrapper, and the Pimpl will destroy itself after it is dismissed.
|
||||
*/
|
||||
std::shared_ptr<ConcreteScopedContentSharerImpl> self;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
/*
|
||||
Instances of this type can show and dismiss a content sharer.
|
||||
|
||||
This is an interface rather than a concrete type so that platforms can pick an implementation at
|
||||
runtime if necessary.
|
||||
*/
|
||||
struct ScopedContentSharerInterface
|
||||
{
|
||||
virtual ~ScopedContentSharerInterface() = default;
|
||||
|
||||
/* Shows the content sharer.
|
||||
|
||||
When the content sharer exits normally, it should send the result to the passed-in function.
|
||||
The passed-in function is safe to call from any thread at any time.
|
||||
*/
|
||||
virtual void runAsync (ContentSharer::Callback callback)
|
||||
{
|
||||
jassertfalse;
|
||||
NullCheckedInvocation::invoke (callback, false, "Content sharing not available on this platform!");
|
||||
}
|
||||
|
||||
/* Forcefully closes the content sharer.
|
||||
|
||||
This will be called when the content sharer handle has fallen out of scope.
|
||||
If the content sharer has already been closed by the user, this shouldn't do anything.
|
||||
*/
|
||||
virtual void close() {}
|
||||
|
||||
/* Implemented differently for each platform. */
|
||||
static std::unique_ptr<ScopedContentSharerInterface> shareFiles (const Array<URL>&, Component*);
|
||||
static std::unique_ptr<ScopedContentSharerInterface> shareText (const String&, Component*);
|
||||
|
||||
/* Implemented below. */
|
||||
static std::unique_ptr<ScopedContentSharerInterface> shareImages (const Array<Image>&, std::unique_ptr<ImageFileFormat>, Component*);
|
||||
static std::unique_ptr<ScopedContentSharerInterface> shareData (MemoryBlock, Component*);
|
||||
};
|
||||
|
||||
class TemporaryFilesDecorator : public ScopedContentSharerInterface,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
explicit TemporaryFilesDecorator (Component* parentIn)
|
||||
: parent (parentIn) {}
|
||||
|
||||
void runAsync (ContentSharer::Callback cb) override
|
||||
{
|
||||
callback = std::move (cb);
|
||||
|
||||
task = std::async (std::launch::async, [this]
|
||||
{
|
||||
std::tie (temporaryFiles, error) = prepareTemporaryFiles();
|
||||
triggerAsyncUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
if (inner != nullptr)
|
||||
inner->close();
|
||||
}
|
||||
|
||||
private:
|
||||
virtual std::tuple<Array<URL>, String> prepareTemporaryFiles() const = 0;
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
if (error.isNotEmpty())
|
||||
{
|
||||
NullCheckedInvocation::invoke (callback, false, error);
|
||||
return;
|
||||
}
|
||||
|
||||
inner = shareFiles (temporaryFiles, parent);
|
||||
|
||||
if (inner == nullptr)
|
||||
{
|
||||
NullCheckedInvocation::invoke (callback, false, TRANS ("Failed to create file sharer"));
|
||||
return;
|
||||
}
|
||||
|
||||
inner->runAsync (callback);
|
||||
}
|
||||
|
||||
Array<URL> temporaryFiles;
|
||||
String error;
|
||||
std::unique_ptr<ScopedContentSharerInterface> inner;
|
||||
ContentSharer::Callback callback;
|
||||
std::future<void> task;
|
||||
Component* parent = nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareImages (const Array<Image>& images,
|
||||
std::unique_ptr<ImageFileFormat> format,
|
||||
Component* parent)
|
||||
{
|
||||
class Decorator : public TemporaryFilesDecorator
|
||||
{
|
||||
public:
|
||||
Decorator (Array<Image> imagesIn, std::unique_ptr<ImageFileFormat> formatIn, Component* parentIn)
|
||||
: TemporaryFilesDecorator (parentIn), images (std::move (imagesIn)), format (std::move (formatIn)) {}
|
||||
|
||||
private:
|
||||
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
|
||||
{
|
||||
const auto extension = format->getFormatName().toLowerCase();
|
||||
|
||||
Array<URL> result;
|
||||
|
||||
for (const auto& image : images)
|
||||
{
|
||||
File tempFile = File::createTempFile (extension);
|
||||
|
||||
if (! tempFile.create().wasOk())
|
||||
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
|
||||
|
||||
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
|
||||
|
||||
if (outputStream == nullptr)
|
||||
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
|
||||
|
||||
if (format->writeImageToStream (image, *outputStream))
|
||||
result.add (URL (tempFile));
|
||||
}
|
||||
|
||||
jassert (std::all_of (result.begin(),
|
||||
result.end(),
|
||||
[] (const auto& url)
|
||||
{
|
||||
return url.isLocalFile() && url.getLocalFile().existsAsFile();
|
||||
}));
|
||||
|
||||
return { std::move (result), String{} };
|
||||
}
|
||||
|
||||
Array<Image> images;
|
||||
std::unique_ptr<ImageFileFormat> format;
|
||||
};
|
||||
|
||||
return std::make_unique<Decorator> (images,
|
||||
format == nullptr ? std::make_unique<PNGImageFormat>() : std::move (format),
|
||||
parent);
|
||||
}
|
||||
|
||||
std::unique_ptr<ScopedContentSharerInterface> ScopedContentSharerInterface::shareData (MemoryBlock mb, Component* parent)
|
||||
{
|
||||
class Decorator : public TemporaryFilesDecorator
|
||||
{
|
||||
public:
|
||||
Decorator (MemoryBlock mbIn, Component* parentIn)
|
||||
: TemporaryFilesDecorator (parentIn), mb (std::move (mbIn)) {}
|
||||
|
||||
private:
|
||||
std::tuple<Array<URL>, String> prepareTemporaryFiles() const override
|
||||
{
|
||||
File tempFile = File::createTempFile ("data");
|
||||
|
||||
if (! tempFile.create().wasOk())
|
||||
return { Array<URL>{}, TRANS ("Failed to create temporary file") };
|
||||
|
||||
std::unique_ptr<FileOutputStream> outputStream (tempFile.createOutputStream());
|
||||
|
||||
if (outputStream == nullptr)
|
||||
return { Array<URL>{}, TRANS ("Failed to open temporary file for writing") };
|
||||
|
||||
size_t pos = 0;
|
||||
size_t totalSize = mb.getSize();
|
||||
|
||||
while (pos < totalSize)
|
||||
{
|
||||
size_t numToWrite = std::min ((size_t) 8192, totalSize - pos);
|
||||
|
||||
if (! outputStream->write (mb.begin() + pos, numToWrite))
|
||||
return { Array<URL>{}, TRANS ("Failed to write to temporary file") };
|
||||
|
||||
pos += numToWrite;
|
||||
}
|
||||
|
||||
return { Array<URL> { URL (tempFile) }, String{} };
|
||||
}
|
||||
|
||||
MemoryBlock mb;
|
||||
};
|
||||
|
||||
return std::make_unique<Decorator> (std::move (mb), parent);
|
||||
}
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class ScopedMessageBoxImpl
|
||||
{
|
||||
public:
|
||||
virtual ~ScopedMessageBoxImpl() = default;
|
||||
virtual void close() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class ConcreteScopedMessageBoxImpl : public ScopedMessageBoxImpl,
|
||||
private AsyncUpdater
|
||||
{
|
||||
public:
|
||||
static ScopedMessageBox show (std::unique_ptr<ScopedMessageBoxInterface>&& native,
|
||||
std::function<void (int)> callback)
|
||||
{
|
||||
return ScopedMessageBox (runAsync (std::move (native),
|
||||
rawToUniquePtr (ModalCallbackFunction::create (std::move (callback)))));
|
||||
}
|
||||
|
||||
static int showUnmanaged (std::unique_ptr<ScopedMessageBoxInterface>&& native,
|
||||
ModalComponentManager::Callback* cb)
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
if (cb == nullptr)
|
||||
return runSync (std::move (native));
|
||||
#endif
|
||||
|
||||
runAsync (std::move (native), rawToUniquePtr (cb));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
~ConcreteScopedMessageBoxImpl() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
}
|
||||
|
||||
void close() override
|
||||
{
|
||||
cancelPendingUpdate();
|
||||
nativeImplementation->close();
|
||||
self.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
static std::shared_ptr<ConcreteScopedMessageBoxImpl> runAsync (std::unique_ptr<ScopedMessageBoxInterface>&& p,
|
||||
std::unique_ptr<ModalComponentManager::Callback>&& c)
|
||||
{
|
||||
std::shared_ptr<ConcreteScopedMessageBoxImpl> result (new ConcreteScopedMessageBoxImpl (std::move (p), std::move (c)));
|
||||
result->self = result;
|
||||
result->triggerAsyncUpdate();
|
||||
return result;
|
||||
}
|
||||
|
||||
static int runSync (std::unique_ptr<ScopedMessageBoxInterface>&& p)
|
||||
{
|
||||
auto local = std::move (p);
|
||||
return local != nullptr ? local->runSync() : 0;
|
||||
}
|
||||
|
||||
explicit ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p)
|
||||
: ConcreteScopedMessageBoxImpl (std::move (p), nullptr) {}
|
||||
|
||||
ConcreteScopedMessageBoxImpl (std::unique_ptr<ScopedMessageBoxInterface>&& p,
|
||||
std::unique_ptr<ModalComponentManager::Callback>&& c)
|
||||
: callback (std::move (c)), nativeImplementation (std::move (p)) {}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
nativeImplementation->runAsync ([weakRecipient = std::weak_ptr<ConcreteScopedMessageBoxImpl> (self)] (int result)
|
||||
{
|
||||
const auto notifyRecipient = [result, weakRecipient]
|
||||
{
|
||||
if (const auto locked = weakRecipient.lock())
|
||||
{
|
||||
if (auto* cb = locked->callback.get())
|
||||
cb->modalStateFinished (result);
|
||||
|
||||
locked->self.reset();
|
||||
}
|
||||
};
|
||||
|
||||
if (MessageManager::getInstance()->isThisTheMessageThread())
|
||||
notifyRecipient();
|
||||
else
|
||||
MessageManager::callAsync (notifyRecipient);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<ModalComponentManager::Callback> callback;
|
||||
std::unique_ptr<ScopedMessageBoxInterface> nativeImplementation;
|
||||
|
||||
/* The 'old' native message box API doesn't have a concept of message box owners.
|
||||
Instead, message boxes have to clean up after themselves, once they're done displaying.
|
||||
To allow this mode of usage, the implementation keeps an owning reference to itself,
|
||||
which is cleared once the message box is closed or asked to quit. To display a native
|
||||
message box without a scoped lifetime, just create a Pimpl instance without using
|
||||
the ScopedMessageBox wrapper, and the Pimpl will destroy itself after it is dismissed.
|
||||
*/
|
||||
std::shared_ptr<ConcreteScopedMessageBoxImpl> self;
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
/*
|
||||
Instances of this type can show and dismiss a message box.
|
||||
|
||||
This is an interface rather than a concrete type so that platforms can pick an implementation at
|
||||
runtime if necessary.
|
||||
*/
|
||||
struct ScopedMessageBoxInterface
|
||||
{
|
||||
virtual ~ScopedMessageBoxInterface() = default;
|
||||
|
||||
/* Shows the message box.
|
||||
|
||||
When the message box exits normally, it should send the result to the passed-in function.
|
||||
The passed-in function is safe to call from any thread at any time.
|
||||
*/
|
||||
virtual void runAsync (std::function<void (int)>) = 0;
|
||||
|
||||
/* Shows the message box and blocks. */
|
||||
virtual int runSync() = 0;
|
||||
|
||||
/* Forcefully closes the message box.
|
||||
|
||||
This will be called when the message box handle has fallen out of scope.
|
||||
If the message box has already been closed by the user, this shouldn't do anything.
|
||||
*/
|
||||
virtual void close() = 0;
|
||||
|
||||
/* Implemented differently for each platform. */
|
||||
static std::unique_ptr<ScopedMessageBoxInterface> create (const MessageBoxOptions& options);
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
Vendored
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
class ToolbarItemDragAndDropOverlayComponent : public Component
|
||||
{
|
||||
public:
|
||||
ToolbarItemDragAndDropOverlayComponent()
|
||||
: isDragging (false)
|
||||
{
|
||||
setAlwaysOnTop (true);
|
||||
setRepaintsOnMouseActivity (true);
|
||||
setMouseCursor (MouseCursor::DraggingHandCursor);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
if (isMouseOverOrDragging()
|
||||
&& tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
{
|
||||
g.setColour (findColour (Toolbar::editingModeOutlineColourId, true));
|
||||
g.drawRect (getLocalBounds(), jmin (2, (getWidth() - 1) / 2,
|
||||
(getHeight() - 1) / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDown (const MouseEvent& e) override
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->dragOffsetX = e.x;
|
||||
tc->dragOffsetY = e.y;
|
||||
}
|
||||
}
|
||||
|
||||
void mouseDrag (const MouseEvent& e) override
|
||||
{
|
||||
if (e.mouseWasDraggedSinceMouseDown() && ! isDragging)
|
||||
{
|
||||
isDragging = true;
|
||||
|
||||
if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this))
|
||||
{
|
||||
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), ScaledImage(), true, nullptr, &e.source);
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->isBeingDragged = true;
|
||||
|
||||
if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
tc->setVisible (false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mouseUp (const MouseEvent&) override
|
||||
{
|
||||
isDragging = false;
|
||||
|
||||
if (ToolbarItemComponent* const tc = getToolbarItemComponent())
|
||||
{
|
||||
tc->isBeingDragged = false;
|
||||
|
||||
if (Toolbar* const tb = tc->getToolbar())
|
||||
tb->updateAllItemPositions (true);
|
||||
else if (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar)
|
||||
delete tc;
|
||||
}
|
||||
}
|
||||
|
||||
void parentSizeChanged() override
|
||||
{
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool isDragging;
|
||||
|
||||
ToolbarItemComponent* getToolbarItemComponent() const noexcept
|
||||
{
|
||||
return dynamic_cast<ToolbarItemComponent*> (getParentComponent());
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToolbarItemDragAndDropOverlayComponent)
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
/* Keeps track of the active top level window. */
|
||||
class TopLevelWindowManager : private Timer,
|
||||
private DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
TopLevelWindowManager() = default;
|
||||
|
||||
~TopLevelWindowManager() override
|
||||
{
|
||||
clearSingletonInstance();
|
||||
}
|
||||
|
||||
JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (TopLevelWindowManager)
|
||||
|
||||
static void checkCurrentlyFocusedTopLevelWindow()
|
||||
{
|
||||
if (auto* wm = TopLevelWindowManager::getInstanceWithoutCreating())
|
||||
wm->checkFocusAsync();
|
||||
}
|
||||
|
||||
void checkFocusAsync()
|
||||
{
|
||||
startTimer (10);
|
||||
}
|
||||
|
||||
void checkFocus()
|
||||
{
|
||||
startTimer (jmin (1731, getTimerInterval() * 2));
|
||||
|
||||
auto* newActive = findCurrentlyActiveWindow();
|
||||
|
||||
if (newActive != currentActive)
|
||||
{
|
||||
currentActive = newActive;
|
||||
|
||||
for (int i = windows.size(); --i >= 0;)
|
||||
if (auto* tlw = windows[i])
|
||||
tlw->setWindowActive (isWindowActive (tlw));
|
||||
|
||||
Desktop::getInstance().triggerFocusCallback();
|
||||
}
|
||||
}
|
||||
|
||||
bool addWindow (TopLevelWindow* const w)
|
||||
{
|
||||
windows.add (w);
|
||||
checkFocusAsync();
|
||||
|
||||
return isWindowActive (w);
|
||||
}
|
||||
|
||||
void removeWindow (TopLevelWindow* const w)
|
||||
{
|
||||
checkFocusAsync();
|
||||
|
||||
if (currentActive == w)
|
||||
currentActive = nullptr;
|
||||
|
||||
windows.removeFirstMatchingValue (w);
|
||||
|
||||
if (windows.isEmpty())
|
||||
deleteInstance();
|
||||
}
|
||||
|
||||
Array<TopLevelWindow*> windows;
|
||||
|
||||
private:
|
||||
TopLevelWindow* currentActive = nullptr;
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
checkFocus();
|
||||
}
|
||||
|
||||
bool isWindowActive (TopLevelWindow* const tlw) const
|
||||
{
|
||||
return (tlw == currentActive
|
||||
|| tlw->isParentOf (currentActive)
|
||||
|| tlw->hasKeyboardFocus (true))
|
||||
&& tlw->isShowing();
|
||||
}
|
||||
|
||||
TopLevelWindow* findCurrentlyActiveWindow() const
|
||||
{
|
||||
if (Process::isForegroundProcess())
|
||||
{
|
||||
auto* focusedComp = Component::getCurrentlyFocusedComponent();
|
||||
auto* w = dynamic_cast<TopLevelWindow*> (focusedComp);
|
||||
|
||||
if (w == nullptr && focusedComp != nullptr)
|
||||
w = focusedComp->findParentComponentOfClass<TopLevelWindow>();
|
||||
|
||||
if (w == nullptr)
|
||||
w = currentActive;
|
||||
|
||||
if (w != nullptr && w->isShowing())
|
||||
return w;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager)
|
||||
};
|
||||
|
||||
JUCE_IMPLEMENT_SINGLETON (TopLevelWindowManager)
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct ViewportHelpers
|
||||
{
|
||||
ViewportHelpers() = delete;
|
||||
|
||||
static bool wouldScrollOnEvent (const Viewport* vp, const MouseInputSource& src)
|
||||
{
|
||||
if (vp != nullptr)
|
||||
{
|
||||
switch (vp->getScrollOnDragMode())
|
||||
{
|
||||
case Viewport::ScrollOnDragMode::all: return true;
|
||||
case Viewport::ScrollOnDragMode::nonHover: return ! src.canHover();
|
||||
case Viewport::ScrollOnDragMode::never: return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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::detail
|
||||
{
|
||||
|
||||
struct WindowingHelpers
|
||||
{
|
||||
WindowingHelpers() = delete;
|
||||
|
||||
static Image createIconForFile (const File& file);
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
static bool isEmbeddedInForegroundProcess (Component* c);
|
||||
static bool isWindowOnCurrentVirtualDesktop (void*);
|
||||
#else
|
||||
static bool isEmbeddedInForegroundProcess (Component*) { return false; }
|
||||
static bool isWindowOnCurrentVirtualDesktop (void*) { return true; }
|
||||
#endif
|
||||
|
||||
/* Returns true if this process is in the foreground, or if the viewComponent
|
||||
is embedded into a window owned by the foreground process.
|
||||
*/
|
||||
static bool isForegroundOrEmbeddedProcess (Component* viewComponent)
|
||||
{
|
||||
return Process::isForegroundProcess() || isEmbeddedInForegroundProcess (viewComponent);
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
static BorderSize<int> roundToInt (BorderSize<Value> border)
|
||||
{
|
||||
return { ::juce::roundToInt (border.getTop()),
|
||||
::juce::roundToInt (border.getLeft()),
|
||||
::juce::roundToInt (border.getBottom()),
|
||||
::juce::roundToInt (border.getRight()) };
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace juce::detail
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
Drawable::Drawable()
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
setPaintingIsUnclipped (true);
|
||||
setAccessible (false);
|
||||
}
|
||||
|
||||
Drawable::Drawable (const Drawable& other)
|
||||
: Component (other.getName())
|
||||
{
|
||||
setInterceptsMouseClicks (false, false);
|
||||
setPaintingIsUnclipped (true);
|
||||
setAccessible (false);
|
||||
|
||||
setComponentID (other.getComponentID());
|
||||
setTransform (other.getTransform());
|
||||
|
||||
if (auto* clipPath = other.drawableClipPath.get())
|
||||
setClipPath (clipPath->createCopy());
|
||||
}
|
||||
|
||||
Drawable::~Drawable()
|
||||
{
|
||||
}
|
||||
|
||||
void Drawable::applyDrawableClipPath (Graphics& g)
|
||||
{
|
||||
if (drawableClipPath != nullptr)
|
||||
{
|
||||
auto clipPath = drawableClipPath->getOutlineAsPath();
|
||||
|
||||
if (! clipPath.isEmpty())
|
||||
g.getInternalContext().clipToPath (clipPath, {});
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Drawable::draw (Graphics& g, float opacity, const AffineTransform& transform) const
|
||||
{
|
||||
const_cast<Drawable*> (this)->nonConstDraw (g, opacity, transform);
|
||||
}
|
||||
|
||||
void Drawable::nonConstDraw (Graphics& g, float opacity, const AffineTransform& transform)
|
||||
{
|
||||
Graphics::ScopedSaveState ss (g);
|
||||
|
||||
g.addTransform (AffineTransform::translation ((float) -(originRelativeToComponent.x),
|
||||
(float) -(originRelativeToComponent.y))
|
||||
.followedBy (getTransform())
|
||||
.followedBy (transform));
|
||||
|
||||
applyDrawableClipPath (g);
|
||||
|
||||
if (! g.isClipEmpty())
|
||||
{
|
||||
if (opacity < 1.0f)
|
||||
{
|
||||
g.beginTransparencyLayer (opacity);
|
||||
paintEntireComponent (g, true);
|
||||
g.endTransparencyLayer();
|
||||
}
|
||||
else
|
||||
{
|
||||
paintEntireComponent (g, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Drawable::drawAt (Graphics& g, float x, float y, float opacity) const
|
||||
{
|
||||
draw (g, opacity, AffineTransform::translation (x, y));
|
||||
}
|
||||
|
||||
void Drawable::drawWithin (Graphics& g, Rectangle<float> destArea,
|
||||
RectanglePlacement placement, float opacity) const
|
||||
{
|
||||
draw (g, opacity, placement.getTransformToFit (getDrawableBounds(), destArea));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
DrawableComposite* Drawable::getParent() const
|
||||
{
|
||||
return dynamic_cast<DrawableComposite*> (getParentComponent());
|
||||
}
|
||||
|
||||
void Drawable::setClipPath (std::unique_ptr<Drawable> clipPath)
|
||||
{
|
||||
if (drawableClipPath != clipPath)
|
||||
{
|
||||
drawableClipPath = std::move (clipPath);
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void Drawable::transformContextToCorrectOrigin (Graphics& g)
|
||||
{
|
||||
g.setOrigin (originRelativeToComponent);
|
||||
}
|
||||
|
||||
void Drawable::parentHierarchyChanged()
|
||||
{
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
}
|
||||
|
||||
void Drawable::setBoundsToEnclose (Rectangle<float> area)
|
||||
{
|
||||
Point<int> parentOrigin;
|
||||
|
||||
if (auto* parent = getParent())
|
||||
parentOrigin = parent->originRelativeToComponent;
|
||||
|
||||
const auto smallestIntegerContainer = area.getSmallestIntegerContainer();
|
||||
auto newBounds = smallestIntegerContainer + parentOrigin;
|
||||
originRelativeToComponent = -smallestIntegerContainer.getPosition();
|
||||
setBounds (newBounds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool Drawable::replaceColour (Colour original, Colour replacement)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
if (auto* d = dynamic_cast<Drawable*> (c))
|
||||
changed = d->replaceColour (original, replacement) || changed;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void Drawable::setDrawableTransform (const AffineTransform& transform)
|
||||
{
|
||||
drawableTransform = transform;
|
||||
updateTransform();
|
||||
}
|
||||
|
||||
void Drawable::updateTransform()
|
||||
{
|
||||
if (drawableTransform.isIdentity())
|
||||
return;
|
||||
|
||||
const auto transformationOrigin = originRelativeToComponent + getPosition();
|
||||
setTransform (AffineTransform::translation (transformationOrigin * (-1))
|
||||
.followedBy (drawableTransform)
|
||||
.followedBy (AffineTransform::translation (transformationOrigin)));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void Drawable::setOriginWithOriginalSize (Point<float> originWithinParent)
|
||||
{
|
||||
setTransform (AffineTransform::translation (originWithinParent.x, originWithinParent.y));
|
||||
}
|
||||
|
||||
void Drawable::setTransformToFit (const Rectangle<float>& area, RectanglePlacement placement)
|
||||
{
|
||||
if (! area.isEmpty())
|
||||
setTransform (placement.getTransformToFit (getDrawableBounds(), area));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<Drawable> Drawable::createFromImageData (const void* data, const size_t numBytes)
|
||||
{
|
||||
auto image = ImageFileFormat::loadFrom (data, numBytes);
|
||||
|
||||
if (image.isValid())
|
||||
return std::make_unique<DrawableImage> (image);
|
||||
|
||||
if (auto svg = parseXMLIfTagMatches (String::createStringFromData (data, (int) numBytes), "svg"))
|
||||
return Drawable::createFromSVG (*svg);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> Drawable::createFromImageDataStream (InputStream& dataSource)
|
||||
{
|
||||
MemoryOutputStream mo;
|
||||
mo << dataSource;
|
||||
|
||||
return createFromImageData (mo.getData(), mo.getDataSize());
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> Drawable::createFromImageFile (const File& file)
|
||||
{
|
||||
FileInputStream fin (file);
|
||||
|
||||
if (fin.openedOk())
|
||||
return createFromImageDataStream (fin);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
namespace detail
|
||||
{
|
||||
class BoundsChangeListener final : private ComponentListener
|
||||
{
|
||||
public:
|
||||
BoundsChangeListener (Component& c, std::function<void()> cb)
|
||||
: callback (std::move (cb)),
|
||||
componentListenerGuard { [comp = &c, this] { comp->removeComponentListener (this); } }
|
||||
{
|
||||
jassert (callback != nullptr);
|
||||
|
||||
c.addComponentListener (this);
|
||||
}
|
||||
|
||||
private:
|
||||
void componentMovedOrResized (Component&, bool, bool) override
|
||||
{
|
||||
callback();
|
||||
}
|
||||
|
||||
std::function<void()> callback;
|
||||
ErasedScopeGuard componentListenerGuard;
|
||||
};
|
||||
} // namespace detail
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
The base class for objects which can draw themselves, e.g. polygons, images, etc.
|
||||
|
||||
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API Drawable : public Component
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** The base class can't be instantiated directly.
|
||||
|
||||
@see DrawableComposite, DrawableImage, DrawablePath, DrawableText
|
||||
*/
|
||||
Drawable();
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
~Drawable() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a deep copy of this Drawable object.
|
||||
|
||||
Use this to create a new copy of this and any sub-objects in the tree.
|
||||
*/
|
||||
virtual std::unique_ptr<Drawable> createCopy() const = 0;
|
||||
|
||||
/** Creates a path that describes the outline of this drawable. */
|
||||
virtual Path getOutlineAsPath() const = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Renders this Drawable object.
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
|
||||
@see drawWithin
|
||||
*/
|
||||
void draw (Graphics& g, float opacity,
|
||||
const AffineTransform& transform = AffineTransform()) const;
|
||||
|
||||
/** Renders the Drawable at a given offset within the Graphics context.
|
||||
|
||||
The coordinates passed-in are used to translate the object relative to its own
|
||||
origin before drawing it - this is basically a quick way of saying:
|
||||
|
||||
@code
|
||||
draw (g, AffineTransform::translation (x, y)).
|
||||
@endcode
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
*/
|
||||
void drawAt (Graphics& g, float x, float y, float opacity) const;
|
||||
|
||||
/** Renders the Drawable within a rectangle, scaling it to fit neatly inside without
|
||||
changing its aspect-ratio.
|
||||
|
||||
The object can placed arbitrarily within the rectangle based on a Justification type,
|
||||
and can either be made as big as possible, or just reduced to fit.
|
||||
|
||||
Note that the preferred way to render a drawable in future is by using it
|
||||
as a component and adding it to a parent, so you might want to consider that
|
||||
before using this method.
|
||||
|
||||
@param g the graphics context to render onto
|
||||
@param destArea the target rectangle to fit the drawable into
|
||||
@param placement defines the alignment and rescaling to use to fit
|
||||
this object within the target rectangle.
|
||||
@param opacity the opacity to use, in the range 0 to 1.0
|
||||
*/
|
||||
void drawWithin (Graphics& g,
|
||||
Rectangle<float> destArea,
|
||||
RectanglePlacement placement,
|
||||
float opacity) const;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Resets any transformations on this drawable, and positions its origin within
|
||||
its parent component.
|
||||
*/
|
||||
void setOriginWithOriginalSize (Point<float> originWithinParent);
|
||||
|
||||
/** Sets a transform for this drawable that will position it within the specified
|
||||
area of its parent component.
|
||||
*/
|
||||
void setTransformToFit (const Rectangle<float>& areaInParent, RectanglePlacement placement);
|
||||
|
||||
/** Returns the DrawableComposite that contains this object, if there is one. */
|
||||
DrawableComposite* getParent() const;
|
||||
|
||||
/** Sets a the clipping region of this drawable using another drawable.
|
||||
The drawable passed in will be deleted when no longer needed.
|
||||
*/
|
||||
void setClipPath (std::unique_ptr<Drawable> drawableClipPath);
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to turn some kind of image file into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static std::unique_ptr<Drawable> createFromImageData (const void* data, size_t numBytes);
|
||||
|
||||
/** Tries to turn a stream containing some kind of image data into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static std::unique_ptr<Drawable> createFromImageDataStream (InputStream& dataSource);
|
||||
|
||||
/** Tries to turn a file containing some kind of image data into a drawable.
|
||||
|
||||
The data could be an image that the ImageFileFormat class understands, or it
|
||||
could be SVG.
|
||||
*/
|
||||
static std::unique_ptr<Drawable> createFromImageFile (const File& file);
|
||||
|
||||
/** Attempts to parse an SVG (Scalable Vector Graphics) document, and to turn this
|
||||
into a Drawable tree.
|
||||
|
||||
If something goes wrong while parsing, it may return nullptr.
|
||||
|
||||
SVG is a pretty large and complex spec, and this doesn't aim to be a full
|
||||
implementation, but it can return the basic vector objects.
|
||||
*/
|
||||
static std::unique_ptr<Drawable> createFromSVG (const XmlElement& svgDocument);
|
||||
|
||||
/** Attempts to parse an SVG (Scalable Vector Graphics) document from a file,
|
||||
and to turn this into a Drawable tree.
|
||||
|
||||
If something goes wrong while parsing, it may return nullptr.
|
||||
|
||||
SVG is a pretty large and complex spec, and this doesn't aim to be a full
|
||||
implementation, but it can return the basic vector objects.
|
||||
|
||||
Any references to references to external image files will be relative to
|
||||
the parent directory of the file passed.
|
||||
*/
|
||||
static std::unique_ptr<Drawable> createFromSVGFile (const File& svgFile);
|
||||
|
||||
/** Parses an SVG path string and returns it. */
|
||||
static Path parseSVGPath (const String& svgPath);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the area that this drawable covers.
|
||||
The result is expressed in this drawable's own coordinate space, and does not take
|
||||
into account any transforms that may be applied to the component.
|
||||
*/
|
||||
virtual Rectangle<float> getDrawableBounds() const = 0;
|
||||
|
||||
/** Recursively replaces a colour that might be used for filling or stroking.
|
||||
return true if any instances of this colour were found.
|
||||
*/
|
||||
virtual bool replaceColour (Colour originalColour, Colour replacementColour);
|
||||
|
||||
/** Sets a transformation that applies to the same coordinate system in which the rest of the
|
||||
draw calls are made. You almost certainly want to call this function when working with
|
||||
Drawables as opposed to Component::setTransform().
|
||||
|
||||
The reason for this is that the origin of a Drawable is not the same as the point returned
|
||||
by Component::getPosition() but has an additional offset internal to the Drawable class.
|
||||
|
||||
Using setDrawableTransform() will take this internal offset into account when applying the
|
||||
transform to the Component base.
|
||||
|
||||
You can only use Drawable::setDrawableTransform() or Component::setTransform() for a given
|
||||
object. Using both will lead to unpredictable behaviour.
|
||||
*/
|
||||
void setDrawableTransform (const AffineTransform& transform);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
friend class DrawableComposite;
|
||||
friend class DrawableShape;
|
||||
|
||||
/** @internal */
|
||||
void transformContextToCorrectOrigin (Graphics&);
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
void setBoundsToEnclose (Rectangle<float>);
|
||||
/** @internal */
|
||||
void applyDrawableClipPath (Graphics&);
|
||||
|
||||
Point<int> originRelativeToComponent;
|
||||
std::unique_ptr<Drawable> drawableClipPath;
|
||||
AffineTransform drawableTransform;
|
||||
|
||||
void nonConstDraw (Graphics&, float opacity, const AffineTransform&);
|
||||
|
||||
Drawable (const Drawable&);
|
||||
Drawable& operator= (const Drawable&);
|
||||
JUCE_LEAK_DETECTOR (Drawable)
|
||||
|
||||
|
||||
private:
|
||||
void updateTransform();
|
||||
|
||||
detail::BoundsChangeListener boundsChangeListener { *this, [this] { updateTransform(); } };
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableComposite::DrawableComposite()
|
||||
: bounds ({ 0.0f, 0.0f, 100.0f, 100.0f })
|
||||
{
|
||||
setContentArea ({ 0.0f, 0.0f, 100.0f, 100.0f });
|
||||
}
|
||||
|
||||
DrawableComposite::DrawableComposite (const DrawableComposite& other)
|
||||
: Drawable (other),
|
||||
bounds (other.bounds),
|
||||
contentArea (other.contentArea)
|
||||
{
|
||||
for (auto* c : other.getChildren())
|
||||
if (auto* d = dynamic_cast<const Drawable*> (c))
|
||||
addAndMakeVisible (d->createCopy().release());
|
||||
}
|
||||
|
||||
DrawableComposite::~DrawableComposite()
|
||||
{
|
||||
deleteAllChildren();
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> DrawableComposite::createCopy() const
|
||||
{
|
||||
return std::make_unique<DrawableComposite> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<float> DrawableComposite::getDrawableBounds() const
|
||||
{
|
||||
Rectangle<float> r;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
if (auto* d = dynamic_cast<const Drawable*> (c))
|
||||
r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform())
|
||||
: d->getDrawableBounds());
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void DrawableComposite::setContentArea (Rectangle<float> newArea)
|
||||
{
|
||||
contentArea = newArea;
|
||||
}
|
||||
|
||||
void DrawableComposite::setBoundingBox (Rectangle<float> newBounds)
|
||||
{
|
||||
setBoundingBox (Parallelogram<float> (newBounds));
|
||||
}
|
||||
|
||||
void DrawableComposite::setBoundingBox (Parallelogram<float> newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
|
||||
auto t = AffineTransform::fromTargetPoints (contentArea.getTopLeft(), bounds.topLeft,
|
||||
contentArea.getTopRight(), bounds.topRight,
|
||||
contentArea.getBottomLeft(), bounds.bottomLeft);
|
||||
|
||||
if (t.isSingularity())
|
||||
t = {};
|
||||
|
||||
setTransform (t);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableComposite::resetBoundingBoxToContentArea()
|
||||
{
|
||||
setBoundingBox (contentArea);
|
||||
}
|
||||
|
||||
void DrawableComposite::resetContentAreaAndBoundingBoxToFitChildren()
|
||||
{
|
||||
setContentArea (getDrawableBounds());
|
||||
resetBoundingBoxToContentArea();
|
||||
}
|
||||
|
||||
void DrawableComposite::parentHierarchyChanged()
|
||||
{
|
||||
if (auto* parent = getParent())
|
||||
originRelativeToComponent = parent->originRelativeToComponent - getPosition();
|
||||
}
|
||||
|
||||
void DrawableComposite::childBoundsChanged (Component*)
|
||||
{
|
||||
updateBoundsToFitChildren();
|
||||
}
|
||||
|
||||
void DrawableComposite::childrenChanged()
|
||||
{
|
||||
updateBoundsToFitChildren();
|
||||
}
|
||||
|
||||
void DrawableComposite::updateBoundsToFitChildren()
|
||||
{
|
||||
if (! updateBoundsReentrant)
|
||||
{
|
||||
const ScopedValueSetter<bool> setter (updateBoundsReentrant, true, false);
|
||||
|
||||
Rectangle<int> childArea;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
childArea = childArea.getUnion (c->getBoundsInParent());
|
||||
|
||||
auto delta = childArea.getPosition();
|
||||
childArea += getPosition();
|
||||
|
||||
if (childArea != getBounds())
|
||||
{
|
||||
if (! delta.isOrigin())
|
||||
{
|
||||
originRelativeToComponent -= delta;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
c->setBounds (c->getBounds() - delta);
|
||||
}
|
||||
|
||||
setBounds (childArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Path DrawableComposite::getOutlineAsPath() const
|
||||
{
|
||||
Path p;
|
||||
|
||||
for (auto* c : getChildren())
|
||||
if (auto* d = dynamic_cast<Drawable*> (c))
|
||||
p.addPath (d->getOutlineAsPath());
|
||||
|
||||
p.applyTransform (getTransform());
|
||||
return p;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 drawable object which acts as a container for a set of other Drawables.
|
||||
|
||||
Note that although this is a Component, it takes ownership of its child components
|
||||
and will delete them, so that you can use it as a self-contained graphic object.
|
||||
The intention is that you should not add your own components to it, only add other
|
||||
Drawable objects.
|
||||
|
||||
@see Drawable
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableComposite : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a composite Drawable. */
|
||||
DrawableComposite();
|
||||
|
||||
/** Creates a copy of a DrawableComposite. */
|
||||
DrawableComposite (const DrawableComposite&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableComposite() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
|
||||
@see setContentArea
|
||||
*/
|
||||
void setBoundingBox (Parallelogram<float> newBoundingBox);
|
||||
|
||||
/** Sets the rectangle that defines the target position of the content rectangle when the drawable is rendered.
|
||||
@see setContentArea
|
||||
*/
|
||||
void setBoundingBox (Rectangle<float> newBoundingBox);
|
||||
|
||||
/** Returns the parallelogram that defines the target position of the content rectangle when the drawable is rendered.
|
||||
@see setBoundingBox
|
||||
*/
|
||||
Parallelogram<float> getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
/** Changes the bounding box transform to match the content area, so that any sub-items will
|
||||
be drawn at their untransformed positions.
|
||||
*/
|
||||
void resetBoundingBoxToContentArea();
|
||||
|
||||
/** Returns the main content rectangle.
|
||||
@see contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
|
||||
*/
|
||||
Rectangle<float> getContentArea() const noexcept { return contentArea; }
|
||||
|
||||
/** Changes the main content area.
|
||||
@see setBoundingBox, contentLeftMarkerName, contentRightMarkerName, contentTopMarkerName, contentBottomMarkerName
|
||||
*/
|
||||
void setContentArea (Rectangle<float> newArea);
|
||||
|
||||
/** Resets the content area and the bounding transform to fit around the area occupied
|
||||
by the child components.
|
||||
*/
|
||||
void resetContentAreaAndBoundingBoxToFitChildren();
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<Drawable> createCopy() const override;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
void childBoundsChanged (Component*) override;
|
||||
/** @internal */
|
||||
void childrenChanged() override;
|
||||
/** @internal */
|
||||
void parentHierarchyChanged() override;
|
||||
/** @internal */
|
||||
Path getOutlineAsPath() const override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Parallelogram<float> bounds;
|
||||
Rectangle<float> contentArea;
|
||||
bool updateBoundsReentrant = false;
|
||||
|
||||
void updateBoundsToFitChildren();
|
||||
|
||||
DrawableComposite& operator= (const DrawableComposite&);
|
||||
JUCE_LEAK_DETECTOR (DrawableComposite)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableImage::DrawableImage() : bounds ({ 0.0f, 0.0f, 1.0f, 1.0f })
|
||||
{
|
||||
}
|
||||
|
||||
DrawableImage::DrawableImage (const DrawableImage& other)
|
||||
: Drawable (other),
|
||||
image (other.image),
|
||||
opacity (other.opacity),
|
||||
overlayColour (other.overlayColour),
|
||||
bounds (other.bounds)
|
||||
{
|
||||
setBounds (other.getBounds());
|
||||
}
|
||||
|
||||
DrawableImage::DrawableImage (const Image& imageToUse)
|
||||
{
|
||||
setImageInternal (imageToUse);
|
||||
}
|
||||
|
||||
DrawableImage::~DrawableImage()
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> DrawableImage::createCopy() const
|
||||
{
|
||||
return std::make_unique<DrawableImage> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableImage::setImage (const Image& imageToUse)
|
||||
{
|
||||
if (setImageInternal (imageToUse))
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableImage::setOpacity (const float newOpacity)
|
||||
{
|
||||
opacity = newOpacity;
|
||||
}
|
||||
|
||||
void DrawableImage::setOverlayColour (Colour newOverlayColour)
|
||||
{
|
||||
overlayColour = newOverlayColour;
|
||||
}
|
||||
|
||||
void DrawableImage::setBoundingBox (Rectangle<float> newBounds)
|
||||
{
|
||||
setBoundingBox (Parallelogram<float> (newBounds));
|
||||
}
|
||||
|
||||
void DrawableImage::setBoundingBox (Parallelogram<float> newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
|
||||
if (image.isValid())
|
||||
{
|
||||
auto tr = bounds.topLeft + (bounds.topRight - bounds.topLeft) / (float) image.getWidth();
|
||||
auto bl = bounds.topLeft + (bounds.bottomLeft - bounds.topLeft) / (float) image.getHeight();
|
||||
|
||||
auto t = AffineTransform::fromTargetPoints (bounds.topLeft.x, bounds.topLeft.y,
|
||||
tr.x, tr.y,
|
||||
bl.x, bl.y);
|
||||
|
||||
if (t.isSingularity())
|
||||
t = {};
|
||||
|
||||
setTransform (t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableImage::paint (Graphics& g)
|
||||
{
|
||||
if (image.isValid())
|
||||
{
|
||||
if (opacity > 0.0f && ! overlayColour.isOpaque())
|
||||
{
|
||||
g.setOpacity (opacity);
|
||||
g.drawImageAt (image, 0, 0, false);
|
||||
}
|
||||
|
||||
if (! overlayColour.isTransparent())
|
||||
{
|
||||
g.setColour (overlayColour.withMultipliedAlpha (opacity));
|
||||
g.drawImageAt (image, 0, 0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableImage::getDrawableBounds() const
|
||||
{
|
||||
return image.getBounds().toFloat();
|
||||
}
|
||||
|
||||
bool DrawableImage::hitTest (int x, int y)
|
||||
{
|
||||
return Drawable::hitTest (x, y) && image.isValid() && image.getPixelAt (x, y).getAlpha() >= 127;
|
||||
}
|
||||
|
||||
Path DrawableImage::getOutlineAsPath() const
|
||||
{
|
||||
return {}; // not applicable for images
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool DrawableImage::setImageInternal (const Image& imageToUse)
|
||||
{
|
||||
if (image != imageToUse)
|
||||
{
|
||||
image = imageToUse;
|
||||
setBounds (image.getBounds());
|
||||
setBoundingBox (image.getBounds().toFloat());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> DrawableImage::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::image);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 drawable object which is a bitmap image.
|
||||
|
||||
@see Drawable
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableImage : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
DrawableImage();
|
||||
DrawableImage (const DrawableImage&);
|
||||
|
||||
/** Sets the image that this drawable will render. */
|
||||
explicit DrawableImage (const Image& imageToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableImage() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the image that this drawable will render. */
|
||||
void setImage (const Image& imageToUse);
|
||||
|
||||
/** Returns the current image. */
|
||||
const Image& getImage() const noexcept { return image; }
|
||||
|
||||
/** Sets the opacity to use when drawing the image. */
|
||||
void setOpacity (float newOpacity);
|
||||
|
||||
/** Returns the image's opacity. */
|
||||
float getOpacity() const noexcept { return opacity; }
|
||||
|
||||
/** Sets a colour to draw over the image's alpha channel.
|
||||
|
||||
By default this is transparent so isn't drawn, but if you set a non-transparent
|
||||
colour here, then it will be overlaid on the image, using the image's alpha
|
||||
channel as a mask.
|
||||
|
||||
This is handy for doing things like darkening or lightening an image by overlaying
|
||||
it with semi-transparent black or white.
|
||||
*/
|
||||
void setOverlayColour (Colour newOverlayColour);
|
||||
|
||||
/** Returns the overlay colour. */
|
||||
Colour getOverlayColour() const noexcept { return overlayColour; }
|
||||
|
||||
/** Sets the bounding box within which the image should be displayed. */
|
||||
void setBoundingBox (Parallelogram<float> newBounds);
|
||||
|
||||
/** Sets the bounding box within which the image should be displayed. */
|
||||
void setBoundingBox (Rectangle<float> newBounds);
|
||||
|
||||
/** Returns the position to which the image's top-left corner should be remapped in the target
|
||||
coordinate space when rendering this object.
|
||||
@see setTransform
|
||||
*/
|
||||
Parallelogram<float> getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<Drawable> createCopy() const override;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
Path getOutlineAsPath() const override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
bool setImageInternal (const Image&);
|
||||
|
||||
//==============================================================================
|
||||
Image image;
|
||||
float opacity = 1.0f;
|
||||
Colour overlayColour { 0 };
|
||||
Parallelogram<float> bounds;
|
||||
|
||||
DrawableImage& operator= (const DrawableImage&);
|
||||
JUCE_LEAK_DETECTOR (DrawableImage)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawablePath::DrawablePath() {}
|
||||
DrawablePath::~DrawablePath() {}
|
||||
|
||||
DrawablePath::DrawablePath (const DrawablePath& other) : DrawableShape (other)
|
||||
{
|
||||
setPath (other.path);
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> DrawablePath::createCopy() const
|
||||
{
|
||||
return std::make_unique<DrawablePath> (*this);
|
||||
}
|
||||
|
||||
void DrawablePath::setPath (const Path& newPath)
|
||||
{
|
||||
path = newPath;
|
||||
pathChanged();
|
||||
}
|
||||
|
||||
void DrawablePath::setPath (Path&& newPath)
|
||||
{
|
||||
path = std::move (newPath);
|
||||
pathChanged();
|
||||
}
|
||||
|
||||
const Path& DrawablePath::getPath() const { return path; }
|
||||
const Path& DrawablePath::getStrokePath() const { return strokePath; }
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 drawable object which renders a filled or outlined shape.
|
||||
|
||||
For details on how to change the fill and stroke, see the DrawableShape class.
|
||||
|
||||
@see Drawable, DrawableShape
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawablePath : public DrawableShape
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DrawablePath. */
|
||||
DrawablePath();
|
||||
DrawablePath (const DrawablePath&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawablePath() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the path that will be drawn.
|
||||
@see setFill, setStrokeType
|
||||
*/
|
||||
void setPath (const Path& newPath);
|
||||
|
||||
/** Changes the path that will be drawn.
|
||||
@see setFill, setStrokeType
|
||||
*/
|
||||
void setPath (Path&& newPath);
|
||||
|
||||
/** Returns the current path. */
|
||||
const Path& getPath() const;
|
||||
|
||||
/** Returns the current path for the outline. */
|
||||
const Path& getStrokePath() const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<Drawable> createCopy() const override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
DrawablePath& operator= (const DrawablePath&);
|
||||
JUCE_LEAK_DETECTOR (DrawablePath)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableRectangle::DrawableRectangle() {}
|
||||
DrawableRectangle::~DrawableRectangle() {}
|
||||
|
||||
DrawableRectangle::DrawableRectangle (const DrawableRectangle& other)
|
||||
: DrawableShape (other),
|
||||
bounds (other.bounds),
|
||||
cornerSize (other.cornerSize)
|
||||
{
|
||||
rebuildPath();
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> DrawableRectangle::createCopy() const
|
||||
{
|
||||
return std::make_unique<DrawableRectangle> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableRectangle::setRectangle (Parallelogram<float> newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
rebuildPath();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableRectangle::setCornerSize (Point<float> newSize)
|
||||
{
|
||||
if (cornerSize != newSize)
|
||||
{
|
||||
cornerSize = newSize;
|
||||
rebuildPath();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableRectangle::rebuildPath()
|
||||
{
|
||||
auto w = bounds.getWidth();
|
||||
auto h = bounds.getHeight();
|
||||
|
||||
Path newPath;
|
||||
|
||||
if (cornerSize.x > 0 && cornerSize.y > 0)
|
||||
newPath.addRoundedRectangle (0, 0, w, h, cornerSize.x, cornerSize.y);
|
||||
else
|
||||
newPath.addRectangle (0, 0, w, h);
|
||||
|
||||
newPath.applyTransform (AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft,
|
||||
Point<float> (w, 0), bounds.topRight,
|
||||
Point<float> (0, h), bounds.bottomLeft));
|
||||
|
||||
if (path != newPath)
|
||||
{
|
||||
path.swapWithPath (newPath);
|
||||
pathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 Drawable object which draws a rectangle.
|
||||
|
||||
For details on how to change the fill and stroke, see the DrawableShape class.
|
||||
|
||||
@see Drawable, DrawableShape
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableRectangle : public DrawableShape
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
DrawableRectangle();
|
||||
DrawableRectangle (const DrawableRectangle&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableRectangle() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the rectangle's bounds. */
|
||||
void setRectangle (Parallelogram<float> newBounds);
|
||||
|
||||
/** Returns the rectangle's bounds. */
|
||||
Parallelogram<float> getRectangle() const noexcept { return bounds; }
|
||||
|
||||
/** Returns the corner size to be used. */
|
||||
Point<float> getCornerSize() const noexcept { return cornerSize; }
|
||||
|
||||
/** Sets a new corner size for the rectangle */
|
||||
void setCornerSize (Point<float> newSize);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
std::unique_ptr<Drawable> createCopy() const override;
|
||||
|
||||
private:
|
||||
Parallelogram<float> bounds;
|
||||
Point<float> cornerSize;
|
||||
|
||||
void rebuildPath();
|
||||
|
||||
DrawableRectangle& operator= (const DrawableRectangle&);
|
||||
JUCE_LEAK_DETECTOR (DrawableRectangle)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableShape::DrawableShape()
|
||||
: strokeType (0.0f),
|
||||
mainFill (Colours::black),
|
||||
strokeFill (Colours::black)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::DrawableShape (const DrawableShape& other)
|
||||
: Drawable (other),
|
||||
strokeType (other.strokeType),
|
||||
dashLengths (other.dashLengths),
|
||||
mainFill (other.mainFill),
|
||||
strokeFill (other.strokeFill)
|
||||
{
|
||||
}
|
||||
|
||||
DrawableShape::~DrawableShape()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableShape::setFill (const FillType& newFill)
|
||||
{
|
||||
if (mainFill != newFill)
|
||||
{
|
||||
mainFill = newFill;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeFill (const FillType& newFill)
|
||||
{
|
||||
if (strokeFill != newFill)
|
||||
{
|
||||
strokeFill = newFill;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeType (const PathStrokeType& newStrokeType)
|
||||
{
|
||||
if (strokeType != newStrokeType)
|
||||
{
|
||||
strokeType = newStrokeType;
|
||||
strokeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setDashLengths (const Array<float>& newDashLengths)
|
||||
{
|
||||
if (dashLengths != newDashLengths)
|
||||
{
|
||||
dashLengths = newDashLengths;
|
||||
strokeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::setStrokeThickness (const float newThickness)
|
||||
{
|
||||
setStrokeType (PathStrokeType (newThickness, strokeType.getJointStyle(), strokeType.getEndStyle()));
|
||||
}
|
||||
|
||||
bool DrawableShape::isStrokeVisible() const noexcept
|
||||
{
|
||||
return strokeType.getStrokeThickness() > 0.0f && ! strokeFill.isInvisible();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableShape::paint (Graphics& g)
|
||||
{
|
||||
transformContextToCorrectOrigin (g);
|
||||
applyDrawableClipPath (g);
|
||||
|
||||
g.setFillType (mainFill);
|
||||
g.fillPath (path);
|
||||
|
||||
if (isStrokeVisible())
|
||||
{
|
||||
g.setFillType (strokeFill);
|
||||
g.fillPath (strokePath);
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableShape::pathChanged()
|
||||
{
|
||||
strokeChanged();
|
||||
}
|
||||
|
||||
void DrawableShape::strokeChanged()
|
||||
{
|
||||
strokePath.clear();
|
||||
const float extraAccuracy = 4.0f;
|
||||
|
||||
if (dashLengths.isEmpty())
|
||||
strokeType.createStrokedPath (strokePath, path, AffineTransform(), extraAccuracy);
|
||||
else
|
||||
strokeType.createDashedStroke (strokePath, path, dashLengths.getRawDataPointer(),
|
||||
dashLengths.size(), AffineTransform(), extraAccuracy);
|
||||
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
repaint();
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableShape::getDrawableBounds() const
|
||||
{
|
||||
if (isStrokeVisible())
|
||||
return strokePath.getBounds();
|
||||
|
||||
return path.getBounds();
|
||||
}
|
||||
|
||||
bool DrawableShape::hitTest (int x, int y)
|
||||
{
|
||||
bool allowsClicksOnThisComponent, allowsClicksOnChildComponents;
|
||||
getInterceptsMouseClicks (allowsClicksOnThisComponent, allowsClicksOnChildComponents);
|
||||
|
||||
if (! allowsClicksOnThisComponent)
|
||||
return false;
|
||||
|
||||
auto globalX = (float) (x - originRelativeToComponent.x);
|
||||
auto globalY = (float) (y - originRelativeToComponent.y);
|
||||
|
||||
return path.contains (globalX, globalY)
|
||||
|| (isStrokeVisible() && strokePath.contains (globalX, globalY));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool replaceColourInFill (FillType& fill, Colour original, Colour replacement)
|
||||
{
|
||||
if (fill.colour == original && fill.isColour())
|
||||
{
|
||||
fill = FillType (replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DrawableShape::replaceColour (Colour original, Colour replacement)
|
||||
{
|
||||
bool changed1 = replaceColourInFill (mainFill, original, replacement);
|
||||
bool changed2 = replaceColourInFill (strokeFill, original, replacement);
|
||||
return changed1 || changed2;
|
||||
}
|
||||
|
||||
Path DrawableShape::getOutlineAsPath() const
|
||||
{
|
||||
auto outline = isStrokeVisible() ? strokePath : path;
|
||||
outline.applyTransform (getTransform());
|
||||
return outline;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 base class implementing common functionality for Drawable classes which
|
||||
consist of some kind of filled and stroked outline.
|
||||
|
||||
@see DrawablePath, DrawableRectangle
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableShape : public Drawable
|
||||
{
|
||||
protected:
|
||||
//==============================================================================
|
||||
DrawableShape();
|
||||
DrawableShape (const DrawableShape&);
|
||||
|
||||
public:
|
||||
/** Destructor. */
|
||||
~DrawableShape() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets a fill type for the path.
|
||||
This colour is used to fill the path - if you don't want the path to be
|
||||
filled (e.g. if you're just drawing an outline), set this to a transparent
|
||||
colour.
|
||||
|
||||
@see setPath, setStrokeFill
|
||||
*/
|
||||
void setFill (const FillType& newFill);
|
||||
|
||||
/** Returns the current fill type.
|
||||
@see setFill
|
||||
*/
|
||||
const FillType& getFill() const noexcept { return mainFill; }
|
||||
|
||||
/** Sets the fill type with which the outline will be drawn.
|
||||
@see setFill
|
||||
*/
|
||||
void setStrokeFill (const FillType& newStrokeFill);
|
||||
|
||||
/** Returns the current stroke fill.
|
||||
@see setStrokeFill
|
||||
*/
|
||||
const FillType& getStrokeFill() const noexcept { return strokeFill; }
|
||||
|
||||
/** Changes the properties of the outline that will be drawn around the path.
|
||||
If the stroke has 0 thickness, no stroke will be drawn.
|
||||
@see setStrokeThickness, setStrokeColour
|
||||
*/
|
||||
void setStrokeType (const PathStrokeType& newStrokeType);
|
||||
|
||||
/** Changes the stroke thickness.
|
||||
This is a shortcut for calling setStrokeType.
|
||||
*/
|
||||
void setStrokeThickness (float newThickness);
|
||||
|
||||
/** Returns the current outline style. */
|
||||
const PathStrokeType& getStrokeType() const noexcept { return strokeType; }
|
||||
|
||||
/** Provides a set of dash lengths to use for stroking the path. */
|
||||
void setDashLengths (const Array<float>& newDashLengths);
|
||||
|
||||
/** Returns the set of dash lengths that the path is using. */
|
||||
const Array<float>& getDashLengths() const noexcept { return dashLengths; }
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
bool hitTest (int x, int y) override;
|
||||
/** @internal */
|
||||
bool replaceColour (Colour originalColour, Colour replacementColour) override;
|
||||
/** @internal */
|
||||
Path getOutlineAsPath() const override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
/** Called when the cached path should be updated. */
|
||||
void pathChanged();
|
||||
/** Called when the cached stroke should be updated. */
|
||||
void strokeChanged();
|
||||
/** True if there's a stroke with a non-zero thickness and non-transparent colour. */
|
||||
bool isStrokeVisible() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
PathStrokeType strokeType;
|
||||
Array<float> dashLengths;
|
||||
Path path, strokePath;
|
||||
|
||||
private:
|
||||
FillType mainFill, strokeFill;
|
||||
|
||||
DrawableShape& operator= (const DrawableShape&);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DrawableText::DrawableText()
|
||||
: colour (Colours::black),
|
||||
justification (Justification::centredLeft)
|
||||
{
|
||||
setBoundingBox (Parallelogram<float> ({ 0.0f, 0.0f, 50.0f, 20.0f }));
|
||||
setFont (Font (15.0f), true);
|
||||
}
|
||||
|
||||
DrawableText::DrawableText (const DrawableText& other)
|
||||
: Drawable (other),
|
||||
bounds (other.bounds),
|
||||
fontHeight (other.fontHeight),
|
||||
fontHScale (other.fontHScale),
|
||||
font (other.font),
|
||||
text (other.text),
|
||||
colour (other.colour),
|
||||
justification (other.justification)
|
||||
{
|
||||
refreshBounds();
|
||||
}
|
||||
|
||||
DrawableText::~DrawableText()
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<Drawable> DrawableText::createCopy() const
|
||||
{
|
||||
return std::make_unique<DrawableText> (*this);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DrawableText::setText (const String& newText)
|
||||
{
|
||||
if (text != newText)
|
||||
{
|
||||
text = newText;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setColour (Colour newColour)
|
||||
{
|
||||
if (colour != newColour)
|
||||
{
|
||||
colour = newColour;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFont (const Font& newFont, bool applySizeAndScale)
|
||||
{
|
||||
if (font != newFont)
|
||||
{
|
||||
font = newFont;
|
||||
|
||||
if (applySizeAndScale)
|
||||
{
|
||||
fontHeight = font.getHeight();
|
||||
fontHScale = font.getHorizontalScale();
|
||||
}
|
||||
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setJustification (Justification newJustification)
|
||||
{
|
||||
justification = newJustification;
|
||||
repaint();
|
||||
}
|
||||
|
||||
void DrawableText::setBoundingBox (Parallelogram<float> newBounds)
|
||||
{
|
||||
if (bounds != newBounds)
|
||||
{
|
||||
bounds = newBounds;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFontHeight (float newHeight)
|
||||
{
|
||||
if (! approximatelyEqual (fontHeight, newHeight))
|
||||
{
|
||||
fontHeight = newHeight;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::setFontHorizontalScale (float newScale)
|
||||
{
|
||||
if (! approximatelyEqual (fontHScale, newScale))
|
||||
{
|
||||
fontHScale = newScale;
|
||||
refreshBounds();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawableText::refreshBounds()
|
||||
{
|
||||
auto w = bounds.getWidth();
|
||||
auto h = bounds.getHeight();
|
||||
|
||||
auto height = jlimit (0.01f, jmax (0.01f, h), fontHeight);
|
||||
auto hscale = jlimit (0.01f, jmax (0.01f, w), fontHScale);
|
||||
|
||||
scaledFont = font;
|
||||
scaledFont.setHeight (height);
|
||||
scaledFont.setHorizontalScale (hscale);
|
||||
|
||||
setBoundsToEnclose (getDrawableBounds());
|
||||
repaint();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
Rectangle<int> DrawableText::getTextArea (float w, float h) const
|
||||
{
|
||||
return Rectangle<float> (w, h).getSmallestIntegerContainer();
|
||||
}
|
||||
|
||||
AffineTransform DrawableText::getTextTransform (float w, float h) const
|
||||
{
|
||||
return AffineTransform::fromTargetPoints (Point<float>(), bounds.topLeft,
|
||||
Point<float> (w, 0), bounds.topRight,
|
||||
Point<float> (0, h), bounds.bottomLeft);
|
||||
}
|
||||
|
||||
void DrawableText::paint (Graphics& g)
|
||||
{
|
||||
transformContextToCorrectOrigin (g);
|
||||
|
||||
auto w = bounds.getWidth();
|
||||
auto h = bounds.getHeight();
|
||||
|
||||
g.addTransform (getTextTransform (w, h));
|
||||
g.setFont (scaledFont);
|
||||
g.setColour (colour);
|
||||
|
||||
g.drawFittedText (text, getTextArea (w, h), justification, 0x100000);
|
||||
}
|
||||
|
||||
Rectangle<float> DrawableText::getDrawableBounds() const
|
||||
{
|
||||
return bounds.getBoundingBox();
|
||||
}
|
||||
|
||||
Path DrawableText::getOutlineAsPath() const
|
||||
{
|
||||
auto w = bounds.getWidth();
|
||||
auto h = bounds.getHeight();
|
||||
auto area = getTextArea (w, h).toFloat();
|
||||
|
||||
GlyphArrangement arr;
|
||||
arr.addFittedText (scaledFont, text,
|
||||
area.getX(), area.getY(),
|
||||
area.getWidth(), area.getHeight(),
|
||||
justification,
|
||||
0x100000);
|
||||
|
||||
Path pathOfAllGlyphs;
|
||||
|
||||
for (auto& glyph : arr)
|
||||
{
|
||||
Path gylphPath;
|
||||
glyph.createPath (gylphPath);
|
||||
pathOfAllGlyphs.addPath (gylphPath);
|
||||
}
|
||||
|
||||
pathOfAllGlyphs.applyTransform (getTextTransform (w, h).followedBy (drawableTransform));
|
||||
|
||||
return pathOfAllGlyphs;
|
||||
}
|
||||
|
||||
bool DrawableText::replaceColour (Colour originalColour, Colour replacementColour)
|
||||
{
|
||||
if (colour != originalColour)
|
||||
return false;
|
||||
|
||||
setColour (replacementColour);
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> DrawableText::createAccessibilityHandler()
|
||||
{
|
||||
class DrawableTextAccessibilityHandler final : public AccessibilityHandler
|
||||
{
|
||||
public:
|
||||
DrawableTextAccessibilityHandler (DrawableText& drawableTextToWrap)
|
||||
: AccessibilityHandler (drawableTextToWrap, AccessibilityRole::staticText),
|
||||
drawableText (drawableTextToWrap)
|
||||
{
|
||||
}
|
||||
|
||||
String getTitle() const override { return drawableText.getText(); }
|
||||
|
||||
private:
|
||||
DrawableText& drawableText;
|
||||
};
|
||||
|
||||
return std::make_unique<DrawableTextAccessibilityHandler> (*this);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 drawable object which renders a line of text.
|
||||
|
||||
@see Drawable
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DrawableText : public Drawable
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DrawableText object. */
|
||||
DrawableText();
|
||||
DrawableText (const DrawableText&);
|
||||
|
||||
/** Destructor. */
|
||||
~DrawableText() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the text to display.*/
|
||||
void setText (const String& newText);
|
||||
|
||||
/** Returns the currently displayed text */
|
||||
const String& getText() const noexcept { return text;}
|
||||
|
||||
/** Sets the colour of the text. */
|
||||
void setColour (Colour newColour);
|
||||
|
||||
/** Returns the current text colour. */
|
||||
Colour getColour() const noexcept { return colour; }
|
||||
|
||||
/** Sets the font to use.
|
||||
Note that the font height and horizontal scale are set using setFontHeight() and
|
||||
setFontHorizontalScale(). If applySizeAndScale is true, then these height
|
||||
and scale values will be changed to match the dimensions of the font supplied;
|
||||
if it is false, then the new font object's height and scale are ignored.
|
||||
*/
|
||||
void setFont (const Font& newFont, bool applySizeAndScale);
|
||||
|
||||
/** Returns the current font. */
|
||||
const Font& getFont() const noexcept { return font; }
|
||||
|
||||
/** Changes the justification of the text within the bounding box. */
|
||||
void setJustification (Justification newJustification);
|
||||
|
||||
/** Returns the current justification. */
|
||||
Justification getJustification() const noexcept { return justification; }
|
||||
|
||||
/** Returns the parallelogram that defines the text bounding box. */
|
||||
Parallelogram<float> getBoundingBox() const noexcept { return bounds; }
|
||||
|
||||
/** Sets the bounding box that contains the text. */
|
||||
void setBoundingBox (Parallelogram<float> newBounds);
|
||||
|
||||
float getFontHeight() const noexcept { return fontHeight; }
|
||||
void setFontHeight (float newHeight);
|
||||
|
||||
float getFontHorizontalScale() const noexcept { return fontHScale; }
|
||||
void setFontHorizontalScale (float newScale);
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void paint (Graphics&) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<Drawable> createCopy() const override;
|
||||
/** @internal */
|
||||
Rectangle<float> getDrawableBounds() const override;
|
||||
/** @internal */
|
||||
Path getOutlineAsPath() const override;
|
||||
/** @internal */
|
||||
bool replaceColour (Colour originalColour, Colour replacementColour) override;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Parallelogram<float> bounds;
|
||||
float fontHeight, fontHScale;
|
||||
Font font, scaledFont;
|
||||
String text;
|
||||
Colour colour;
|
||||
Justification justification;
|
||||
|
||||
void refreshBounds();
|
||||
Rectangle<int> getTextArea (float width, float height) const;
|
||||
AffineTransform getTextTransform (float width, float height) const;
|
||||
|
||||
DrawableText& operator= (const DrawableText&);
|
||||
JUCE_LEAK_DETECTOR (DrawableText)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
ScopedMessageBox ContentSharer::shareFilesScoped (const Array<URL>& files,
|
||||
Callback callback,
|
||||
Component* parent)
|
||||
{
|
||||
auto impl = detail::ScopedContentSharerInterface::shareFiles (files, parent);
|
||||
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
|
||||
}
|
||||
|
||||
ScopedMessageBox ContentSharer::shareTextScoped (const String& text,
|
||||
Callback callback,
|
||||
Component* parent)
|
||||
{
|
||||
auto impl = detail::ScopedContentSharerInterface::shareText (text, parent);
|
||||
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
|
||||
}
|
||||
|
||||
ScopedMessageBox ContentSharer::shareImagesScoped (const Array<Image>& images,
|
||||
std::unique_ptr<ImageFileFormat> format,
|
||||
Callback callback,
|
||||
Component* parent)
|
||||
{
|
||||
auto impl = detail::ScopedContentSharerInterface::shareImages (images, std::move (format), parent);
|
||||
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
|
||||
}
|
||||
|
||||
ScopedMessageBox ContentSharer::shareDataScoped (const MemoryBlock& mb,
|
||||
Callback callback,
|
||||
Component* parent)
|
||||
{
|
||||
auto impl = detail::ScopedContentSharerInterface::shareData (mb, parent);
|
||||
return detail::ConcreteScopedContentSharerImpl::show (std::move (impl), std::move (callback));
|
||||
}
|
||||
|
||||
#if ! (JUCE_CONTENT_SHARING && (JUCE_IOS || JUCE_ANDROID))
|
||||
auto detail::ScopedContentSharerInterface::shareFiles (const Array<URL>&, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
return std::make_unique<detail::ScopedContentSharerInterface>();
|
||||
}
|
||||
|
||||
auto detail::ScopedContentSharerInterface::shareText (const String&, Component*) -> std::unique_ptr<ScopedContentSharerInterface>
|
||||
{
|
||||
return std::make_unique<detail::ScopedContentSharerInterface>();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Functions that allow sharing content between apps and devices.
|
||||
|
||||
You can share text, images, files or an arbitrary data block.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API ContentSharer
|
||||
{
|
||||
public:
|
||||
ContentSharer() = delete;
|
||||
|
||||
/** A callback of this type is passed when starting a content sharing
|
||||
session.
|
||||
|
||||
When the session ends, the function will receive a flag indicating
|
||||
whether the session was successful. In the case of failure, the
|
||||
errorText argument may hold a string describing the problem.
|
||||
*/
|
||||
using Callback = std::function<void (bool success, const String& errorText)>;
|
||||
|
||||
/** Shares the given files. Each URL should be either a full file path
|
||||
or it should point to a resource within the application bundle. For
|
||||
resources on iOS it should be something like "content/image.png" if you
|
||||
want to specify a file from application bundle located in "content"
|
||||
directory. On Android you should specify only a filename, without an
|
||||
extension.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
|
||||
@param files the files to share
|
||||
@param callback a callback that will be called on the main thread
|
||||
when the sharing session ends
|
||||
@param parent the component that should be used to host the
|
||||
sharing view
|
||||
*/
|
||||
[[nodiscard]] static ScopedMessageBox shareFilesScoped (const Array<URL>& files,
|
||||
Callback callback,
|
||||
Component* parent = nullptr);
|
||||
|
||||
/** Shares the given text.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
|
||||
@param text the text to share
|
||||
@param callback a callback that will be called on the main thread
|
||||
when the sharing session ends
|
||||
@param parent the component that should be used to host the
|
||||
sharing view
|
||||
*/
|
||||
[[nodiscard]] static ScopedMessageBox shareTextScoped (const String& text,
|
||||
Callback callback,
|
||||
Component* parent = nullptr);
|
||||
|
||||
/** A convenience function to share an image. This is useful when you have images
|
||||
loaded in memory. The images will be written to temporary files first, so if
|
||||
you have the images in question stored on disk already call shareFiles() instead.
|
||||
By default, images will be saved to PNG files, but you can supply a custom
|
||||
ImageFileFormat to override this. The custom file format will be owned and
|
||||
deleted by the sharer. e.g.
|
||||
|
||||
@code
|
||||
Graphics g (myImage);
|
||||
g.setColour (Colours::green);
|
||||
g.fillEllipse (20, 20, 300, 200);
|
||||
Array<Image> images;
|
||||
images.add (myImage);
|
||||
ContentSharer::getInstance()->shareImages (images, myCallback);
|
||||
@endcode
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
|
||||
@param images the images to share
|
||||
@param format the file format to use when saving the images.
|
||||
If no format is provided, a sensible default will
|
||||
be used.
|
||||
@param callback a callback that will be called on the main thread
|
||||
when the sharing session ends
|
||||
@param parent the component that should be used to host the
|
||||
sharing view
|
||||
*/
|
||||
[[nodiscard]] static ScopedMessageBox shareImagesScoped (const Array<Image>& images,
|
||||
std::unique_ptr<ImageFileFormat> format,
|
||||
Callback callback,
|
||||
Component* parent = nullptr);
|
||||
|
||||
/** A convenience function to share arbitrary data. The data will be written
|
||||
to a temporary file and then that file will be shared. If you have
|
||||
your data stored on disk already, call shareFiles() instead.
|
||||
|
||||
Upon completion you will receive a callback with a sharing result. Note:
|
||||
Sadly on Android the returned success flag may be wrong as there is no
|
||||
standard way the sharing targets report if the sharing operation
|
||||
succeeded. Also, the optional error message is always empty on Android.
|
||||
|
||||
@param mb the data to share
|
||||
@param callback a callback that will be called on the main thread
|
||||
when the sharing session ends
|
||||
@param parent the component that should be used to host the
|
||||
sharing view
|
||||
*/
|
||||
[[nodiscard]] static ScopedMessageBox shareDataScoped (const MemoryBlock& mb,
|
||||
Callback callback,
|
||||
Component* parent = nullptr);
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DirectoryContentsDisplayComponent::DirectoryContentsDisplayComponent (DirectoryContentsList& l)
|
||||
: directoryContentsList (l)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent::~DirectoryContentsDisplayComponent()
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileBrowserListener::~FileBrowserListener()
|
||||
{
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::addListener (FileBrowserListener* l) { listeners.add (l); }
|
||||
void DirectoryContentsDisplayComponent::removeListener (FileBrowserListener* l) { listeners.remove (l); }
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendSelectionChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendMouseClickMessage (const File& file, const MouseEvent& e)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (file, e); });
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsDisplayComponent::sendDoubleClickMessage (const File& file)
|
||||
{
|
||||
if (directoryContentsList.getDirectory().exists())
|
||||
{
|
||||
Component::BailOutChecker checker (dynamic_cast<Component*> (this));
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (file); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
Vendored
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 base class for components that display a list of the files in a directory.
|
||||
|
||||
@see DirectoryContentsList
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsDisplayComponent
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a DirectoryContentsDisplayComponent for a given list of files. */
|
||||
DirectoryContentsDisplayComponent (DirectoryContentsList& listToShow);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~DirectoryContentsDisplayComponent();
|
||||
|
||||
//==============================================================================
|
||||
/** The list that this component is displaying */
|
||||
DirectoryContentsList& directoryContentsList;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files the user has got selected.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
virtual int getNumSelectedFiles() const = 0;
|
||||
|
||||
/** Returns one of the files that the user has currently selected.
|
||||
The index should be in the range 0 to (getNumSelectedFiles() - 1).
|
||||
@see getNumSelectedFiles
|
||||
*/
|
||||
virtual File getSelectedFile (int index) const = 0;
|
||||
|
||||
/** Deselects any selected files. */
|
||||
virtual void deselectAllFiles() = 0;
|
||||
|
||||
/** Scrolls this view to the top. */
|
||||
virtual void scrollToTop() = 0;
|
||||
|
||||
/** If the specified file is in the list, it will become the only selected item
|
||||
(and if the file isn't in the list, all other items will be deselected). */
|
||||
virtual void setSelectedFile (const File&) = 0;
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when files are selected or clicked.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the list.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
highlightColourId = 0x1000540, /**< The colour to use to fill a highlighted row of the list. */
|
||||
textColourId = 0x1000541, /**< The colour for the text. */
|
||||
highlightedTextColourId = 0x1000542 /**< The colour with which to draw the text in highlighted sections. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void sendSelectionChangeMessage();
|
||||
/** @internal */
|
||||
void sendDoubleClickMessage (const File&);
|
||||
/** @internal */
|
||||
void sendMouseClickMessage (const File&, const MouseEvent&);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
private:
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsDisplayComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
DirectoryContentsList::DirectoryContentsList (const FileFilter* f, TimeSliceThread& t)
|
||||
: fileFilter (f), thread (t)
|
||||
{
|
||||
}
|
||||
|
||||
DirectoryContentsList::~DirectoryContentsList()
|
||||
{
|
||||
stopSearching();
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setIgnoresHiddenFiles (const bool shouldIgnoreHiddenFiles)
|
||||
{
|
||||
setTypeFlags (shouldIgnoreHiddenFiles ? (fileTypeFlags | File::ignoreHiddenFiles)
|
||||
: (fileTypeFlags & ~File::ignoreHiddenFiles));
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::ignoresHiddenFiles() const
|
||||
{
|
||||
return (fileTypeFlags & File::ignoreHiddenFiles) != 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void DirectoryContentsList::setDirectory (const File& directory,
|
||||
const bool includeDirectories,
|
||||
const bool includeFiles)
|
||||
{
|
||||
jassert (includeDirectories || includeFiles); // you have to specify at least one of these!
|
||||
|
||||
if (directory != root)
|
||||
{
|
||||
clear();
|
||||
root = directory;
|
||||
changed();
|
||||
|
||||
// (this forces a refresh when setTypeFlags() is called, rather than triggering two refreshes)
|
||||
fileTypeFlags &= ~(File::findDirectories | File::findFiles);
|
||||
}
|
||||
|
||||
auto newFlags = fileTypeFlags;
|
||||
|
||||
if (includeDirectories) newFlags |= File::findDirectories;
|
||||
else newFlags &= ~File::findDirectories;
|
||||
|
||||
if (includeFiles) newFlags |= File::findFiles;
|
||||
else newFlags &= ~File::findFiles;
|
||||
|
||||
setTypeFlags (newFlags);
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setTypeFlags (const int newFlags)
|
||||
{
|
||||
if (fileTypeFlags != newFlags)
|
||||
{
|
||||
fileTypeFlags = newFlags;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::stopSearching()
|
||||
{
|
||||
shouldStop = true;
|
||||
thread.removeTimeSliceClient (this);
|
||||
isSearching = false;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::clear()
|
||||
{
|
||||
stopSearching();
|
||||
|
||||
if (! files.isEmpty())
|
||||
{
|
||||
files.clear();
|
||||
changed();
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::refresh()
|
||||
{
|
||||
stopSearching();
|
||||
wasEmpty = files.isEmpty();
|
||||
files.clear();
|
||||
|
||||
if (root.isDirectory())
|
||||
{
|
||||
fileFindHandle = std::make_unique<RangedDirectoryIterator> (root, false, "*", fileTypeFlags);
|
||||
shouldStop = false;
|
||||
isSearching = true;
|
||||
thread.addTimeSliceClient (this);
|
||||
}
|
||||
}
|
||||
|
||||
void DirectoryContentsList::setFileFilter (const FileFilter* newFileFilter)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
fileFilter = newFileFilter;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::getNumFiles() const noexcept
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
return files.size();
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::getFileInfo (const int index, FileInfo& result) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
{
|
||||
result = *info;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
File DirectoryContentsList::getFile (const int index) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (auto* info = files [index])
|
||||
return root.getChildFile (info->filename);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::contains (const File& targetFile) const
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (root.getChildFile (files.getUnchecked (i)->filename) == targetFile)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::isStillLoading() const
|
||||
{
|
||||
return isSearching;
|
||||
}
|
||||
|
||||
void DirectoryContentsList::changed()
|
||||
{
|
||||
sendChangeMessage();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int DirectoryContentsList::useTimeSlice()
|
||||
{
|
||||
auto startTime = Time::getApproximateMillisecondCounter();
|
||||
bool hasChanged = false;
|
||||
|
||||
for (int i = 100; --i >= 0;)
|
||||
{
|
||||
if (! checkNextFile (hasChanged))
|
||||
{
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 500;
|
||||
}
|
||||
|
||||
if (shouldStop || (Time::getApproximateMillisecondCounter() > startTime + 150))
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasChanged)
|
||||
changed();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::checkNextFile (bool& hasChanged)
|
||||
{
|
||||
if (fileFindHandle == nullptr)
|
||||
return false;
|
||||
|
||||
if (*fileFindHandle == RangedDirectoryIterator())
|
||||
{
|
||||
fileFindHandle = nullptr;
|
||||
isSearching = false;
|
||||
hasChanged = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto entry = *(*fileFindHandle)++;
|
||||
|
||||
hasChanged |= addFile (entry.getFile(),
|
||||
entry.isDirectory(),
|
||||
entry.getFileSize(),
|
||||
entry.getModificationTime(),
|
||||
entry.getCreationTime(),
|
||||
entry.isReadOnly());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectoryContentsList::addFile (const File& file, const bool isDir,
|
||||
const int64 fileSize,
|
||||
Time modTime, Time creationTime,
|
||||
const bool isReadOnly)
|
||||
{
|
||||
const ScopedLock sl (fileListLock);
|
||||
|
||||
if (fileFilter == nullptr
|
||||
|| ((! isDir) && fileFilter->isFileSuitable (file))
|
||||
|| (isDir && fileFilter->isDirectorySuitable (file)))
|
||||
{
|
||||
auto info = std::make_unique<FileInfo>();
|
||||
|
||||
info->filename = file.getFileName();
|
||||
info->fileSize = fileSize;
|
||||
info->modificationTime = modTime;
|
||||
info->creationTime = creationTime;
|
||||
info->isDirectory = isDir;
|
||||
info->isReadOnly = isReadOnly;
|
||||
|
||||
for (int i = files.size(); --i >= 0;)
|
||||
if (files.getUnchecked (i)->filename == info->filename)
|
||||
return false;
|
||||
|
||||
files.add (std::move (info));
|
||||
|
||||
std::sort (files.begin(), files.end(), [] (const FileInfo* a, const FileInfo* b)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
if (a->isDirectory != b->isDirectory)
|
||||
return a->isDirectory;
|
||||
#endif
|
||||
|
||||
return a->filename.compareNatural (b->filename) < 0;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+226
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 class to asynchronously scan for details about the files in a directory.
|
||||
|
||||
This keeps a list of files and some information about them, using a background
|
||||
thread to scan for more files. As files are found, it broadcasts change messages
|
||||
to tell any listeners.
|
||||
|
||||
@see FileListComponent, FileBrowserComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API DirectoryContentsList : public ChangeBroadcaster,
|
||||
private TimeSliceClient
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a directory list.
|
||||
|
||||
To set the directory it should point to, use setDirectory(), which will
|
||||
also start it scanning for files on the background thread.
|
||||
|
||||
When the background thread finds and adds new files to this list, the
|
||||
ChangeBroadcaster class will send a change message, so you can register
|
||||
listeners and update them when the list changes.
|
||||
|
||||
@param fileFilter an optional filter to select which files are
|
||||
included in the list. If this is nullptr, then all files
|
||||
and directories are included. Make sure that the filter
|
||||
doesn't get deleted during the lifetime of this object
|
||||
@param threadToUse a thread object that this list can use
|
||||
to scan for files as a background task. Make sure
|
||||
that the thread you give it has been started, or you
|
||||
won't get any files!
|
||||
*/
|
||||
DirectoryContentsList (const FileFilter* fileFilter,
|
||||
TimeSliceThread& threadToUse);
|
||||
|
||||
/** Destructor. */
|
||||
~DirectoryContentsList() override;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory that's currently being used. */
|
||||
const File& getDirectory() const noexcept { return root; }
|
||||
|
||||
/** Sets the directory to look in for files.
|
||||
|
||||
If the directory that's passed in is different to the current one, this will
|
||||
also start the background thread scanning it for files.
|
||||
*/
|
||||
void setDirectory (const File& directory,
|
||||
bool includeDirectories,
|
||||
bool includeFiles);
|
||||
|
||||
/** Returns true if this list contains directories.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingDirectories() const noexcept { return (fileTypeFlags & File::findDirectories) != 0; }
|
||||
|
||||
/** Returns true if this list contains files.
|
||||
@see setDirectory
|
||||
*/
|
||||
bool isFindingFiles() const noexcept { return (fileTypeFlags & File::findFiles) != 0; }
|
||||
|
||||
/** Clears the list, and stops the thread scanning for files. */
|
||||
void clear();
|
||||
|
||||
/** Clears the list and restarts scanning the directory for files. */
|
||||
void refresh();
|
||||
|
||||
/** True if the background thread hasn't yet finished scanning for files. */
|
||||
bool isStillLoading() const;
|
||||
|
||||
/** Tells the list whether or not to ignore hidden files.
|
||||
By default these are ignored.
|
||||
*/
|
||||
void setIgnoresHiddenFiles (bool shouldIgnoreHiddenFiles);
|
||||
|
||||
/** Returns true if hidden files are ignored.
|
||||
@see setIgnoresHiddenFiles
|
||||
*/
|
||||
bool ignoresHiddenFiles() const;
|
||||
|
||||
/** Replaces the current FileFilter.
|
||||
This can be nullptr to have no filter. The DirectoryContentList does not take
|
||||
ownership of this object - it just keeps a pointer to it, so you must manage its
|
||||
lifetime.
|
||||
Note that this only replaces the filter, it doesn't refresh the list - you'll
|
||||
probably want to call refresh() after calling this.
|
||||
*/
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
//==============================================================================
|
||||
/** Contains cached information about one of the files in a DirectoryContentsList.
|
||||
*/
|
||||
struct FileInfo
|
||||
{
|
||||
//==============================================================================
|
||||
/** The filename.
|
||||
|
||||
This isn't a full pathname, it's just the last part of the path, same as you'd
|
||||
get from File::getFileName().
|
||||
|
||||
To get the full pathname, use DirectoryContentsList::getDirectory().getChildFile (filename).
|
||||
*/
|
||||
String filename;
|
||||
|
||||
/** File size in bytes. */
|
||||
int64 fileSize;
|
||||
|
||||
/** File modification time.
|
||||
As supplied by File::getLastModificationTime().
|
||||
*/
|
||||
Time modificationTime;
|
||||
|
||||
/** File creation time.
|
||||
As supplied by File::getCreationTime().
|
||||
*/
|
||||
Time creationTime;
|
||||
|
||||
/** True if the file is a directory. */
|
||||
bool isDirectory;
|
||||
|
||||
/** True if the file is read-only. */
|
||||
bool isReadOnly;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files currently available in the list.
|
||||
|
||||
The info about one of these files can be retrieved with getFileInfo() or getFile().
|
||||
|
||||
Obviously as the background thread runs and scans the directory for files, this
|
||||
number will change.
|
||||
|
||||
@see getFileInfo, getFile
|
||||
*/
|
||||
int getNumFiles() const noexcept;
|
||||
|
||||
/** Returns the cached information about one of the files in the list.
|
||||
|
||||
If the index is in-range, this will return true and will copy the file's details
|
||||
to the structure that is passed-in.
|
||||
|
||||
If it returns false, then the index wasn't in range, and the structure won't
|
||||
be affected.
|
||||
|
||||
@see getNumFiles, getFile
|
||||
*/
|
||||
bool getFileInfo (int index, FileInfo& resultInfo) const;
|
||||
|
||||
/** Returns one of the files in the list.
|
||||
|
||||
@param index should be less than getNumFiles(). If this is out-of-range, the
|
||||
return value will be a default File() object
|
||||
@see getNumFiles, getFileInfo
|
||||
*/
|
||||
File getFile (int index) const;
|
||||
|
||||
/** Returns the file filter being used.
|
||||
The filter is specified in the constructor.
|
||||
*/
|
||||
const FileFilter* getFilter() const noexcept { return fileFilter; }
|
||||
|
||||
/** Returns true if the list contains the specified file. */
|
||||
bool contains (const File&) const;
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
TimeSliceThread& getTimeSliceThread() const noexcept { return thread; }
|
||||
|
||||
private:
|
||||
File root;
|
||||
const FileFilter* fileFilter = nullptr;
|
||||
TimeSliceThread& thread;
|
||||
int fileTypeFlags = File::ignoreHiddenFiles | File::findFiles;
|
||||
|
||||
CriticalSection fileListLock;
|
||||
OwnedArray<FileInfo> files;
|
||||
|
||||
std::unique_ptr<RangedDirectoryIterator> fileFindHandle;
|
||||
std::atomic<bool> shouldStop { true }, isSearching { false };
|
||||
|
||||
bool wasEmpty = true;
|
||||
|
||||
int useTimeSlice() override;
|
||||
void stopSearching();
|
||||
void changed();
|
||||
bool checkNextFile (bool& hasChanged);
|
||||
bool addFile (const File&, bool isDir, int64 fileSize, Time modTime,
|
||||
Time creationTime, bool isReadOnly);
|
||||
void setTypeFlags (int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DirectoryContentsList)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+624
@@ -0,0 +1,624 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
FileBrowserComponent::FileBrowserComponent (int flags_,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter_,
|
||||
FilePreviewComponent* previewComp_)
|
||||
: FileFilter ({}),
|
||||
fileFilter (fileFilter_),
|
||||
flags (flags_),
|
||||
previewComp (previewComp_),
|
||||
currentPathBox ("path"),
|
||||
fileLabel ("f", TRANS ("file:")),
|
||||
thread ("JUCE FileBrowser"),
|
||||
wasProcessActive (true)
|
||||
{
|
||||
// You need to specify one or other of the open/save flags..
|
||||
jassert ((flags & (saveMode | openMode)) != 0);
|
||||
jassert ((flags & (saveMode | openMode)) != (saveMode | openMode));
|
||||
|
||||
// You need to specify at least one of these flags..
|
||||
jassert ((flags & (canSelectFiles | canSelectDirectories)) != 0);
|
||||
|
||||
String filename;
|
||||
|
||||
if (initialFileOrDirectory == File())
|
||||
{
|
||||
currentRoot = File::getCurrentWorkingDirectory();
|
||||
}
|
||||
else if (initialFileOrDirectory.isDirectory())
|
||||
{
|
||||
currentRoot = initialFileOrDirectory;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenFiles.add (initialFileOrDirectory);
|
||||
currentRoot = initialFileOrDirectory.getParentDirectory();
|
||||
filename = initialFileOrDirectory.getFileName();
|
||||
}
|
||||
|
||||
// The thread must be started before the DirectoryContentsList attempts to scan any file
|
||||
thread.startThread (Thread::Priority::low);
|
||||
|
||||
fileList.reset (new DirectoryContentsList (this, thread));
|
||||
fileList->setDirectory (currentRoot, true, true);
|
||||
|
||||
if ((flags & useTreeView) != 0)
|
||||
{
|
||||
auto tree = new FileTreeComponent (*fileList);
|
||||
fileListComponent.reset (tree);
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
tree->setMultiSelectEnabled (true);
|
||||
|
||||
addAndMakeVisible (tree);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto list = new FileListComponent (*fileList);
|
||||
fileListComponent.reset (list);
|
||||
list->setOutlineThickness (1);
|
||||
|
||||
if ((flags & canSelectMultipleItems) != 0)
|
||||
list->setMultipleSelectionEnabled (true);
|
||||
|
||||
addAndMakeVisible (list);
|
||||
}
|
||||
|
||||
fileListComponent->addListener (this);
|
||||
|
||||
addAndMakeVisible (currentPathBox);
|
||||
currentPathBox.setEditableText (true);
|
||||
resetRecentPaths();
|
||||
currentPathBox.onChange = [this] { updateSelectedPath(); };
|
||||
|
||||
addAndMakeVisible (filenameBox);
|
||||
filenameBox.setMultiLine (false);
|
||||
filenameBox.setSelectAllWhenFocused (true);
|
||||
filenameBox.setText (filename, false);
|
||||
filenameBox.onTextChange = [this] { sendListenerChangeMessage(); };
|
||||
filenameBox.onReturnKey = [this] { changeFilename(); };
|
||||
filenameBox.onFocusLost = [this]
|
||||
{
|
||||
if (! isSaveMode())
|
||||
selectionChanged();
|
||||
};
|
||||
|
||||
filenameBox.setReadOnly ((flags & (filenameBoxIsReadOnly | canSelectMultipleItems)) != 0);
|
||||
|
||||
addAndMakeVisible (fileLabel);
|
||||
fileLabel.attachToComponent (&filenameBox, true);
|
||||
|
||||
if (previewComp != nullptr)
|
||||
addAndMakeVisible (previewComp);
|
||||
|
||||
lookAndFeelChanged();
|
||||
|
||||
setRoot (currentRoot);
|
||||
|
||||
if (filename.isNotEmpty())
|
||||
setFileName (filename);
|
||||
|
||||
startTimer (2000);
|
||||
}
|
||||
|
||||
FileBrowserComponent::~FileBrowserComponent()
|
||||
{
|
||||
fileListComponent.reset();
|
||||
fileList.reset();
|
||||
thread.stopThread (10000);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::addListener (FileBrowserListener* const newListener)
|
||||
{
|
||||
listeners.add (newListener);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::removeListener (FileBrowserListener* const listener)
|
||||
{
|
||||
listeners.remove (listener);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isSaveMode() const noexcept
|
||||
{
|
||||
return (flags & saveMode) != 0;
|
||||
}
|
||||
|
||||
int FileBrowserComponent::getNumSelectedFiles() const noexcept
|
||||
{
|
||||
if (chosenFiles.isEmpty() && currentFileIsValid())
|
||||
return 1;
|
||||
|
||||
return chosenFiles.size();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getSelectedFile (int index) const noexcept
|
||||
{
|
||||
if ((flags & canSelectDirectories) != 0 && filenameBox.getText().isEmpty())
|
||||
return currentRoot;
|
||||
|
||||
if (! filenameBox.isReadOnly())
|
||||
return currentRoot.getChildFile (filenameBox.getText());
|
||||
|
||||
return chosenFiles[index];
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::currentFileIsValid() const
|
||||
{
|
||||
auto f = getSelectedFile (0);
|
||||
|
||||
if ((flags & canSelectDirectories) == 0 && f.isDirectory())
|
||||
return false;
|
||||
|
||||
return isSaveMode() || f.exists();
|
||||
}
|
||||
|
||||
File FileBrowserComponent::getHighlightedFile() const noexcept
|
||||
{
|
||||
return fileListComponent->getSelectedFile (0);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::deselectAllFiles()
|
||||
{
|
||||
fileListComponent->deselectAllFiles();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool FileBrowserComponent::isFileSuitable (const File& file) const
|
||||
{
|
||||
return (flags & canSelectFiles) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (file));
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isDirectorySuitable (const File&) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileBrowserComponent::isFileOrDirSuitable (const File& f) const
|
||||
{
|
||||
if (f.isDirectory())
|
||||
return (flags & canSelectDirectories) != 0
|
||||
&& (fileFilter == nullptr || fileFilter->isDirectorySuitable (f));
|
||||
|
||||
return (flags & canSelectFiles) != 0 && f.exists()
|
||||
&& (fileFilter == nullptr || fileFilter->isFileSuitable (f));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const File& FileBrowserComponent::getRoot() const
|
||||
{
|
||||
return currentRoot;
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setRoot (const File& newRootDirectory)
|
||||
{
|
||||
bool callListeners = false;
|
||||
|
||||
if (currentRoot != newRootDirectory)
|
||||
{
|
||||
callListeners = true;
|
||||
fileListComponent->scrollToTop();
|
||||
|
||||
String path (newRootDirectory.getFullPathName());
|
||||
|
||||
if (path.isEmpty())
|
||||
path = File::getSeparatorString();
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (! rootPaths.contains (path, true))
|
||||
{
|
||||
bool alreadyListed = false;
|
||||
|
||||
for (int i = currentPathBox.getNumItems(); --i >= 0;)
|
||||
{
|
||||
if (currentPathBox.getItemText (i).equalsIgnoreCase (path))
|
||||
{
|
||||
alreadyListed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! alreadyListed)
|
||||
currentPathBox.addItem (path, currentPathBox.getNumItems() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
currentRoot = newRootDirectory;
|
||||
fileList->setDirectory (currentRoot, true, true);
|
||||
|
||||
if (auto* tree = dynamic_cast<FileTreeComponent*> (fileListComponent.get()))
|
||||
tree->refresh();
|
||||
|
||||
auto currentRootName = currentRoot.getFullPathName();
|
||||
|
||||
if (currentRootName.isEmpty())
|
||||
currentRootName = File::getSeparatorString();
|
||||
|
||||
currentPathBox.setText (currentRootName, dontSendNotification);
|
||||
|
||||
goUpButton->setEnabled (currentRoot.getParentDirectory().isDirectory()
|
||||
&& currentRoot.getParentDirectory() != currentRoot);
|
||||
|
||||
if (callListeners)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.browserRootChanged (currentRoot); });
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileName (const String& newName)
|
||||
{
|
||||
filenameBox.setText (newName, true);
|
||||
|
||||
fileListComponent->setSelectedFile (currentRoot.getChildFile (newName));
|
||||
}
|
||||
|
||||
void FileBrowserComponent::resetRecentPaths()
|
||||
{
|
||||
currentPathBox.clear();
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
for (int i = 0; i < rootNames.size(); ++i)
|
||||
{
|
||||
if (rootNames[i].isEmpty())
|
||||
currentPathBox.addSeparator();
|
||||
else
|
||||
currentPathBox.addItem (rootNames[i], i + 1);
|
||||
}
|
||||
|
||||
currentPathBox.addSeparator();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::goUp()
|
||||
{
|
||||
setRoot (getRoot().getParentDirectory());
|
||||
}
|
||||
|
||||
void FileBrowserComponent::refresh()
|
||||
{
|
||||
fileList->refresh();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFileFilter (const FileFilter* const newFileFilter)
|
||||
{
|
||||
if (fileFilter != newFileFilter)
|
||||
{
|
||||
fileFilter = newFileFilter;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
String FileBrowserComponent::getActionVerb() const
|
||||
{
|
||||
return isSaveMode() ? ((flags & canSelectDirectories) != 0 ? TRANS ("Choose")
|
||||
: TRANS ("Save"))
|
||||
: TRANS ("Open");
|
||||
}
|
||||
|
||||
void FileBrowserComponent::setFilenameBoxLabel (const String& name)
|
||||
{
|
||||
fileLabel.setText (name, dontSendNotification);
|
||||
}
|
||||
|
||||
FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept
|
||||
{
|
||||
return previewComp;
|
||||
}
|
||||
|
||||
DirectoryContentsDisplayComponent* FileBrowserComponent::getDisplayComponent() const noexcept
|
||||
{
|
||||
return fileListComponent.get();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::resized()
|
||||
{
|
||||
getLookAndFeel()
|
||||
.layoutFileBrowserComponent (*this, fileListComponent.get(), previewComp,
|
||||
¤tPathBox, &filenameBox, goUpButton.get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::lookAndFeelChanged()
|
||||
{
|
||||
goUpButton.reset (getLookAndFeel().createFileBrowserGoUpButton());
|
||||
|
||||
if (auto* buttonPtr = goUpButton.get())
|
||||
{
|
||||
addAndMakeVisible (*buttonPtr);
|
||||
buttonPtr->onClick = [this] { goUp(); };
|
||||
buttonPtr->setTooltip (TRANS ("Go up to parent directory"));
|
||||
}
|
||||
|
||||
currentPathBox.setColour (ComboBox::backgroundColourId, findColour (currentPathBoxBackgroundColourId));
|
||||
currentPathBox.setColour (ComboBox::textColourId, findColour (currentPathBoxTextColourId));
|
||||
currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId));
|
||||
|
||||
filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId));
|
||||
filenameBox.applyColourToAllText (findColour (filenameBoxTextColourId));
|
||||
|
||||
resized();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::sendListenerChangeMessage()
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
|
||||
if (previewComp != nullptr)
|
||||
previewComp->selectedFileChanged (getSelectedFile (0));
|
||||
|
||||
// You shouldn't delete the browser when the file gets changed!
|
||||
jassert (! checker.shouldBailOut());
|
||||
|
||||
listeners.callChecked (checker, [] (FileBrowserListener& l) { l.selectionChanged(); });
|
||||
}
|
||||
|
||||
void FileBrowserComponent::selectionChanged()
|
||||
{
|
||||
StringArray newFilenames;
|
||||
bool resetChosenFiles = true;
|
||||
|
||||
for (int i = 0; i < fileListComponent->getNumSelectedFiles(); ++i)
|
||||
{
|
||||
const File f (fileListComponent->getSelectedFile (i));
|
||||
|
||||
if (isFileOrDirSuitable (f))
|
||||
{
|
||||
if (resetChosenFiles)
|
||||
{
|
||||
chosenFiles.clear();
|
||||
resetChosenFiles = false;
|
||||
}
|
||||
|
||||
chosenFiles.add (f);
|
||||
newFilenames.add (f.getRelativePathFrom (getRoot()));
|
||||
}
|
||||
}
|
||||
|
||||
if (newFilenames.size() > 0)
|
||||
filenameBox.setText (newFilenames.joinIntoString (", "), false);
|
||||
|
||||
sendListenerChangeMessage();
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileClicked (const File& f, const MouseEvent& e)
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileClicked (f, e); });
|
||||
}
|
||||
|
||||
void FileBrowserComponent::fileDoubleClicked (const File& f)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
|
||||
if ((flags & canSelectDirectories) != 0 && (flags & doNotClearFileNameOnRootChange) == 0)
|
||||
filenameBox.setText ({});
|
||||
}
|
||||
else
|
||||
{
|
||||
Component::BailOutChecker checker (this);
|
||||
listeners.callChecked (checker, [&] (FileBrowserListener& l) { l.fileDoubleClicked (f); });
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::browserRootChanged (const File&) {}
|
||||
|
||||
bool FileBrowserComponent::keyPressed ([[maybe_unused]] const KeyPress& key)
|
||||
{
|
||||
#if JUCE_LINUX || JUCE_BSD || JUCE_WINDOWS
|
||||
if (key.getModifiers().isCommandDown()
|
||||
&& (key.getKeyCode() == 'H' || key.getKeyCode() == 'h'))
|
||||
{
|
||||
fileList->setIgnoresHiddenFiles (! fileList->ignoresHiddenFiles());
|
||||
fileList->refresh();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::changeFilename()
|
||||
{
|
||||
if (filenameBox.getText().containsChar (File::getSeparatorChar()))
|
||||
{
|
||||
auto f = currentRoot.getChildFile (filenameBox.getText());
|
||||
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
chosenFiles.clear();
|
||||
|
||||
if ((flags & doNotClearFileNameOnRootChange) == 0)
|
||||
filenameBox.setText ({});
|
||||
}
|
||||
else
|
||||
{
|
||||
setRoot (f.getParentDirectory());
|
||||
chosenFiles.clear();
|
||||
chosenFiles.add (f);
|
||||
filenameBox.setText (f.getFileName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDoubleClicked (getSelectedFile (0));
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void FileBrowserComponent::updateSelectedPath()
|
||||
{
|
||||
auto newText = currentPathBox.getText().trim().unquoted();
|
||||
|
||||
if (newText.isNotEmpty())
|
||||
{
|
||||
auto index = currentPathBox.getSelectedId() - 1;
|
||||
|
||||
StringArray rootNames, rootPaths;
|
||||
getRoots (rootNames, rootPaths);
|
||||
|
||||
if (rootPaths[index].isNotEmpty())
|
||||
{
|
||||
setRoot (File (rootPaths[index]));
|
||||
}
|
||||
else
|
||||
{
|
||||
File f (newText);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (f.isDirectory())
|
||||
{
|
||||
setRoot (f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (f.getParentDirectory() == f)
|
||||
break;
|
||||
|
||||
f = f.getParentDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getDefaultRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
Array<File> roots;
|
||||
File::findFileSystemRoots (roots);
|
||||
rootPaths.clear();
|
||||
|
||||
for (int i = 0; i < roots.size(); ++i)
|
||||
{
|
||||
const File& drive = roots.getReference (i);
|
||||
|
||||
String name (drive.getFullPathName());
|
||||
rootPaths.add (name);
|
||||
|
||||
if (drive.isOnHardDisk())
|
||||
{
|
||||
String volume (drive.getVolumeLabel());
|
||||
|
||||
if (volume.isEmpty())
|
||||
volume = TRANS ("Hard Drive");
|
||||
|
||||
name << " [" << volume << ']';
|
||||
}
|
||||
else if (drive.isOnCDRomDrive())
|
||||
{
|
||||
name << " [" << TRANS ("CD/DVD drive") << ']';
|
||||
}
|
||||
|
||||
rootNames.add (name);
|
||||
}
|
||||
|
||||
rootPaths.add ({});
|
||||
rootNames.add ({});
|
||||
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Desktop"));
|
||||
|
||||
#elif JUCE_MAC
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDocumentsDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Documents"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userMusicDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Music"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userPicturesDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Pictures"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Desktop"));
|
||||
|
||||
rootPaths.add ({});
|
||||
rootNames.add ({});
|
||||
|
||||
for (auto& volume : File ("/Volumes").findChildFiles (File::findDirectories, false))
|
||||
{
|
||||
if (volume.isDirectory() && ! volume.getFileName().startsWithChar ('.'))
|
||||
{
|
||||
rootPaths.add (volume.getFullPathName());
|
||||
rootNames.add (volume.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
rootPaths.add ("/");
|
||||
rootNames.add ("/");
|
||||
rootPaths.add (File::getSpecialLocation (File::userHomeDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Home folder"));
|
||||
rootPaths.add (File::getSpecialLocation (File::userDesktopDirectory).getFullPathName());
|
||||
rootNames.add (TRANS ("Desktop"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void FileBrowserComponent::getRoots (StringArray& rootNames, StringArray& rootPaths)
|
||||
{
|
||||
getDefaultRoots (rootNames, rootPaths);
|
||||
}
|
||||
|
||||
void FileBrowserComponent::timerCallback()
|
||||
{
|
||||
const auto isProcessActive = detail::WindowingHelpers::isForegroundOrEmbeddedProcess (this);
|
||||
|
||||
if (wasProcessActive != isProcessActive)
|
||||
{
|
||||
wasProcessActive = isProcessActive;
|
||||
|
||||
if (isProcessActive && fileList != nullptr)
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::unique_ptr<AccessibilityHandler> FileBrowserComponent::createAccessibilityHandler()
|
||||
{
|
||||
return std::make_unique<AccessibilityHandler> (*this, AccessibilityRole::group);
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 for browsing and selecting a file or directory to open or save.
|
||||
|
||||
This contains a FileListComponent and adds various boxes and controls for
|
||||
navigating and selecting a file. It can work in different modes so that it can
|
||||
be used for loading or saving a file, or for choosing a directory.
|
||||
|
||||
@see FileChooserDialogBox, FileChooser, FileListComponent
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserComponent : public Component,
|
||||
private FileBrowserListener,
|
||||
private FileFilter,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Various options for the browser.
|
||||
|
||||
A combination of these is passed into the FileBrowserComponent constructor.
|
||||
*/
|
||||
enum FileChooserFlags
|
||||
{
|
||||
openMode = 1, /**< specifies that the component should allow the user to
|
||||
choose an existing file with the intention of opening it. */
|
||||
saveMode = 2, /**< specifies that the component should allow the user to specify
|
||||
the name of a file that will be used to save something. */
|
||||
canSelectFiles = 4, /**< specifies that the user can select files (can be used in
|
||||
conjunction with canSelectDirectories). */
|
||||
canSelectDirectories = 8, /**< specifies that the user can select directories (can be used in
|
||||
conjunction with canSelectFiles). */
|
||||
canSelectMultipleItems = 16, /**< specifies that the user can select multiple items. */
|
||||
useTreeView = 32, /**< specifies that a tree-view should be shown instead of a file list. */
|
||||
filenameBoxIsReadOnly = 64, /**< specifies that the user can't type directly into the filename box. */
|
||||
warnAboutOverwriting = 128, /**< specifies that the dialog should warn about overwriting existing files (if possible). */
|
||||
doNotClearFileNameOnRootChange = 256 /**< specifies that the file name should not be cleared upon root change. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a FileBrowserComponent.
|
||||
|
||||
@param flags A combination of flags from the FileChooserFlags enumeration, used to
|
||||
specify the component's behaviour. The flags must contain either openMode
|
||||
or saveMode, and canSelectFiles and/or canSelectDirectories.
|
||||
@param initialFileOrDirectory The file or directory that should be selected when the component begins.
|
||||
If this is File(), a default directory will be chosen.
|
||||
@param fileFilter an optional filter to use to determine which files are shown.
|
||||
If this is nullptr then all files are displayed. Note that a pointer
|
||||
is kept internally to this object, so make sure that it is not deleted
|
||||
before the FileBrowserComponent object is deleted.
|
||||
@param previewComp an optional preview component that will be used to show previews of
|
||||
files that the user selects
|
||||
*/
|
||||
FileBrowserComponent (int flags,
|
||||
const File& initialFileOrDirectory,
|
||||
const FileFilter* fileFilter,
|
||||
FilePreviewComponent* previewComp);
|
||||
|
||||
/** Destructor. */
|
||||
~FileBrowserComponent() override;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the number of files that the user has got selected.
|
||||
If multiple select isn't active, this will only be 0 or 1. To get the complete
|
||||
list of files they've chosen, pass an index to getCurrentFile().
|
||||
*/
|
||||
int getNumSelectedFiles() const noexcept;
|
||||
|
||||
/** Returns one of the files that the user has chosen.
|
||||
If the box has multi-select enabled, the index lets you specify which of the files
|
||||
to get - see getNumSelectedFiles() to find out how many files were chosen.
|
||||
@see getHighlightedFile
|
||||
*/
|
||||
File getSelectedFile (int index) const noexcept;
|
||||
|
||||
/** Deselects any files that are currently selected. */
|
||||
void deselectAllFiles();
|
||||
|
||||
/** Returns true if the currently selected file(s) are usable.
|
||||
|
||||
This can be used to decide whether the user can press "ok" for the
|
||||
current file. What it does depends on the mode, so for example in an "open"
|
||||
mode, this only returns true if a file has been selected and if it exists.
|
||||
In a "save" mode, a non-existent file would also be valid.
|
||||
*/
|
||||
bool currentFileIsValid() const;
|
||||
|
||||
/** This returns the last item in the view that the user has highlighted.
|
||||
This may be different from getCurrentFile(), which returns the value
|
||||
that is shown in the filename box, and if there are multiple selections,
|
||||
this will only return one of them.
|
||||
@see getSelectedFile
|
||||
*/
|
||||
File getHighlightedFile() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the directory whose contents are currently being shown in the listbox. */
|
||||
const File& getRoot() const;
|
||||
|
||||
/** Changes the directory that's being shown in the listbox. */
|
||||
void setRoot (const File& newRootDirectory);
|
||||
|
||||
/** Changes the name that is currently shown in the filename box. */
|
||||
void setFileName (const String& newName);
|
||||
|
||||
/** Equivalent to pressing the "up" button to browse the parent directory. */
|
||||
void goUp();
|
||||
|
||||
/** Refreshes the directory that's currently being listed. */
|
||||
void refresh();
|
||||
|
||||
/** Changes the filter that's being used to sift the files. */
|
||||
void setFileFilter (const FileFilter* newFileFilter);
|
||||
|
||||
/** Returns a verb to describe what should happen when the file is accepted.
|
||||
|
||||
E.g. if browsing in "load file" mode, this will be "Open", if in "save file"
|
||||
mode, it'll be "Save", etc.
|
||||
*/
|
||||
virtual String getActionVerb() const;
|
||||
|
||||
/** Returns true if the saveMode flag was set when this component was created. */
|
||||
bool isSaveMode() const noexcept;
|
||||
|
||||
/** Sets the label that will be displayed next to the filename entry box.
|
||||
By default this is just "file", but you might want to change it to something more
|
||||
appropriate for your app.
|
||||
*/
|
||||
void setFilenameBoxLabel (const String& name);
|
||||
|
||||
//==============================================================================
|
||||
/** Adds a listener to be told when the user selects and clicks on files.
|
||||
@see removeListener
|
||||
*/
|
||||
void addListener (FileBrowserListener* listener);
|
||||
|
||||
/** Removes a listener.
|
||||
@see addListener
|
||||
*/
|
||||
void removeListener (FileBrowserListener* listener);
|
||||
|
||||
/** Returns a platform-specific list of names and paths for some suggested places the user
|
||||
might want to use as root folders.
|
||||
The list returned contains empty strings to indicate section breaks.
|
||||
@see getRoots()
|
||||
*/
|
||||
static void getDefaultRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
//==============================================================================
|
||||
/** This abstract base class is implemented by LookAndFeel classes to provide
|
||||
various file-browser layout and drawing methods.
|
||||
*/
|
||||
struct JUCE_API LookAndFeelMethods
|
||||
{
|
||||
virtual ~LookAndFeelMethods() = default;
|
||||
|
||||
// These return a pointer to an internally cached drawable - make sure you don't keep
|
||||
// a copy of this pointer anywhere, as it may become invalid in the future.
|
||||
virtual const Drawable* getDefaultFolderImage() = 0;
|
||||
virtual const Drawable* getDefaultDocumentFileImage() = 0;
|
||||
|
||||
virtual AttributedString createFileChooserHeaderText (const String& title,
|
||||
const String& instructions) = 0;
|
||||
|
||||
virtual void drawFileBrowserRow (Graphics&, int width, int height,
|
||||
const File& file,
|
||||
const String& filename,
|
||||
Image* optionalIcon,
|
||||
const String& fileSizeDescription,
|
||||
const String& fileTimeDescription,
|
||||
bool isDirectory,
|
||||
bool isItemSelected,
|
||||
int itemIndex,
|
||||
DirectoryContentsDisplayComponent&) = 0;
|
||||
|
||||
virtual Button* createFileBrowserGoUpButton() = 0;
|
||||
|
||||
virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp,
|
||||
DirectoryContentsDisplayComponent* fileListComponent,
|
||||
FilePreviewComponent* previewComp,
|
||||
ComboBox* currentPathBox,
|
||||
TextEditor* filenameBox,
|
||||
Button* goUpButton) = 0;
|
||||
};
|
||||
|
||||
/** A set of colour IDs to use to change the colour of various aspects of the FileBrowserComponent.
|
||||
|
||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
|
||||
methods.
|
||||
|
||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
|
||||
*/
|
||||
enum ColourIds
|
||||
{
|
||||
currentPathBoxBackgroundColourId = 0x1000640, /**< The colour to use to fill the background of the current path ComboBox. */
|
||||
currentPathBoxTextColourId = 0x1000641, /**< The colour to use for the text of the current path ComboBox. */
|
||||
currentPathBoxArrowColourId = 0x1000642, /**< The colour to use to draw the arrow of the current path ComboBox. */
|
||||
filenameBoxBackgroundColourId = 0x1000643, /**< The colour to use to fill the background of the filename TextEditor. */
|
||||
filenameBoxTextColourId = 0x1000644 /**< The colour to use for the text of the filename TextEditor. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** @internal */
|
||||
void resized() override;
|
||||
/** @internal */
|
||||
void lookAndFeelChanged() override;
|
||||
/** @internal */
|
||||
bool keyPressed (const KeyPress&) override;
|
||||
/** @internal */
|
||||
void selectionChanged() override;
|
||||
/** @internal */
|
||||
void fileClicked (const File&, const MouseEvent&) override;
|
||||
/** @internal */
|
||||
void fileDoubleClicked (const File&) override;
|
||||
/** @internal */
|
||||
void browserRootChanged (const File&) override;
|
||||
/** @internal */
|
||||
bool isFileSuitable (const File&) const override;
|
||||
/** @internal */
|
||||
bool isDirectorySuitable (const File&) const override;
|
||||
/** @internal */
|
||||
FilePreviewComponent* getPreviewComponent() const noexcept;
|
||||
/** @internal */
|
||||
DirectoryContentsDisplayComponent* getDisplayComponent() const noexcept;
|
||||
/** @internal */
|
||||
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override;
|
||||
|
||||
protected:
|
||||
/** Returns a list of names and paths for the default places the user might want to look.
|
||||
|
||||
By default this just calls getDefaultRoots(), but you may want to override it to
|
||||
return a custom list.
|
||||
*/
|
||||
virtual void getRoots (StringArray& rootNames, StringArray& rootPaths);
|
||||
|
||||
/** Updates the items in the dropdown list of recent paths with the values from getRoots(). */
|
||||
void resetRecentPaths();
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<DirectoryContentsList> fileList;
|
||||
const FileFilter* fileFilter;
|
||||
|
||||
int flags;
|
||||
File currentRoot;
|
||||
Array<File> chosenFiles;
|
||||
ListenerList<FileBrowserListener> listeners;
|
||||
|
||||
std::unique_ptr<DirectoryContentsDisplayComponent> fileListComponent;
|
||||
FilePreviewComponent* previewComp;
|
||||
ComboBox currentPathBox;
|
||||
TextEditor filenameBox;
|
||||
Label fileLabel;
|
||||
std::unique_ptr<Button> goUpButton;
|
||||
TimeSliceThread thread;
|
||||
bool wasProcessActive;
|
||||
|
||||
void timerCallback() override;
|
||||
void sendListenerChangeMessage();
|
||||
bool isFileOrDirSuitable (const File&) const;
|
||||
void updateSelectedPath();
|
||||
void changeFilename();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBrowserComponent)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 listener for user selection events in a file browser.
|
||||
|
||||
This is used by a FileBrowserComponent or FileListComponent.
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileBrowserListener
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Destructor. */
|
||||
virtual ~FileBrowserListener();
|
||||
|
||||
//==============================================================================
|
||||
/** Callback when the user selects a different file in the browser. */
|
||||
virtual void selectionChanged() = 0;
|
||||
|
||||
/** Callback when the user clicks on a file in the browser. */
|
||||
virtual void fileClicked (const File& file, const MouseEvent& e) = 0;
|
||||
|
||||
/** Callback when the user double-clicks on a file in the browser. */
|
||||
virtual void fileDoubleClicked (const File& file) = 0;
|
||||
|
||||
/** Callback when the browser's root folder changes. */
|
||||
virtual void browserRootChanged (const File& newRoot) = 0;
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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 FileChooser::NonNative final : public std::enable_shared_from_this<NonNative>,
|
||||
public FileChooser::Pimpl
|
||||
{
|
||||
public:
|
||||
NonNative (FileChooser& fileChooser, int flags, FilePreviewComponent* preview)
|
||||
: owner (fileChooser),
|
||||
selectsDirectories ((flags & FileBrowserComponent::canSelectDirectories) != 0),
|
||||
selectsFiles ((flags & FileBrowserComponent::canSelectFiles) != 0),
|
||||
warnAboutOverwrite ((flags & FileBrowserComponent::warnAboutOverwriting) != 0),
|
||||
|
||||
filter (selectsFiles ? owner.filters : String(), selectsDirectories ? "*" : String(), {}),
|
||||
browserComponent (flags, owner.startingFile, &filter, preview),
|
||||
dialogBox (owner.title, {}, browserComponent, warnAboutOverwrite,
|
||||
browserComponent.findColour (AlertWindow::backgroundColourId), owner.parent)
|
||||
{}
|
||||
|
||||
~NonNative() override
|
||||
{
|
||||
dialogBox.exitModalState (0);
|
||||
}
|
||||
|
||||
void launch() override
|
||||
{
|
||||
dialogBox.centreWithDefaultSize (nullptr);
|
||||
|
||||
const std::weak_ptr<NonNative> ref (shared_from_this());
|
||||
auto* callback = ModalCallbackFunction::create ([ref] (int r)
|
||||
{
|
||||
if (auto locked = ref.lock())
|
||||
locked->modalStateFinished (r);
|
||||
});
|
||||
|
||||
dialogBox.enterModalState (true, callback, true);
|
||||
}
|
||||
|
||||
void runModally() override
|
||||
{
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
modalStateFinished (dialogBox.show() ? 1 : 0);
|
||||
#else
|
||||
jassertfalse;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void modalStateFinished (int returnValue)
|
||||
{
|
||||
Array<URL> result;
|
||||
|
||||
if (returnValue != 0)
|
||||
{
|
||||
for (int i = 0; i < browserComponent.getNumSelectedFiles(); ++i)
|
||||
result.add (URL (browserComponent.getSelectedFile (i)));
|
||||
}
|
||||
|
||||
owner.finished (result);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
FileChooser& owner;
|
||||
bool selectsDirectories, selectsFiles, warnAboutOverwrite;
|
||||
|
||||
WildcardFileFilter filter;
|
||||
FileBrowserComponent browserComponent;
|
||||
FileChooserDialogBox dialogBox;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NonNative)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
FileChooser::FileChooser (const String& chooserBoxTitle,
|
||||
const File& currentFileOrDirectory,
|
||||
const String& fileFilters,
|
||||
const bool useNativeBox,
|
||||
const bool treatFilePackagesAsDirectories,
|
||||
Component* parentComponentToUse)
|
||||
: title (chooserBoxTitle),
|
||||
filters (fileFilters),
|
||||
startingFile (currentFileOrDirectory),
|
||||
parent (parentComponentToUse),
|
||||
useNativeDialogBox (useNativeBox && isPlatformDialogAvailable()),
|
||||
treatFilePackagesAsDirs (treatFilePackagesAsDirectories)
|
||||
{
|
||||
#ifndef JUCE_MAC
|
||||
ignoreUnused (treatFilePackagesAsDirs);
|
||||
#endif
|
||||
|
||||
if (! fileFilters.containsNonWhitespaceChars())
|
||||
filters = "*";
|
||||
}
|
||||
|
||||
FileChooser::~FileChooser()
|
||||
{
|
||||
asyncCallback = nullptr;
|
||||
}
|
||||
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
bool FileChooser::browseForFileToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesToOpen (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComp)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| FileBrowserComponent::canSelectDirectories
|
||||
| FileBrowserComponent::canSelectMultipleItems,
|
||||
previewComp);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForFileToSave (const bool warnAboutOverwrite)
|
||||
{
|
||||
return showDialog (FileBrowserComponent::saveMode
|
||||
| FileBrowserComponent::canSelectFiles
|
||||
| (warnAboutOverwrite ? FileBrowserComponent::warnAboutOverwriting : 0),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::browseForDirectory()
|
||||
{
|
||||
return showDialog (FileBrowserComponent::openMode
|
||||
| FileBrowserComponent::canSelectDirectories,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
bool FileChooser::showDialog (const int flags, FilePreviewComponent* const previewComp)
|
||||
{
|
||||
detail::FocusRestorer focusRestorer;
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->runModally();
|
||||
|
||||
// ensure that the finished function was invoked
|
||||
jassert (pimpl == nullptr);
|
||||
|
||||
return (results.size() > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void FileChooser::launchAsync (int flags, std::function<void (const FileChooser&)> callback,
|
||||
FilePreviewComponent* previewComp)
|
||||
{
|
||||
// You must specify a callback when using launchAsync
|
||||
jassert (callback);
|
||||
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassert (asyncCallback == nullptr);
|
||||
|
||||
asyncCallback = std::move (callback);
|
||||
|
||||
pimpl = createPimpl (flags, previewComp);
|
||||
pimpl->launch();
|
||||
}
|
||||
|
||||
std::shared_ptr<FileChooser::Pimpl> FileChooser::createPimpl (int flags, FilePreviewComponent* previewComp)
|
||||
{
|
||||
results.clear();
|
||||
|
||||
// the preview component needs to be the right size before you pass it in here..
|
||||
jassert (previewComp == nullptr || (previewComp->getWidth() > 10
|
||||
&& previewComp->getHeight() > 10));
|
||||
|
||||
if (pimpl != nullptr)
|
||||
{
|
||||
// you cannot run two file chooser dialog boxes at the same time
|
||||
jassertfalse;
|
||||
pimpl.reset();
|
||||
}
|
||||
|
||||
// You've set the flags for both saveMode and openMode!
|
||||
jassert (! (((flags & FileBrowserComponent::saveMode) != 0)
|
||||
&& ((flags & FileBrowserComponent::openMode) != 0)));
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
const bool selectsFiles = (flags & FileBrowserComponent::canSelectFiles) != 0;
|
||||
const bool selectsDirectories = (flags & FileBrowserComponent::canSelectDirectories) != 0;
|
||||
|
||||
if (useNativeDialogBox && ! (selectsFiles && selectsDirectories))
|
||||
#else
|
||||
if (useNativeDialogBox)
|
||||
#endif
|
||||
{
|
||||
return showPlatformDialog (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
return std::make_unique<NonNative> (*this, flags, previewComp);
|
||||
}
|
||||
|
||||
Array<File> FileChooser::getResults() const noexcept
|
||||
{
|
||||
Array<File> files;
|
||||
|
||||
for (auto url : getURLResults())
|
||||
if (url.isLocalFile())
|
||||
files.add (url.getLocalFile());
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
File FileChooser::getResult() const
|
||||
{
|
||||
auto fileResults = getResults();
|
||||
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (fileResults.size() <= 1);
|
||||
|
||||
return fileResults.getFirst();
|
||||
}
|
||||
|
||||
URL FileChooser::getURLResult() const
|
||||
{
|
||||
// if you've used a multiple-file select, you should use the getResults() method
|
||||
// to retrieve all the files that were chosen.
|
||||
jassert (results.size() <= 1);
|
||||
|
||||
return results.getFirst();
|
||||
}
|
||||
|
||||
void FileChooser::finished (const Array<URL>& asyncResults)
|
||||
{
|
||||
const auto callback = std::exchange (asyncCallback, nullptr);
|
||||
|
||||
results = asyncResults;
|
||||
|
||||
pimpl.reset();
|
||||
|
||||
if (callback)
|
||||
callback (*this);
|
||||
}
|
||||
|
||||
#if ! JUCE_ANDROID
|
||||
void FileChooser::registerCustomMimeTypeForFileExtension ([[maybe_unused]] const String& mimeType,
|
||||
[[maybe_unused]] const String& fileExtension)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
//==============================================================================
|
||||
FilePreviewComponent::FilePreviewComponent() {}
|
||||
FilePreviewComponent::~FilePreviewComponent() {}
|
||||
|
||||
} // namespace juce
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
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 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Creates a dialog box to choose a file or directory to load or save.
|
||||
|
||||
@code
|
||||
std::unique_ptr<FileChooser> myChooser;
|
||||
|
||||
void loadMooseFile()
|
||||
{
|
||||
myChooser = std::make_unique<FileChooser> ("Please select the moose you want to load...",
|
||||
File::getSpecialLocation (File::userHomeDirectory),
|
||||
"*.moose");
|
||||
|
||||
auto folderChooserFlags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectDirectories;
|
||||
|
||||
myChooser->launchAsync (folderChooserFlags, [this] (const FileChooser& chooser)
|
||||
{
|
||||
File mooseFile (chooser.getResult());
|
||||
|
||||
loadMoose (mooseFile);
|
||||
});
|
||||
}
|
||||
@endcode
|
||||
|
||||
@tags{GUI}
|
||||
*/
|
||||
class JUCE_API FileChooser
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a FileChooser.
|
||||
|
||||
After creating one of these, use one of the browseFor... methods to display it.
|
||||
|
||||
@param dialogBoxTitle a text string to display in the dialog box to
|
||||
tell the user what's going on
|
||||
@param initialFileOrDirectory the file or directory that should be selected
|
||||
when the dialog box opens. If this parameter is
|
||||
set to File(), a sensible default directory will
|
||||
be used instead. When using native dialogs, not
|
||||
all platforms will actually select the file. For
|
||||
example, on macOS, when initialFileOrDirectory is
|
||||
a file, only the parent directory of
|
||||
initialFileOrDirectory will be used as the initial
|
||||
directory of the native file chooser.
|
||||
|
||||
Note: On iOS when saving a file, a user will not
|
||||
be able to change a file name, so it may be a good
|
||||
idea to include at least a valid file name in
|
||||
initialFileOrDirectory. When no filename is found,
|
||||
"Untitled" will be used.
|
||||
|
||||
Also, if you pass an already existing file on iOS,
|
||||
that file will be automatically copied to the
|
||||
destination chosen by user and if it can be previewed,
|
||||
its preview will be presented in the dialog too. You
|
||||
will still be able to write into this file copy, since
|
||||
its URL will be returned by getURLResult(). This can be
|
||||
useful when you want to save e.g. an image, so that
|
||||
you can pass a (temporary) file with low quality
|
||||
preview and after the user picks the destination,
|
||||
you can write a high quality image into the copied
|
||||
file. If you create such a temporary file, you need
|
||||
to delete it yourself, once it is not needed anymore.
|
||||
|
||||
@param filePatternsAllowed a set of file patterns to specify which files can be
|
||||
selected - each pattern should be separated by a comma or
|
||||
semi-colon, e.g. "*" or "*.jpg;*.gif". The native MacOS
|
||||
file browser only supports wildcard that specify
|
||||
extensions, so "*.jpg" is OK but "myfilename*" will not
|
||||
work. An empty string means that all files are allowed
|
||||
@param useOSNativeDialogBox if true, then a native dialog box will be used
|
||||
if possible; if false, then a Juce-based
|
||||
browser dialog box will always be used
|
||||
@param treatFilePackagesAsDirectories if true, then the file chooser will allow the
|
||||
selection of files inside packages when
|
||||
invoked on OS X and when using native dialog
|
||||
boxes.
|
||||
@param parentComponent An optional component which should be the parent
|
||||
for the file chooser. If this is a nullptr then the
|
||||
FileChooser will be a top-level window. AUv3s on iOS
|
||||
must specify this parameter as opening a top-level window
|
||||
in an AUv3 is forbidden due to sandbox restrictions.
|
||||
|
||||
@see browseForFileToOpen, browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
FileChooser (const String& dialogBoxTitle,
|
||||
const File& initialFileOrDirectory = File(),
|
||||
const String& filePatternsAllowed = String(),
|
||||
bool useOSNativeDialogBox = true,
|
||||
bool treatFilePackagesAsDirectories = false,
|
||||
Component* parentComponent = nullptr);
|
||||
|
||||
/** Destructor. */
|
||||
~FileChooser();
|
||||
|
||||
//==============================================================================
|
||||
#if JUCE_MODAL_LOOPS_PERMITTED
|
||||
/** Shows a dialog box to choose a file to open.
|
||||
|
||||
This will display the dialog box modally, using an "open file" mode, so that
|
||||
it won't allow non-existent files or directories to be chosen.
|
||||
|
||||
@param previewComponent an optional component to display inside the dialog
|
||||
box to show special info about the files that the user
|
||||
is browsing. The component will not be deleted by this
|
||||
object, so the caller must take care of it.
|
||||
@returns true if the user selected a file, in which case, use the getResult()
|
||||
method to find out what it was. Returns false if they cancelled instead.
|
||||
@see browseForFileToSave, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesToOpen (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
/** Shows a dialog box to choose a file to save.
|
||||
|
||||
This will display the dialog box modally, using an "save file" mode, so it
|
||||
will allow non-existent files to be chosen, but not directories.
|
||||
|
||||
@param warnAboutOverwritingExistingFiles if true, the dialog box will ask
|
||||
the user if they're sure they want to overwrite a file that already
|
||||
exists
|
||||
@returns true if the user chose a file and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what the file was. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForDirectory
|
||||
*/
|
||||
bool browseForFileToSave (bool warnAboutOverwritingExistingFiles);
|
||||
|
||||
/** Shows a dialog box to choose a directory.
|
||||
|
||||
This will display the dialog box modally, using an "open directory" mode, so it
|
||||
will only allow directories to be returned, not files.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see browseForFileToOpen, browseForFileToSave
|
||||
*/
|
||||
bool browseForDirectory();
|
||||
|
||||
/** Same as browseForFileToOpen, but allows the user to select multiple files and directories.
|
||||
|
||||
The files that are returned can be obtained by calling getResults(). See
|
||||
browseForFileToOpen() for more info about the behaviour of this method.
|
||||
*/
|
||||
bool browseForMultipleFilesOrDirectories (FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Runs a dialog box for the given set of option flags.
|
||||
The flag values used are those in FileBrowserComponent::FileChooserFlags.
|
||||
|
||||
@returns true if the user chose a directory and pressed 'ok', in which case, use
|
||||
the getResult() method to find out what they chose. Returns false
|
||||
if they cancelled instead.
|
||||
@see FileBrowserComponent::FileChooserFlags
|
||||
*/
|
||||
bool showDialog (int flags, FilePreviewComponent* previewComponent);
|
||||
#endif
|
||||
|
||||
/** Use this method to launch the file browser window asynchronously.
|
||||
|
||||
This will create a file browser dialog based on the settings in this
|
||||
structure and will launch it modally, returning immediately.
|
||||
|
||||
You must specify a callback which is called when the file browser is
|
||||
cancelled or a file is selected. To abort the file selection, simply
|
||||
delete the FileChooser object.
|
||||
|
||||
You must ensure that the lifetime of the callback object is longer than
|
||||
the lifetime of the file-chooser.
|
||||
*/
|
||||
void launchAsync (int flags,
|
||||
std::function<void (const FileChooser&)>,
|
||||
FilePreviewComponent* previewComponent = nullptr);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last file that was chosen by one of the browseFor methods.
|
||||
|
||||
After calling the appropriate browseFor... method, this method lets you
|
||||
find out what file or directory they chose.
|
||||
|
||||
Note that the file returned is only valid if the browse method returned true (i.e.
|
||||
if the user pressed 'ok' rather than cancelling).
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResult() instead.
|
||||
|
||||
If you're using a multiple-file select, then use the getResults() method instead,
|
||||
to obtain the list of all files chosen.
|
||||
|
||||
@see getURLResult, getResults
|
||||
*/
|
||||
File getResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
On mobile platforms, the file browser may return a URL instead of a local file.
|
||||
Therefore, on mobile platforms, you should call getURLResults() instead.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
@see getURLResults, getResult
|
||||
*/
|
||||
Array<File> getResults() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the last document that was chosen by one of the browseFor methods.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResult, URL::getLocalFile
|
||||
*/
|
||||
URL getURLResult() const;
|
||||
|
||||
/** Returns a list of all the files that were chosen during the last call to a
|
||||
browse method.
|
||||
|
||||
Use this method if you are using the FileChooser on a mobile platform which
|
||||
may return a URL to a remote document. If a local file is chosen then you can
|
||||
convert this file to a JUCE File class via the URL::getLocalFile method.
|
||||
|
||||
This array may be empty if no files were chosen, or can contain multiple entries
|
||||
if multiple files were chosen.
|
||||
|
||||
Note: On iOS you must use the returned URL object directly (you are also
|
||||
allowed to copy- or move-construct another URL from the returned URL), rather
|
||||
than just storing the path as a String and then creating a new URL from that
|
||||
String. This is because the returned URL contains internally a security
|
||||
bookmark that is required to access the files pointed by it. Then, once you stop
|
||||
dealing with the file pointed by the URL, you should dispose that URL object,
|
||||
so that the security bookmark can be released by the system (only a limited
|
||||
number of such URLs is allowed).
|
||||
|
||||
@see getResults, URL::getLocalFile
|
||||
*/
|
||||
const Array<URL>& getURLResults() const noexcept { return results; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns if a native filechooser is currently available on this platform.
|
||||
|
||||
Note: On iOS this will only return true if you have iCloud permissions
|
||||
and code-signing enabled in the Projucer and have added iCloud containers
|
||||
to your app in Apple's online developer portal. Additionally, the user must
|
||||
have installed the iCloud app on their device and used the app at least once.
|
||||
*/
|
||||
static bool isPlatformDialogAvailable();
|
||||
|
||||
/** Associate a particular file-extension to a mime-type
|
||||
|
||||
On Android, JUCE needs to convert common file extensions to mime-types when using
|
||||
wildcard filters in native file chooser dialog boxes. JUCE has an extensive conversion
|
||||
table to convert between the most common file-types and mime-types transparently, but
|
||||
some more obscure file-types may be missing. Use this method to register your own
|
||||
mime-type to file extension conversions. Please contact the JUCE team if you think
|
||||
that a common mime-type/file-extension entry is missing in JUCE's internal tables.
|
||||
Does nothing on other platforms.
|
||||
*/
|
||||
static void registerCustomMimeTypeForFileExtension (const String& mimeType,
|
||||
const String& fileExtension);
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
class Native;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
String title, filters;
|
||||
File startingFile;
|
||||
Component* parent;
|
||||
Array<URL> results;
|
||||
const bool useNativeDialogBox;
|
||||
const bool treatFilePackagesAsDirs;
|
||||
std::function<void (const FileChooser&)> asyncCallback;
|
||||
|
||||
//==============================================================================
|
||||
void finished (const Array<URL>&);
|
||||
|
||||
//==============================================================================
|
||||
struct Pimpl
|
||||
{
|
||||
virtual ~Pimpl() = default;
|
||||
|
||||
virtual void launch() = 0;
|
||||
virtual void runModally() = 0;
|
||||
};
|
||||
|
||||
std::shared_ptr<Pimpl> pimpl;
|
||||
|
||||
//==============================================================================
|
||||
std::shared_ptr<Pimpl> createPimpl (int, FilePreviewComponent*);
|
||||
static std::shared_ptr<Pimpl> showPlatformDialog (FileChooser&, int, FilePreviewComponent*);
|
||||
|
||||
class NonNative;
|
||||
friend class NonNative;
|
||||
friend class Native;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileChooser)
|
||||
};
|
||||
|
||||
} // namespace juce
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user