Move mnemonic key as a property of ui::Widget

In this was we can process the text string just one time to remove the
character preceded by '&' that will be finally acts as a mnemonic. This
simplifies the rendering and text measure code too.
This commit is contained in:
David Capello 2017-02-14 14:16:37 -03:00
parent bb4faca1d1
commit 17151cddcd
15 changed files with 131 additions and 102 deletions

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -231,12 +231,14 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
// Create the item
AppMenuItem* menuitem = new AppMenuItem(elem->Attribute("text"), command, params);
if (!menuitem)
return NULL;
return nullptr;
/* has it a ID? */
menuitem->processMnemonicFromText();
// Has it a ID?
const char* id = elem->Attribute("id");
if (id) {
/* recent list menu */
// Recent list menu
if (strcmp(id, "recent_list") == 0) {
m_recentListMenuitem = menuitem;
}

View File

@ -214,7 +214,7 @@ private:
g->drawUIText(text(), fg, bg,
gfx::Point(
bounds.x + m_level*16 * guiscale(),
bounds.y + 2*guiscale()));
bounds.y + 2*guiscale()), 0);
if (m_key && !m_key->accels().empty()) {
std::string buf;
@ -292,12 +292,13 @@ private:
const char* label = "x";
m_deleteButton->setBgColor(gfx::ColorNone);
m_deleteButton->setBounds(gfx::Rect(
itemBounds.x + itemBounds.w + 2*guiscale(),
itemBounds.y,
Graphics::measureUITextLength(
label, font()) + 4*guiscale(),
itemBounds.h));
m_deleteButton->setBounds(
gfx::Rect(
itemBounds.x + itemBounds.w + 2*guiscale(),
itemBounds.y,
Graphics::measureUITextLength(
label, font()) + 4*guiscale(),
itemBounds.h));
m_deleteButton->setText(label);
invalidate();

View File

@ -138,8 +138,7 @@ void ButtonSet::Item::onPaint(ui::PaintEvent& ev)
if (hasText()) {
g->setFont(font());
g->drawUIText(text(), fg, gfx::ColorNone, textRc.origin(),
false);
g->drawUIText(text(), fg, gfx::ColorNone, textRc.origin(), 0);
}
}
@ -159,7 +158,7 @@ bool ButtonSet::Item::onProcessMessage(ui::Message* msg)
if (isEnabled() && hasText()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
bool mnemonicPressed = (msg->altPressed() &&
mnemonicCharPressed(keymsg));
isMnemonicPressed(keymsg));
if (mnemonicPressed ||
(hasFocus() && keymsg->scancode() == kKeySpace)) {

View File

@ -230,7 +230,7 @@ void ColorButton::onPaint(PaintEvent& ev)
gfx::Rect textrc;
getTextIconInfo(NULL, &textrc);
g->drawUIText(text(), textcolor, gfx::ColorNone, textrc.origin());
g->drawUIText(text(), textcolor, gfx::ColorNone, textrc.origin(), 0);
}
void ColorButton::onClick(Event& ev)

View File

