1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Add FocusOutline class for indicating Component keyboard focus

This commit is contained in:
ed 2021-12-20 09:40:42 +00:00 committed by Tom Poole
parent 0e24c9557e
commit 461192b355
14 changed files with 416 additions and 32 deletions

View file

@ -190,10 +190,13 @@ private:
ButtonsComponent()
{
addAndMakeVisible (radioButtons);
textButton.setHasFocusOutline (true);
addAndMakeVisible (textButton);
shapeButton.setShape (getJUCELogoPath(), false, true, false);
shapeButton.onClick = [] { AlertWindow::showMessageBoxAsync (MessageBoxIconType::InfoIcon, "Alert", "This is an AlertWindow"); };
shapeButton.setHasFocusOutline (true);
addAndMakeVisible (shapeButton);
}
@ -217,6 +220,7 @@ private:
{
b.setRadioGroupId (1);
b.setButtonText ("Button " + String (index++));
b.setHasFocusOutline (true);
addAndMakeVisible (b);
}

View file

@ -1475,6 +1475,23 @@ public:
*/
virtual std::unique_ptr<ComponentTraverser> createKeyboardFocusTraverser();
/** Use this to indicate that the component should have an outline drawn around it
when it has keyboard focus.
If this is set to true, then when the component gains keyboard focus the
LookAndFeel::createFocusOutlineForComponent() method will be used to draw an outline
around it.
@see FocusOutline, hasFocusOutline
*/
void setHasFocusOutline (bool hasFocusOutline) noexcept { flags.hasFocusOutlineFlag = hasFocusOutline; }
/** Returns true if this component should have a focus outline.
@see FocusOutline, setHasFocusOutline
*/
bool hasFocusOutline() const noexcept { return flags.hasFocusOutlineFlag; }
//==============================================================================
/** Returns true if the component (and all its parents) are enabled.
@ -2548,6 +2565,7 @@ private:
bool isKeyboardFocusContainerFlag : 1;
bool childKeyboardFocusedFlag : 1;
bool dontFocusOnMouseClickFlag : 1;
bool hasFocusOutlineFlag : 1;
bool alwaysOnTopFlag : 1;
bool bufferToImageFlag : 1;
bool bringToFrontOnClickFlag : 1;

View file

@ -189,6 +189,24 @@ void Desktop::addFocusChangeListener (FocusChangeListener* l) { focusListen
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
@ -197,6 +215,8 @@ void Desktop::handleAsyncUpdate()
{
l.globalFocusChanged (currentFocus.get());
});
updateFocusOutline();
}
//==============================================================================

View file

@ -436,6 +436,8 @@ private:
std::unique_ptr<LookAndFeel> defaultLookAndFeel;
WeakReference<LookAndFeel> currentLookAndFeel;
std::unique_ptr<FocusOutline> focusOutline;
Component* kioskModeComponent = nullptr;
Rectangle<int> kioskComponentOriginalBounds;
bool kioskModeReentrant = false;
@ -458,6 +460,7 @@ private:
void setKioskComponent (Component*, bool shouldBeEnabled, bool allowMenusAndBars);
void triggerFocusCallback();
void updateFocusOutline();
void handleAsyncUpdate() override;
static Point<float> getMousePositionFloat();

View file

@ -246,6 +246,7 @@ namespace juce
#include "application/juce_Application.cpp"
#include "misc/juce_BubbleComponent.cpp"
#include "misc/juce_DropShadower.cpp"
#include "misc/juce_FocusOutline.cpp"
#include "misc/juce_JUCESplashScreen.cpp"
#include "layout/juce_FlexBox.cpp"

View file

@ -161,6 +161,7 @@ namespace juce
class FlexBox;
class Grid;
class FocusOutline;
#if JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX
Image createSnapshotOfNativeWindow (void* nativeWindowHandle);
@ -259,6 +260,7 @@ namespace juce
#include "menus/juce_BurgerMenuComponent.h"
#include "buttons/juce_ToolbarButton.h"
#include "misc/juce_DropShadower.h"
#include "misc/juce_FocusOutline.h"
#include "misc/juce_JUCESplashScreen.h"
#include "widgets/juce_TreeView.h"
#include "windows/juce_TopLevelWindow.h"

View file

@ -153,7 +153,9 @@ public:
Colour findColour (int colourId) const noexcept;
/** Registers a colour to be used for a particular purpose.
For more details, see the comments for findColour().
@see findColour, Component::findColour, Component::setColour
*/
void setColour (int colourId, Colour colour) noexcept;
@ -165,22 +167,27 @@ public:
//==============================================================================
/** Returns the typeface that should be used for a given font.
The default implementation just does what you'd expect it to, but you can override
this if you want to intercept fonts and use your own custom typeface object.
@see setDefaultTypeface
*/
virtual Typeface::Ptr getTypefaceForFont (const Font&);
/** Allows you to supply a default typeface that will be returned as the default
sans-serif font.
Instead of a typeface object, you can specify a typeface by name using the
setDefaultSansSerifTypefaceName() method.
You can perform more complex typeface substitutions by overloading
getTypefaceForFont() but this lets you easily set a global typeface.
*/
void setDefaultSansSerifTypeface (Typeface::Ptr newDefaultTypeface);
/** Allows you to change the default sans-serif font.
If you need to supply your own Typeface object for any of the default fonts, rather
than just supplying the name (e.g. if you want to use an embedded font), then
you can instead call setDefaultSansSerifTypeface() with an object to use.
@ -188,39 +195,64 @@ public:
void setDefaultSansSerifTypefaceName (const String& newName);
//==============================================================================
/** Override this to get the chance to swap a component's mouse cursor for a
customised one.
/** Sets whether native alert windows (if available) or standard JUCE AlertWindows
drawn with AlertWindow::LookAndFeelMethods will be used.
@see isUsingNativeAlertWindows
*/
virtual MouseCursor getMouseCursorFor (Component&);
//==============================================================================
/** Creates a new graphics context object. */
virtual std::unique_ptr<LowLevelGraphicsContext> createGraphicsContext (const Image& imageToRenderOn,
Point<int> origin,
const RectangleList<int>& initialClip);
void setUsingNativeAlertWindows (bool shouldUseNativeAlerts);
/** Returns true if native alert windows will be used (if available).
The default setting for this is false.
@see setUsingNativeAlertWindows
*/
bool isUsingNativeAlertWindows();
//==============================================================================
/** Draws a small image that spins to indicate that something's happening.
This method should use the current time to animate itself, so just keep
repainting it every so often.
*/
virtual void drawSpinningWaitAnimation (Graphics&, const Colour& colour,
int x, int y, int w, int h) = 0;
//==============================================================================
/** Returns a tick shape for use in yes/no boxes, etc. */
virtual Path getTickShape (float height) = 0;
/** Returns a cross shape for use in yes/no boxes, etc. */
virtual Path getCrossShape (float height) = 0;
//==============================================================================
virtual DropShadower* createDropShadowerForComponent (Component*) = 0;
/** Creates a drop-shadower for a given component, if required.
@see DropShadower
*/
virtual std::unique_ptr<DropShadower> createDropShadowerForComponent (Component&) = 0;
/** Creates a focus outline for a given component, if required.
@see FocusOutline
*/
virtual std::unique_ptr<FocusOutline> createFocusOutlineForComponent (Component&) = 0;
//==============================================================================
/** Plays the system's default 'beep' noise, to alert the user about something very important. */
/** Override this to get the chance to swap a component's mouse cursor for a
customised one.
@see MouseCursor
*/
virtual MouseCursor getMouseCursorFor (Component&);
/** Creates a new graphics context object. */
virtual std::unique_ptr<LowLevelGraphicsContext> createGraphicsContext (const Image& imageToRenderOn,
Point<int> origin,
const RectangleList<int>& initialClip);
/** Plays the system's default 'beep' noise, to alert the user about something
very important. This is only supported on some platforms.
*/
virtual void playAlertSound();
private:

