Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Widgets|libappfw: Added focus indicator and Focusable flag
The focus will be used for keyboard navigation.
  • Loading branch information
skyjake committed Feb 6, 2016
1 parent 08ca2d9 commit bbe8ef8
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 22 deletions.
28 changes: 17 additions & 11 deletions doomsday/apps/client/src/ui/home/drawerbuttonwidget.cpp
Expand Up @@ -51,6 +51,8 @@ DENG_GUI_PIMPL(DrawerButtonWidget)
DrawerButtonWidget::DrawerButtonWidget()
: d(new Instance(this))
{
setBehavior(Focusable);

Rule const &iconSize = d->label->rule().height();

d->icon->rule()
Expand Down Expand Up @@ -106,18 +108,22 @@ bool DrawerButtonWidget::isSelected() const

bool DrawerButtonWidget::handleEvent(Event const &event)
{
switch(handleMouseClick(event))
if(event.isMouse())
{
case MouseClickUnrelated:
return false;

case MouseClickStarted:
case MouseClickAborted:
return true;

case MouseClickFinished:
emit clicked();
return true;
switch(handleMouseClick(event))
{
case MouseClickUnrelated:
return false;

case MouseClickStarted:
case MouseClickAborted:
return true;

case MouseClickFinished:
root().setFocus(this);
emit clicked();
return true;
}
}
return false;
}
1 change: 1 addition & 0 deletions doomsday/apps/client/src/ui/widgets/inputbindingwidget.cpp
Expand Up @@ -174,6 +174,7 @@ DENG_GUI_PIMPL(InputBindingWidget)

InputBindingWidget::InputBindingWidget() : d(new Instance(this))
{
setBehavior(Focusable);
auxiliary().hide();
}

Expand Down
1 change: 1 addition & 0 deletions doomsday/apps/client/src/ui/widgets/keygrabberwidget.cpp
Expand Up @@ -50,6 +50,7 @@ DENG_GUI_PIMPL(KeyGrabberWidget)
KeyGrabberWidget::KeyGrabberWidget(String const &name)
: LabelWidget(name), d(new Instance(this))
{
setBehavior(Focusable);
setTextLineAlignment(AlignLeft);
setText(tr("Click to focus"));
}
Expand Down
1 change: 1 addition & 0 deletions doomsday/sdk/libappfw/include/de/FocusWidget
@@ -0,0 +1 @@
#include "widgets/focuswidget.h"
51 changes: 51 additions & 0 deletions doomsday/sdk/libappfw/include/de/widgets/focuswidget.h
@@ -0,0 +1,51 @@
/** @file focuswidget.h Input focus indicator.
*
* @authors Copyright (c) 2016 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBAPPFW_FOCUSWIDGET_H
#define LIBAPPFW_FOCUSWIDGET_H

#include <de/LabelWidget>

namespace de {

/**
* Input focus indicator.
*
* GuiRootWidget owns an instance of FocusWidget to show where the input focus
* is currently.
*/
class LIBAPPFW_PUBLIC FocusWidget : public LabelWidget
{
Q_OBJECT

public:
FocusWidget(de::String const &name = "focus");

void startFlashing();
void stopFlashing();

protected slots:
void updateFlash();

private:
DENG2_PRIVATE(d)
};

} // namespace de

#endif // LIBAPPFW_FOCUSWIDGET_H
24 changes: 24 additions & 0 deletions doomsday/sdk/libappfw/src/guirootwidget.cpp
Expand Up @@ -21,6 +21,7 @@
#include "de/BaseGuiApp"
#include "de/Style"
#include "de/BaseWindow"
#include "de/FocusWidget"

#include <de/CanvasWindow>
#include <de/TextureBank>
Expand All @@ -47,6 +48,7 @@ static DotPath const ID_DOT = "GuiRootWidget.dot";