@ -947,7 +947,8 @@ void SkinTheme::paintCheckBox(PaintEvent& ev)
}
// Text
drawText(g, NULL, ColorNone, ColorNone, widget, text, 0);
drawText(g, nullptr, ColorNone, ColorNone, widget, text, 0,
widget->mnemonic());
// Paint the icon
if (iconInterface)
@ -1136,7 +1137,7 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
drawText(
g, widget->getSuffix().c_str(),
colors.entrySuffix(), ColorNone,
widget, sufBounds, 0);
widget, sufBounds, 0, 0);
}
}
@ -1227,7 +1228,7 @@ void SkinTheme::paintListItem(ui::PaintEvent& ev)
if (widget->hasText()) {
bounds.shrink(widget->border());
drawText(g, NULL, fg, bg, widget, bounds, 0);
drawText(g, nullptr, fg, bg, widget, bounds, 0, 0);
}
}
@ -1298,7 +1299,8 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev)
Rect pos = bounds;
if (!bar)
pos.offset(widget->childSpacing()/2, 0);
drawText(g, NULL, fg, ColorNone, widget, pos, 0);
drawText(g, nullptr, fg, ColorNone, widget, pos, 0,
widget->mnemonic());
// For menu-box
if (!bar) {
@ -1335,7 +1337,7 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev)
std::string buf = appMenuItem->key()->accels().front().toString();
widget->setAlign(RIGHT | MIDDLE);
drawText(g, buf.c_str(), fg, ColorNone, widget, pos, 0);
drawText(g, buf.c_str(), fg, ColorNone, widget, pos, 0, 0);
widget->setAlign(old_align);
}
}
@ -1375,7 +1377,7 @@ void SkinTheme::paintRadioButton(PaintEvent& ev)
}
// Text
drawText(g, NULL, ColorNone, ColorNone, widget, text, 0);
drawText(g, nullptr, ColorNone, ColorNone, widget, text, 0, widget->mnemonic());
// Icon
if (iconInterface)
@ -1417,9 +1419,9 @@ void SkinTheme::paintSeparator(ui::PaintEvent& ev)
bounds.y + bounds.h/2 - h/2,
widget->textWidth(), h);
drawText(g, NULL,
colors.separatorLabel(), BGCOLOR,
widget, r, 0);
drawText(g, nullptr,
colors.separatorLabel(), BGCOLOR,
widget, r, 0, widget->mnemonic());
}
}
@ -1522,18 +1524,18 @@ void SkinTheme::paintSlider(PaintEvent& ev)
{
IntersectClip clip(g, Rect(rc.x, rc.y, x-rc.x, rc.h));
if (clip) {
drawText(g, NULL,
colors.sliderFullText(), ColorNone,
widget, rc, 0);
drawText(g, nullptr,
colors.sliderFullText(), ColorNone,
widget, rc, 0, widget->mnemonic());
}
}
{
IntersectClip clip(g, Rect(x+1, rc.y, rc.w-(x-rc.x+1), rc.h));
if (clip) {
drawText(g, NULL,
colors.sliderEmptyText(),
ColorNone, widget, rc, 0);
drawText(g, nullptr,
colors.sliderEmptyText(),
ColorNone, widget, rc, 0, widget->mnemonic());
}
}
@ -1816,7 +1818,7 @@ gfx::Color SkinTheme::getWidgetBgColor(Widget* widget)
void SkinTheme::drawText(Graphics* g, const char *t, gfx::Color fg_color, gfx::Color bg_color,
Widget* widget, const Rect& rc,
int selected_offset)
int selected_offset, int mnemonic)
{
if (t || widget->hasText()) {
Rect textrc;
@ -1869,18 +1871,22 @@ void SkinTheme::drawText(Graphics* g, const char *t, gfx::Color fg_color, gfx::C
if (clip) {
if (!widget->isEnabled()) {
// Draw white part
g->drawUIText(t,
g->drawUIText(
t,
colors.background(),
gfx::ColorNone,
textrc.origin() + Point(guiscale(), guiscale()));
textrc.origin() + Point(guiscale(), guiscale()),
mnemonic);
}
g->drawUIText(t,
g->drawUIText(
t,
(!widget->isEnabled() ?
colors.disabled():
(gfx::geta(fg_color) > 0 ? fg_color :
colors.text())),
bg_color, textrc.origin());
colors.disabled():
(gfx::geta(fg_color) > 0 ? fg_color :
colors.text())),
bg_color, textrc.origin(),
mnemonic);
}
}
}

View File

@ -135,7 +135,7 @@ namespace app {
gfx::Color getWidgetBgColor(ui::Widget* widget);
void drawText(ui::Graphics* g, const char *t, gfx::Color fg_color, gfx::Color bg_color,
ui::Widget* widget, const gfx::Rect& rc,
int selected_offset);
int selected_offset, int mnemonic);
void drawEntryText(ui::Graphics* g, ui::Entry* widget);
void paintIcon(ui::Widget* widget, ui::Graphics* g, ui::IButtonIcon* iconInterface, int x, int y);

View File