View file

@ -2056,9 +2056,28 @@ int LookAndFeel_V2::getDefaultMenuBarHeight()
}
//==============================================================================
DropShadower* LookAndFeel_V2::createDropShadowerForComponent (Component*)
std::unique_ptr<DropShadower> LookAndFeel_V2::createDropShadowerForComponent (Component&)
{
return new DropShadower (DropShadow (Colours::black.withAlpha (0.4f), 10, Point<int> (0, 2)));
return std::make_unique<DropShadower> (DropShadow (Colours::black.withAlpha (0.4f), 10, Point<int> (0, 2)));
}
std::unique_ptr<FocusOutline> LookAndFeel_V2::createFocusOutlineForComponent (Component&)
{
struct WindowProperties : public FocusOutline::OutlineWindowProperties
{
Rectangle<int> getOutlineBounds (Component& c) override
{
return c.getScreenBounds();
}
void drawOutline (Graphics& g, int width, int height) override
{
g.setColour (Colours::yellow.withAlpha (0.6f));
g.drawRoundedRectangle ({ (float) width, (float) height }, 3.0f, 3.0f);
}
};
return std::make_unique<FocusOutline> (std::make_unique<WindowProperties>());
}
//==============================================================================

View file

@ -299,7 +299,8 @@ public:
bool positionTitleBarButtonsOnLeft) override;
//==============================================================================
DropShadower* createDropShadowerForComponent (Component*) override;
std::unique_ptr<DropShadower> createDropShadowerForComponent (Component&) override;
std::unique_ptr<FocusOutline> createFocusOutlineForComponent (Component&) override;
//==============================================================================
void drawStretchableLayoutResizerBar (Graphics&, int w, int h, bool isVerticalBar,

View file

@ -55,17 +55,8 @@ public:
/** Attaches the DropShadower to the component you want to shadow. */
void setOwner (Component* componentToFollow);
private:
//==============================================================================
class ShadowWindow;
WeakReference<Component> owner;
OwnedArray<Component> shadowWindows;
DropShadow shadow;
bool reentrant = false;
WeakReference<Component> lastParentComp;
void componentMovedOrResized (Component&, bool, bool) override;
void componentBroughtToFront (Component&) override;
void componentChildrenChanged (Component&) override;
@ -75,6 +66,14 @@ private:
void updateParent();
void updateShadows();
class ShadowWindow;
WeakReference<Component> owner;
OwnedArray<Component> shadowWindows;
DropShadow shadow;
bool reentrant = false;
WeakReference<Component> lastParentComp;
class ParentVisibilityChangedListener;
std::unique_ptr<ParentVisibilityChangedListener> visibilityChangedListener;

View file

@ -0,0 +1,185 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - 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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-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 OutlineWindowComponent : public Component
{
OutlineWindowComponent (Component* c, FocusOutline::OutlineWindowProperties& p)
: target (c), props (p)
{
setVisible (true);
setInterceptsMouseClicks (false, false);
if (target->isOnDesktop())
{
setSize (1, 1);
addToDesktop (ComponentPeer::windowIgnoresMouseClicks
| ComponentPeer::windowIsTemporary
| ComponentPeer::windowIgnoresKeyPresses);
}
else if (auto* parent = target->getParentComponent())
{
auto targetIndex = parent->getIndexOfChildComponent (target);
parent->addChildComponent (this, targetIndex + 1);
}
}
void paint (Graphics& g) override
{
if (target != nullptr)
props.drawOutline (g, getWidth(), getHeight());
}
void resized() override
{
repaint();
}
float getDesktopScaleFactor() const override
{
return target != nullptr ? target->getDesktopScaleFactor()
: Component::getDesktopScaleFactor();
}
private:
WeakReference<Component> target;
FocusOutline::OutlineWindowProperties& props;
JUCE_DECLARE_NON_COPYABLE (OutlineWindowComponent)
};
//==============================================================================
FocusOutline::FocusOutline (std::unique_ptr<OutlineWindowProperties> props)
: properties (std::move (props))
{
}
FocusOutline::~FocusOutline()
{
if (owner != nullptr)
owner->removeComponentListener (this);
if (lastParentComp != nullptr)
lastParentComp->removeComponentListener (this);
}
void FocusOutline::setOwner (Component* componentToFollow)
{
if (componentToFollow != owner)
{
if (owner != nullptr)
owner->removeComponentListener (this);
owner = componentToFollow;
if (owner != nullptr)
owner->addComponentListener (this);
updateParent();
updateOutlineWindow();
}
}
void FocusOutline::componentMovedOrResized (Component& c, bool, bool)
{
if (owner == &c)
updateOutlineWindow();
}
void FocusOutline::componentBroughtToFront (Component& c)
{
if (owner == &c)
updateOutlineWindow();
}
void FocusOutline::componentParentHierarchyChanged (Component& c)
{
if (owner == &c)
{
updateParent();
updateOutlineWindow();
}
}
void FocusOutline::componentVisibilityChanged (Component& c)
{
if (owner == &c)
updateOutlineWindow();
}
void FocusOutline::updateParent()
{
lastParentComp = (owner != nullptr ? owner->getParentComponent()
: nullptr);
}
void FocusOutline::updateOutlineWindow()
{
if (reentrant)
return;
const ScopedValueSetter<bool> setter (reentrant, true);
if (owner == nullptr)
{
outlineWindow = nullptr;
return;
}
if (owner->isShowing()
&& owner->getWidth() > 0 && owner->getHeight() > 0)
{
if (outlineWindow == nullptr)
outlineWindow = std::make_unique<OutlineWindowComponent> (owner, *properties);
WeakReference<Component> deletionChecker (outlineWindow.get());
outlineWindow->setAlwaysOnTop (owner->isAlwaysOnTop());
if (deletionChecker == nullptr)
return;
const auto windowBounds = [this]
{
const auto bounds = properties->getOutlineBounds (*owner);
if (lastParentComp != nullptr)
return lastParentComp->getLocalArea (nullptr, bounds);
return bounds;
}();
outlineWindow->setBounds (windowBounds);
}
else
{
outlineWindow = nullptr;
}
}
} // namespace juce