DENG2_PIMPL(GuiRootWidget)
, DENG2_OBSERVES(Widget, ChildAddition)
, DENG2_OBSERVES(RootWidget, FocusChange)
{
/*
* Built-in runtime-generated images:
Expand Down Expand Up @@ -105,6 +107,7 @@ DENG2_PIMPL(GuiRootWidget)
GLUniform uTexAtlas;
TextureBank texBank; ///< Bank for the atlas contents.
bool noFramesDrawnYet;
FocusWidget *focusIndicator;

Instance(Public *i, CanvasWindow *win)
: Base(i)
Expand All @@ -114,6 +117,10 @@ DENG2_PIMPL(GuiRootWidget)
, noFramesDrawnYet(true)
{
self.audienceForChildAddition() += this;
self.audienceForFocusChange() += this;

focusIndicator = new FocusWidget;
self.add(focusIndicator);
}

~Instance()
Expand Down Expand Up @@ -167,6 +174,23 @@ DENG2_PIMPL(GuiRootWidget)
// Make sure newly added children know the view size.
child.viewResized();
child.notifyTree(&Widget::viewResized);

// Keep the focus at the top.
self.moveChildToLast(*focusIndicator);
}

void focusedWidgetChanged(Widget *focused)
{
if(GuiWidget *w = focused->maybeAs<GuiWidget>())
{
qDebug() << "focus set" << w->name();
focusIndicator->rule().setRect(w->rule());
focusIndicator->startFlashing();
}
else
{
focusIndicator->stopFlashing();
}
}
};

Expand Down
9 changes: 8 additions & 1 deletion doomsday/sdk/libappfw/src/widgets/buttonwidget.cpp
Expand Up @@ -199,7 +199,9 @@ DENG2_AUDIENCE_METHOD(ButtonWidget, Press)
DENG2_AUDIENCE_METHOD(ButtonWidget, Triggered)

ButtonWidget::ButtonWidget(String const &name) : LabelWidget(name), d(new Instance(this))
{}
{
setBehavior(Focusable);
}

void ButtonWidget::useInfoStyle(bool yes)
{
Expand Down Expand Up @@ -269,6 +271,11 @@ Action const *ButtonWidget::action() const

void ButtonWidget::trigger()
{
if(behavior().testFlag(Focusable))
{
root().setFocus(this);
}

// Hold an extra ref so the action isn't deleted by triggering.
AutoRef<Action> held = holdRef(d->action);

Expand Down
83 changes: 83 additions & 0 deletions doomsday/sdk/libappfw/src/widgets/focuswidget.cpp
@@ -0,0 +1,83 @@
/** @file focuswidget.cpp Input focus indicator.
*
* @authors Copyright (c) 2016 Jaakko Keränen <jaakko.keranen@iki.fi>
*
* @par License
* LGPL: http://www.gnu.org/licenses/lgpl.html
*
* <small>This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 3 of the License, or (at your
* option) any later version. This program is distributed in the hope that it
* will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
*/

#include "de/FocusWidget"

#include <QTimer>

namespace de {

static TimeDelta FLASH_SPAN = .1;

DENG2_PIMPL(FocusWidget)
{
QTimer flashing;

Instance(Public *i) : Base(i)
{
self.set(Background(Background::GradientFrame,
Style::get().colors().colorf("accent"), 6));

flashing.setInterval(FLASH_SPAN.asMilliSeconds());
flashing.setSingleShot(false);
}

void flash()
{
if(self.opacity().target() == 0)
{
self.setOpacity(.8f, FLASH_SPAN + .1, .1);
}
else if(self.opacity().target() > .5f)
{
self.setOpacity(.2f, FLASH_SPAN);
}
else
{
self.setOpacity(.8f, FLASH_SPAN);
}
}
};

FocusWidget::FocusWidget(String const &name)
: LabelWidget(name)
, d(new Instance(this))
{
connect(&d->flashing, SIGNAL(timeout()), this, SLOT(updateFlash()));
}

void FocusWidget::startFlashing()
{
setOpacity(0);
show();
d->flashing.start();
}

void FocusWidget::stopFlashing()
{
d->flashing.stop();
hide();
}

void FocusWidget::updateFlash()
{
d->flash();
}

} // namespace de

4 changes: 2 additions & 2 deletions doomsday/sdk/libappfw/src/widgets/lineeditwidget.cpp
Expand Up @@ -13,7 +13,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#include "de/LineEditWidget"
Expand Down Expand Up @@ -235,7 +235,7 @@ LineEditWidget::LineEditWidget(String const &name)
AbstractLineEditor(new FontLineWrapping),
d(new Instance(this))
{
setBehavior(ContentClipping);
setBehavior(ContentClipping | Focusable);

// The widget's height is tied to the number of lines.
rule().setInput(Rule::Height, *d->height);
Expand Down
5 changes: 4 additions & 1 deletion doomsday/sdk/libcore/include/de/widgets/rootwidget.h
Expand Up @@ -13,7 +13,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#ifndef LIBDENG2_ROOTWIDGET_H
Expand Down Expand Up @@ -44,6 +44,9 @@ class DENG2_PUBLIC RootWidget : public Widget
public:
typedef Vector2ui Size;

/// Notified when the focused widget changes.
DENG2_DEFINE_AUDIENCE2(FocusChange, void focusedWidgetChanged(Widget *))

public:
RootWidget();

Expand Down
3 changes: 3 additions & 0 deletions doomsday/sdk/libcore/include/de/widgets/widget.h
Expand Up @@ -77,6 +77,9 @@ class DENG2_PUBLIC Widget
/// Children of the widget should be clipped when drawing.
ChildVisibilityClipping = 0x100,

/// Widget can receive input focus.
Focusable = 0x200,

DefaultBehavior = 0
};
Q_DECLARE_FLAGS(Behaviors, Behavior)
Expand Down
27 changes: 22 additions & 5 deletions doomsday/sdk/libcore/src/widgets/rootwidget.cpp
Expand Up @@ -14,7 +14,7 @@
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details. You should have received a copy of
* the GNU Lesser General Public License along with this program; if not, see:
* http://www.gnu.org/licenses</small>
* http://www.gnu.org/licenses</small>
*/

#include "de/RootWidget"
Expand Down Expand Up @@ -46,8 +46,12 @@ DENG2_PIMPL_NOREF(RootWidget)
return Size(de::max(0, viewRect->width().valuei()),
de::max(0, viewRect->height().valuei()));
}