@ -475,8 +475,9 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
// Was the widget created?
if (widget)
if (widget) {
fillWidgetWithXmlElementAttributesWithChildren(elem, root, widget);
}
return widget;
}
@ -587,6 +588,9 @@ void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem,
}
}
}
// Assign widget mnemonic from the character preceded by a '&'
widget->processMnemonicFromText();
}
void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget)

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -158,6 +158,7 @@ void Alert::processString(std::string& buf)
else if (button) {
char buttonId[256];
Button* button_widget = new Button(item);
button_widget->processMnemonicFromText();
button_widget->setMinSize(gfx::Size(60*guiscale(), 0));
m_buttons.push_back(button_widget);

View File

@ -101,7 +101,7 @@ bool ButtonBase::onProcessMessage(Message* msg)
if (isEnabled()) {
bool mnemonicPressed =
((msg->altPressed() || msg->cmdPressed()) &&
mnemonicCharPressed(keymsg));
isMnemonicPressed(keymsg));
// For kButtonWidget
if (m_behaviorType == kButtonWidget) {

View File

@ -237,11 +237,11 @@ namespace {
class DrawUITextDelegate : public she::DrawTextDelegate {
public:
DrawUITextDelegate(she::Surface* surface,
she::Font* font, const bool drawUnderscore)
she::Font* font, const int mnemonic)
: m_surface(surface)
, m_font(font)
, m_drawUnderscore(drawUnderscore)
, m_underscoreNext(false) {
, m_mnemonic(std::tolower(mnemonic))
, m_underscoreColor(gfx::ColorNone) {
}
gfx::Rect bounds() const { return m_bounds; }
@ -253,19 +253,15 @@ public:
gfx::Color& bg,
bool& drawChar,
bool& moveCaret) override {
if (m_underscoreNext)
m_underscoreColor = fg;
if (!m_surface)
drawChar = false;
moveCaret = true;
if (chr == '&') { // TODO change this with other character, maybe '_' or configurable
auto it2 = it;
++it2;
if (it2 != end && *it2 != '&') {
m_underscoreNext = true;
moveCaret = false;
else {
if (m_mnemonic && std::tolower(chr) == m_mnemonic) {
m_underscoreColor = fg;
m_mnemonic = 0; // Just one time
}
else {
m_underscoreColor = gfx::ColorNone;
}
}
}
@ -276,40 +272,36 @@ public:
}
void postDrawChar(const gfx::Rect& charBounds) override {
if (m_underscoreNext) {
m_underscoreNext = false;
if (m_drawUnderscore) {
// TODO underscore height = guiscale() should be configurable from ui::Theme
int dy = 0;
if (m_font->type() == she::FontType::kTrueType) // TODO use other method to locate the underline
dy += guiscale();
gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy,
charBounds.w, guiscale());
m_surface->fillRect(m_underscoreColor, underscoreBounds);
m_bounds |= underscoreBounds;
}
if (!gfx::is_transparent(m_underscoreColor)) {
// TODO underscore height = guiscale() should be configurable from ui::Theme
int dy = 0;
if (m_font->type() == she::FontType::kTrueType) // TODO use other method to locate the underline
dy += guiscale();
gfx::Rect underscoreBounds(charBounds.x, charBounds.y+charBounds.h+dy,
charBounds.w, guiscale());
m_surface->fillRect(m_underscoreColor, underscoreBounds);
m_bounds |= underscoreBounds;
}
}
private:
she::Surface* m_surface;
she::Font* m_font;
bool m_drawUnderscore;
bool m_underscoreNext;
int m_mnemonic;
gfx::Color m_underscoreColor;
gfx::Rect m_bounds;
};
}
void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt,
bool drawUnderscore)
void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
const gfx::Point& pt, const int mnemonic)
{
she::SurfaceLock lock(m_surface);
int x = m_dx+pt.x;
int y = m_dy+pt.y;
DrawUITextDelegate delegate(m_surface, m_font, drawUnderscore);
DrawUITextDelegate delegate(m_surface, m_font, mnemonic);
she::draw_text(m_surface, m_font,
base::utf8_const_iterator(str.begin()),
base::utf8_const_iterator(str.end()),
@ -318,7 +310,8 @@ void Graphics::drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
dirty(delegate.bounds());
}
void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, int align)
void Graphics::drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
const gfx::Rect& rc, const int align)
{
doUIStringAlgorithm(str, fg, bg, rc, align, true);
}
@ -333,7 +326,7 @@ gfx::Size Graphics::measureUIText(const std::string& str)
// static
int Graphics::measureUITextLength(const std::string& str, she::Font* font)
{
DrawUITextDelegate delegate(nullptr, font, false);
DrawUITextDelegate delegate(nullptr, font, 0);
she::draw_text(nullptr, font,
base::utf8_const_iterator(str.begin()),
base::utf8_const_iterator(str.end()),

View File

@ -86,13 +86,9 @@ namespace ui {
const base::utf8_const_iterator& end,
gfx::Color fg, gfx::Color bg, const gfx::Point& pt,
she::DrawTextDelegate* delegate);
void drawText(const std::string& str, gfx::Color fg, gfx::Color bg,
const gfx::Point& pt);
void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
const gfx::Point& pt, bool drawUnderscore = true);
void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg,
const gfx::Rect& rc, int align);
void drawText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt);
void drawUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Point& pt, const int mnemonic);
void drawAlignedUIText(const std::string& str, gfx::Color fg, gfx::Color bg, const gfx::Rect& rc, const int align);
gfx::Size measureUIText(const std::string& str);
static int measureUITextLength(const std::string& str, she::Font* font);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2016 David Capello
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -1218,7 +1218,7 @@ static MenuItem* check_for_letter(Menu* menu, const KeyMessage* keymsg)
continue;
MenuItem* menuitem = static_cast<MenuItem*>(child);
if (menuitem->mnemonicCharPressed(keymsg))
if (menuitem->isMnemonicPressed(keymsg))
return menuitem;
}
return NULL;