View file

@ -0,0 +1,100 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - 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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-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
{
/**
Adds a focus outline to a component.
This object creates and manages a component that sits on top of a target
component. It will track the position of the target component and will be
brought to the front with the tracked component.
Use the Component::setHasFocusOutline() method to indicate that a component
should have a focus outline drawn around it, and when it receives keyboard
focus one of these objects will be created using the
LookAndFeel::createFocusOutlineForComponent() method. You can override this
method in your own LookAndFeel classes to draw a custom outline if required.
@tags{GUI}
*/
class JUCE_API FocusOutline : private ComponentListener
{
public:
//==============================================================================
/** Defines the focus outline window properties.
Pass an instance of one of these to the FocusOutline constructor to control
the bounds for the outline window and how it is drawn.
*/
struct JUCE_API OutlineWindowProperties
{
virtual ~OutlineWindowProperties() = default;
/** Return the bounds for the outline window in screen coordinates. */
virtual Rectangle<int> getOutlineBounds (Component& focusedComponent) = 0;
/** This method will be called to draw the focus outline. */
virtual void drawOutline (Graphics&, int width, int height) = 0;
};
//==============================================================================
/** Creates a FocusOutline.
Call setOwner to attach it to a component.
*/
FocusOutline (std::unique_ptr<OutlineWindowProperties> props);
/** Destructor. */
~FocusOutline() override;
/** Attaches the outline to a component. */
void setOwner (Component* componentToFollow);
private:
//==============================================================================
void componentMovedOrResized (Component&, bool, bool) override;
void componentBroughtToFront (Component&) override;
void componentParentHierarchyChanged (Component&) override;
void componentVisibilityChanged (Component&) override;
void updateOutlineWindow();
void updateParent();
//==============================================================================
std::unique_ptr<OutlineWindowProperties> properties;
WeakReference<Component> owner;
std::unique_ptr<Component> outlineWindow;
WeakReference<Component> lastParentComp;
bool reentrant = false;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FocusOutline)
};
} // namespace juce

