Some improvements to the color selection UI (#1019)

* Tab and Shift+Tab keys cycle through RGB/HSB text fields
* Sliders don't get the keyboard focus (only text fields)
* Up/Down keys increase/decrease the text field value
This commit is contained in:
David Capello 2016-11-21 18:03:06 -03:00
parent 54da860a00
commit f985e6c6c0
8 changed files with 141 additions and 23 deletions

View File

@ -251,10 +251,10 @@ PaletteEntryEditor::PaletteEntryEditor()
, m_selfPalChange(false) , m_selfPalChange(false)
, m_fromPalette(0, 0) , m_fromPalette(0, 0)
{ {
m_colorType.addItem("RGB"); m_colorType.addItem("RGB")->setFocusStop(false);
m_colorType.addItem("HSB"); m_colorType.addItem("HSB")->setFocusStop(false);
m_changeMode.addItem("Abs"); m_changeMode.addItem("Abs")->setFocusStop(false);
m_changeMode.addItem("Rel"); m_changeMode.addItem("Rel")->setFocusStop(false);
m_topBox.setBorder(gfx::Border(0)); m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0); m_topBox.setChildSpacing(0);

View File

@ -58,11 +58,11 @@ ColorPopup::ColorPopup(bool canPin)
, m_canPin(canPin) , m_canPin(canPin)
, m_disableHexUpdate(false) , m_disableHexUpdate(false)
{ {
m_colorType.addItem("Index"); m_colorType.addItem("Index")->setFocusStop(false);
m_colorType.addItem("RGB"); m_colorType.addItem("RGB")->setFocusStop(false);
m_colorType.addItem("HSB"); m_colorType.addItem("HSB")->setFocusStop(false);
m_colorType.addItem("Gray"); m_colorType.addItem("Gray")->setFocusStop(false);
m_colorType.addItem("Mask"); m_colorType.addItem("Mask")->setFocusStop(false);
m_topBox.setBorder(gfx::Border(0)); m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0); m_topBox.setChildSpacing(0);

View File

@ -13,6 +13,7 @@
#include "app/ui/skin/skin_slider_property.h" #include "app/ui/skin/skin_slider_property.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/scoped_value.h"
#include "ui/box.h" #include "ui/box.h"
#include "ui/entry.h" #include "ui/entry.h"
#include "ui/graphics.h" #include "ui/graphics.h"
@ -84,26 +85,100 @@ namespace {
class ColorEntry : public Entry { class ColorEntry : public Entry {
public: public:
ColorEntry() : Entry(4, "0") { ColorEntry(Slider* absSlider, Slider* relSlider)
: Entry(4, "0")
, m_absSlider(absSlider)
, m_relSlider(relSlider)
, m_recent_focus(false) {
}
private:
int minValue() const {
if (m_absSlider->isVisible())
return m_absSlider->getMinValue();
else if (m_relSlider->isVisible())
return m_relSlider->getMinValue();
else
return 0;
}
int maxValue() const {
if (m_absSlider->isVisible())
return m_absSlider->getMaxValue();
else if (m_relSlider->isVisible())
return m_relSlider->getMaxValue();
else
return 0;
} }
bool onProcessMessage(Message* msg) override { bool onProcessMessage(Message* msg) override {
switch (msg->type()) { switch (msg->type()) {
case kFocusEnterMessage:
m_recent_focus = true;
break;
case kKeyDownMessage: case kKeyDownMessage:
if (Entry::onProcessMessage(msg)) if (Entry::onProcessMessage(msg))
return true; return true;
// Process focus movement key here because if our
// CustomizedGuiManager catches this kKeyDownMessage it will if (hasFocus()) {
// process it as a shortcut to switch the Timeline. int scancode = static_cast<KeyMessage*>(msg)->scancode();
else if (manager()->processFocusMovementMessage(msg))
return true; switch (scancode) {
else // Enter just remove the focus
return false; case kKeyEnter:
case kKeyEnterPad:
releaseFocus();
return true;
case kKeyDown:
case kKeyUp: {
int value = textInt();
if (scancode == kKeyDown)
--value;
else
++value;
setTextf("%d", MID(minValue(), value, maxValue()));
selectAllText();
onChange();
return true;
}
}
// Process focus movement key here because if our
// CustomizedGuiManager catches this kKeyDownMessage it
// will process it as a shortcut to switch the Timeline.
//
// Note: The default ui::Manager handles focus movement
// shortcuts only for foreground windows.
// TODO maybe that should change
if (hasFocus() &&
manager()->processFocusMovementMessage(msg))
return true;
}
return false;
} }
return Entry::onProcessMessage(msg);
bool result = Entry::onProcessMessage(msg);
if (msg->type() == kMouseDownMessage && m_recent_focus) {
m_recent_focus = false;
selectAllText();
}
return result;
} }
Slider* m_absSlider;
Slider* m_relSlider;
// TODO remove this calling setFocus() in
// Widget::onProcessMessage() instead of
// Manager::handleWindowZOrder()
bool m_recent_focus;
}; };
} }
@ -115,6 +190,7 @@ ColorSliders::ColorSliders()
: Widget(kGenericWidget) : Widget(kGenericWidget)
, m_grid(3, false) , m_grid(3, false)
, m_mode(Absolute) , m_mode(Absolute)
, m_lockEntry(-1)
{ {
addChild(&m_grid); addChild(&m_grid);
m_grid.setChildSpacing(0); m_grid.setChildSpacing(0);
@ -161,7 +237,7 @@ void ColorSliders::addSlider(Channel channel, const char* labelText, int min, in
Label* label = new Label(labelText); Label* label = new Label(labelText);
Slider* absSlider = new Slider(min, max, 0); Slider* absSlider = new Slider(min, max, 0);
Slider* relSlider = new Slider(min-max, max-min, 0); Slider* relSlider = new Slider(min-max, max-min, 0);
Entry* entry = new ColorEntry(); Entry* entry = new ColorEntry(absSlider, relSlider);
m_label.push_back(label); m_label.push_back(label);
m_absSlider.push_back(absSlider); m_absSlider.push_back(absSlider);
@ -180,6 +256,8 @@ void ColorSliders::addSlider(Channel channel, const char* labelText, int min, in
HBox* box = new HBox(); HBox* box = new HBox();
box->addChild(absSlider); box->addChild(absSlider);
box->addChild(relSlider); box->addChild(relSlider);
absSlider->setFocusStop(false);
relSlider->setFocusStop(false);
absSlider->setExpansive(true); absSlider->setExpansive(true);
relSlider->setExpansive(true); relSlider->setExpansive(true);
relSlider->setVisible(false); relSlider->setVisible(false);
@ -218,6 +296,8 @@ void ColorSliders::onSliderChange(int i)
void ColorSliders::onEntryChange(int i) void ColorSliders::onEntryChange(int i)
{ {
base::ScopedValue<int> lock(m_lockEntry, i, m_lockEntry);
// Update the slider related to the changed entry widget. // Update the slider related to the changed entry widget.
int value = m_entry[i]->textInt(); int value = m_entry[i]->textInt();
@ -245,10 +325,15 @@ void ColorSliders::onControlChange(int i)
// Updates the entry related to the changed slider widget. // Updates the entry related to the changed slider widget.
void ColorSliders::updateEntryText(int entryIndex) void ColorSliders::updateEntryText(int entryIndex)
{ {
if (m_lockEntry == entryIndex)
return;
Slider* slider = (m_mode == Absolute ? m_absSlider[entryIndex]: Slider* slider = (m_mode == Absolute ? m_absSlider[entryIndex]:
m_relSlider[entryIndex]); m_relSlider[entryIndex]);
m_entry[entryIndex]->setTextf("%d", slider->getValue()); m_entry[entryIndex]->setTextf("%d", slider->getValue());
if (m_entry[entryIndex]->hasFocus())
m_entry[entryIndex]->selectAllText();
} }
void ColorSliders::updateSlidersBgColor(const app::Color& color) void ColorSliders::updateSlidersBgColor(const app::Color& color)

View File

@ -72,6 +72,7 @@ namespace app {
std::vector<Channel> m_channel; std::vector<Channel> m_channel;
ui::Grid m_grid; ui::Grid m_grid;
Mode m_mode; Mode m_mode;
int m_lockEntry;
}; };
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////

View File

@ -14,21 +14,41 @@
#include "app/ui/hex_color_entry.h" #include "app/ui/hex_color_entry.h"
#include "base/hex.h" #include "base/hex.h"
#include "gfx/border.h" #include "gfx/border.h"
#include "ui/message.h"
#include "ui/theme.h" #include "ui/theme.h"
namespace app { namespace app {
using namespace ui; using namespace ui;
HexColorEntry::CustomEntry::CustomEntry()
: Entry(16, "")
{
}
bool HexColorEntry::CustomEntry::onProcessMessage(ui::Message* msg)
{
switch (msg->type()) {
case kMouseDownMessage:
setFocusStop(true);
requestFocus();
break;
case kFocusLeaveMessage:
setFocusStop(false);
break;
}
return Entry::onProcessMessage(msg);
}
HexColorEntry::HexColorEntry() HexColorEntry::HexColorEntry()
: Box(HORIZONTAL) : Box(HORIZONTAL)
, m_label("#") , m_label("#")
, m_entry(16, "")
{ {
addChild(&m_label); addChild(&m_label);
addChild(&m_entry); addChild(&m_entry);
m_entry.Change.connect(&HexColorEntry::onEntryChange, this); m_entry.Change.connect(&HexColorEntry::onEntryChange, this);
m_entry.setFocusStop(false);
initTheme(); initTheme();

View File

@ -30,8 +30,15 @@ namespace app {
void onEntryChange(); void onEntryChange();
private: private:
class CustomEntry : public ui::Entry {
public:
CustomEntry();
private:
bool onProcessMessage(ui::Message* msg) override;
};
ui::Label m_label; ui::Label m_label;
ui::Entry m_entry; CustomEntry m_entry;
}; };
} // namespace app } // namespace app

View File

@ -32,6 +32,7 @@ PopupWindowPin::PopupWindowPin(const std::string& text, ClickBehavior clickBehav
{ {
SkinTheme* theme = SkinTheme::instance(); SkinTheme* theme = SkinTheme::instance();
m_pin.setFocusStop(false);
m_pin.Click.connect(&PopupWindowPin::onPinClick, this); m_pin.Click.connect(&PopupWindowPin::onPinClick, this);
m_pin.setIconInterface( m_pin.setIconInterface(
new ButtonIconImpl(theme->parts.unpinned(), new ButtonIconImpl(theme->parts.unpinned(),

View File

@ -895,9 +895,13 @@ void SkinTheme::paintCheckBox(PaintEvent& ev)
if (iconInterface) if (iconInterface)
paintIcon(widget, g, iconInterface, icon.x, icon.y); paintIcon(widget, g, iconInterface, icon.x, icon.y);
// draw focus // Draw focus
if (look != WithoutBordersLook && widget->hasFocus()) if (look != WithoutBordersLook &&
(widget->hasFocus() || (iconInterface &&
widget->text().empty() &&
widget->hasMouseOver()))) {
drawRect(g, bounds, parts.checkFocus().get(), gfx::ColorNone); drawRect(g, bounds, parts.checkFocus().get(), gfx::ColorNone);
}
} }
void SkinTheme::paintGrid(PaintEvent& ev) void SkinTheme::paintGrid(PaintEvent& ev)