DENG2_PIMPL_AUDIENCE(FocusChange)
};

DENG2_AUDIENCE_METHOD(RootWidget, FocusChange)

RootWidget::RootWidget() : Widget(), d(new Instance)
{}

Expand Down Expand Up @@ -102,11 +106,23 @@ void RootWidget::setViewSize(Size const &size)
void RootWidget::setFocus(Widget *widget)
{
Widget *oldFocus = d->focus;

d->focus = 0;
if(oldFocus) oldFocus->focusLost();

d->focus = widget;
if(d->focus) d->focus->focusGained();
if(widget && widget->behavior().testFlag(Focusable))
{
d->focus = widget;
if(d->focus) d->focus->focusGained();
}

if(d->focus != oldFocus)
{
DENG2_FOR_AUDIENCE2(FocusChange, i)
{
i->focusedWidgetChanged(widget);
}
}
}

Widget *RootWidget::focus() const
Expand All @@ -125,7 +141,7 @@ void RootWidget::update()
}

void RootWidget::draw()
{
{
NotifyArgs args(&Widget::draw);
args.conditionFunc = &Widget::isVisible;
args.preNotifyFunc = &Widget::preDrawChildren;
Expand All @@ -137,7 +153,8 @@ void RootWidget::draw()

bool RootWidget::processEvent(Event const &event)
{
if(focus() && focus()->handleEvent(event))
// Focus is only for the keyboard.
if(event.isKey() && focus() && focus()->handleEvent(event))
{
// The focused widget ate the event.
return true;
Expand Down

0 comments on commit bbe8ef8

Please sign in to comment.