View file

@ -2452,7 +2452,7 @@ private:
if (! component.isCurrentlyModal() && (styleFlags & windowHasDropShadow) != 0
&& ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista))
{
shadower.reset (component.getLookAndFeel().createDropShadowerForComponent (&component));
shadower = component.getLookAndFeel().createDropShadowerForComponent (component);
if (shadower != nullptr)
shadower->setOwner (&component);

View file

@ -150,7 +150,7 @@ TopLevelWindow::TopLevelWindow (const String& name, const bool shouldAddToDeskto
TopLevelWindow::~TopLevelWindow()
{
shadower.reset();
shadower = nullptr;
TopLevelWindowManager::getInstance()->removeWindow (this);
}
@ -213,7 +213,7 @@ void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
if (isOnDesktop())
{
shadower.reset();
shadower = nullptr;
Component::addToDesktop (getDesktopWindowStyleFlags());
}
else
@ -222,7 +222,7 @@ void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
{
if (shadower == nullptr)
{
shadower.reset (getLookAndFeel().createDropShadowerForComponent (this));
shadower = getLookAndFeel().createDropShadowerForComponent (*this);
if (shadower != nullptr)
shadower->setOwner (this);
@ -230,7 +230,7 @@ void TopLevelWindow::setDropShadowEnabled (const bool useShadow)
}
else
{
shadower.reset();
shadower = nullptr;
}
}
}
@ -257,7 +257,7 @@ void TopLevelWindow::recreateDesktopWindow()
void TopLevelWindow::addToDesktop()
{
shadower.reset();
shadower = nullptr;
Component::addToDesktop (getDesktopWindowStyleFlags());
setDropShadowEnabled (isDropShadowEnabled()); // force an update to clear away any fake shadows if necessary.
}