View File

@ -152,7 +152,8 @@ void Theme::paintLayer(Graphics* g, Widget* widget,
layer.color(),
gfx::ColorNone,
gfx::Point(rc.x+rc.w/2-textSize.w/2,
rc.y+rc.h/2-textSize.h/2), true);
rc.y+rc.h/2-textSize.h/2),
widget->mnemonic());
}
break;

View File

@ -70,6 +70,7 @@ Widget::Widget(WidgetType type)
, m_bounds(0, 0, 0, 0)
, m_parent(nullptr)
, m_sizeHint(nullptr)
, m_mnemonic(0)
, m_minSize(0, 0)
, m_maxSize(INT_MAX, INT_MAX)
, m_childSpacing(0)
@ -1260,12 +1261,12 @@ bool Widget::offerCapture(ui::MouseMessage* mouseMsg, int widget_type)
return false;
}
bool Widget::hasFocus()
bool Widget::hasFocus() const
{
return hasFlags(HAS_FOCUS);
}
bool Widget::hasMouse()
bool Widget::hasMouse() const
{
return hasFlags(HAS_MOUSE);
}
@ -1275,24 +1276,43 @@ bool Widget::hasMouseOver()
return (this == pick(get_mouse_position()));
}
bool Widget::hasCapture()
bool Widget::hasCapture() const
{
return hasFlags(HAS_CAPTURE);
}
int Widget::mnemonicChar() const
void Widget::setMnemonic(int mnemonic)
{
if (hasText()) {
for (int c=0; m_text[c]; ++c)
if ((m_text[c] == '&') && (m_text[c+1] != '&'))
return std::tolower(m_text[c+1]);
}
return 0;
m_mnemonic = mnemonic;
}
bool Widget::mnemonicCharPressed(const KeyMessage* keyMsg) const
void Widget::processMnemonicFromText(int escapeChar)
{
int chr = mnemonicChar();
std::string newText;
if (!m_text.empty())
newText.reserve(m_text.size());
for (base::utf8_const_iterator
it(m_text.begin()),
end(m_text.end()); it != end; ++it) {
if (*it == escapeChar) {
++it;
if (it == end) {
break; // Ill-formed string (it ends with escape character)
}
else if (*it != escapeChar) {
setMnemonic(*it);
}
}
newText.push_back(*it);
}
setText(newText);
}
bool Widget::isMnemonicPressed(const KeyMessage* keyMsg) const
{
int chr = std::tolower(mnemonic());
return
((chr) &&
((chr == std::tolower(keyMsg->unicodeChar())) ||

View File

@ -339,10 +339,10 @@ namespace ui {
void captureMouse();
void releaseMouse();
bool hasFocus();
bool hasMouse();
bool hasFocus() const;
bool hasMouse() const;
bool hasMouseOver();
bool hasCapture();
bool hasCapture() const;
// Offer the capture to widgets of the given type. Returns true if
// the capture was passed to other widget.
@ -350,10 +350,15 @@ namespace ui {
// Returns lower-case letter that represet the mnemonic of the widget
// (the underscored character, i.e. the letter after & symbol).
int mnemonicChar() const;
int mnemonic() const { return m_mnemonic; }
void setMnemonic(int mnemonic);
// Assigns mnemonic from the character preceded by the given
// escapeChar ('&' by default).
void processMnemonicFromText(int escapeChar = '&');
// Returns true if the mnemonic character is pressed.
bool mnemonicCharPressed(const ui::KeyMessage* keyMsg) const;
bool isMnemonicPressed(const ui::KeyMessage* keyMsg) const;
protected:
// ===============================================================
@ -399,6 +404,7 @@ namespace ui {
WidgetsList m_children; // Sub-widgets
Widget* m_parent; // Who is the parent?
gfx::Size* m_sizeHint;
int m_mnemonic; // Keyboard shortcut to access this widget like Alt+mnemonic
// Widget size limits
gfx::Size m_minSize, m_maxSize;