diff options
author | mukai <mukai@chromium.org> | 2015-03-11 13:30:05 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-11 20:31:27 +0000 |
commit | 1dfc595ffc753b6b1c99cca58e676e8b3e1bf6d8 (patch) | |
tree | 53e997b61ed9e7a79047cc6305b866aa747f67bf | |
parent | d1dcb057ee12b4fe19363944bacbf324dcfb8ff9 (diff) | |
download | chromium_src-1dfc595ffc753b6b1c99cca58e676e8b3e1bf6d8.zip chromium_src-1dfc595ffc753b6b1c99cca58e676e8b3e1bf6d8.tar.gz chromium_src-1dfc595ffc753b6b1c99cca58e676e8b3e1bf6d8.tar.bz2 |
Cache gfx::RenderText instances in views::Label.
This is a rebase of crrev.com/413823002 by msw@.
Cache RenderText instances, avoid Canvas::DrawString*.
(avoids repeating itemization, layout, etc. on each paint)
Recalculate colors and reset the layout as needed.
Large cleanup; remove obsolete flag tests; update tests.
Update App List's CachedLabel views::Label subclass use.
(SchedulePaintInRect triggered paint with color changes)
Ensure SetTitleSubpixelAA is otherwise called as needed.
(skip early return; which breaks folder reorganization)
This will increase the performance of painting
significantly. See https://docs.google.com/document/d/1q4RrBjNO52l1pNTkIZPhQ60aPfBZnV4Af0vdQNr2Juc/edit#
for the detailed analysis.
BUG=240037, 125348, 450791
TEST=no appearance changes with performance improvement as:
On daisy with 15 bookmarks, repainting of bookmark is
decreased as 135msec -> 2.8msec
R=sky@chromium.org
Review URL: https://codereview.chromium.org/867003002
Cr-Commit-Position: refs/heads/master@{#320138}
-rw-r--r-- | ui/app_list/views/app_list_item_view.cc | 16 | ||||
-rw-r--r-- | ui/app_list/views/app_list_item_view.h | 1 | ||||
-rw-r--r-- | ui/gfx/render_text.cc | 15 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 13 | ||||
-rw-r--r-- | ui/gfx/render_text_harfbuzz.cc | 4 | ||||
-rw-r--r-- | ui/gfx/render_text_harfbuzz.h | 1 | ||||
-rw-r--r-- | ui/gfx/render_text_mac.cc | 4 | ||||
-rw-r--r-- | ui/gfx/render_text_mac.h | 1 | ||||
-rw-r--r-- | ui/views/controls/button/label_button.cc | 2 | ||||
-rw-r--r-- | ui/views/controls/label.cc | 589 | ||||
-rw-r--r-- | ui/views/controls/label.h | 123 | ||||
-rw-r--r-- | ui/views/controls/label_unittest.cc | 594 |
12 files changed, 478 insertions, 885 deletions
diff --git a/ui/app_list/views/app_list_item_view.cc b/ui/app_list/views/app_list_item_view.cc index 74b487d..a4107c1 100644 --- a/ui/app_list/views/app_list_item_view.cc +++ b/ui/app_list/views/app_list_item_view.cc @@ -187,6 +187,7 @@ void AppListItemView::SetUIState(UIState state) { } #endif // !OS_WIN + SetTitleSubpixelAA(); SchedulePaint(); } @@ -211,10 +212,7 @@ void AppListItemView::SetTitleSubpixelAA() { !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) && !apps_grid_view_->IsAnimatingView(this); - bool currently_enabled = title_->background() != NULL; - if (currently_enabled == enable_aa) - return; - + title_->SetSubpixelRenderingEnabled(enable_aa); if (enable_aa) { title_->SetBackgroundColor(app_list::kLabelBackgroundColor); title_->set_background(views::Background::CreateSolidBackground( @@ -276,6 +274,7 @@ void AppListItemView::SetItemName(const base::string16& display_name, void AppListItemView::SetItemIsHighlighted(bool is_highlighted) { is_highlighted_ = is_highlighted; + SetTitleSubpixelAA(); SchedulePaint(); } @@ -285,6 +284,7 @@ void AppListItemView::SetItemIsInstalling(bool is_installing) { title_->SetVisible(!is_installing); progress_bar_->SetVisible(is_installing); } + SetTitleSubpixelAA(); SchedulePaint(); } @@ -331,6 +331,7 @@ void AppListItemView::Layout() { title_size.height()); title_bounds.Intersect(rect); title_->SetBoundsRect(title_bounds); + SetTitleSubpixelAA(); gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize()); progress_bar_bounds.set_x(GetContentsBounds().x() + @@ -339,11 +340,6 @@ void AppListItemView::Layout() { progress_bar_->SetBoundsRect(progress_bar_bounds); } -void AppListItemView::SchedulePaintInRect(const gfx::Rect& r) { - SetTitleSubpixelAA(); - views::CustomButton::SchedulePaintInRect(r); -} - void AppListItemView::OnPaint(gfx::Canvas* canvas) { if (apps_grid_view_->IsDraggedView(this)) return; @@ -405,7 +401,7 @@ void AppListItemView::StateChanged() { item_weak_->set_highlighted(false); title_->SetEnabledColor(kGridTitleColor); } - title_->Invalidate(); + SetTitleSubpixelAA(); } bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) { diff --git a/ui/app_list/views/app_list_item_view.h b/ui/app_list/views/app_list_item_view.h index f7cba65..d5a6516 100644 --- a/ui/app_list/views/app_list_item_view.h +++ b/ui/app_list/views/app_list_item_view.h @@ -116,7 +116,6 @@ class APP_LIST_EXPORT AppListItemView : public views::CustomButton, // views::View overrides: const char* GetClassName() const override; void Layout() override; - void SchedulePaintInRect(const gfx::Rect& r) override; void OnPaint(gfx::Canvas* canvas) override; // views::ContextMenuController overrides: diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 2f10216..c9fe334 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -424,6 +424,7 @@ void ApplyRenderParams(const FontRenderParams& params, RenderText::~RenderText() { } +// static RenderText* RenderText::CreateInstance() { #if defined(OS_MACOSX) static const bool use_native = @@ -435,6 +436,7 @@ RenderText* RenderText::CreateInstance() { return new RenderTextHarfBuzz; } +// static RenderText* RenderText::CreateInstanceForEditing() { return new RenderTextHarfBuzz; } @@ -527,6 +529,14 @@ void RenderText::SetMultiline(bool multiline) { } } +void RenderText::SetReplaceNewlineCharsWithSymbols(bool replace) { + if (replace_newline_chars_with_symbols_ == replace) + return; + replace_newline_chars_with_symbols_ = replace; + cached_bounds_and_offset_valid_ = false; + OnTextAttributeChanged(); +} + void RenderText::SetMinLineHeight(int line_height) { if (min_line_height_ == line_height) return; @@ -952,6 +962,7 @@ RenderText::RenderText() text_elided_(false), min_line_height_(0), multiline_(false), + replace_newline_chars_with_symbols_(true), subpixel_rendering_suppressed_(false), clip_to_display_rect_(true), baseline_(kInvalidBaseline), @@ -1126,7 +1137,7 @@ HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() { Vector2d RenderText::GetAlignmentOffset(size_t line_number) { // TODO(ckocagil): Enable |lines_| usage on RenderTextMac. - if (multiline_) + if (MultilineSupported() && multiline_) DCHECK_LT(line_number, lines_.size()); Vector2d offset; HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment(); @@ -1301,7 +1312,7 @@ void RenderText::OnTextAttributeChanged() { } static const base::char16 kNewline[] = { '\n', 0 }; static const base::char16 kNewlineSymbol[] = { 0x2424, 0 }; - if (!multiline_) + if (!multiline_ && replace_newline_chars_with_symbols_) base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_); OnLayoutTextAttributeChanged(true); diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index 4abb7c8..3bee634 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -260,6 +260,12 @@ class GFX_EXPORT RenderText { bool multiline() const { return multiline_; } void SetMultiline(bool multiline); + // Set whether newline characters should be replaced with newline symbols. + void SetReplaceNewlineCharsWithSymbols(bool replace); + + // Returns true if this instance supports multiline rendering. + virtual bool MultilineSupported() const = 0; + // TODO(ckocagil): Add vertical alignment and line spacing support instead. int min_line_height() const { return min_line_height_; } void SetMinLineHeight(int line_height); @@ -270,7 +276,7 @@ class GFX_EXPORT RenderText { // WARNING: Only use this for system limits, it lacks complex text support. void set_truncate_length(size_t length) { truncate_length_ = length; } - // The layout text will be elided to fit |display_rect| using this behavior. + // The display text will be elided to fit |display_rect| using this behavior. void SetElideBehavior(ElideBehavior elide_behavior); ElideBehavior elide_behavior() const { return elide_behavior_; } @@ -437,7 +443,7 @@ class GFX_EXPORT RenderText { // Sets shadows to drawn with text. void set_shadows(const ShadowValues& shadows) { shadows_ = shadows; } - const ShadowValues& shadows() { return shadows_; } + const ShadowValues& shadows() const { return shadows_; } typedef std::pair<Font, Range> FontSpan; // For testing purposes, returns which fonts were chosen for which parts of @@ -751,6 +757,9 @@ class GFX_EXPORT RenderText { // |display_rect_| as the width cap. bool multiline_; + // Whether newline characters should be replaced with newline symbols. + bool replace_newline_chars_with_symbols_; + // Set to true to suppress subpixel rendering due to non-font reasons (eg. // if the background is transparent). The default value is false. bool subpixel_rendering_suppressed_; diff --git a/ui/gfx/render_text_harfbuzz.cc b/ui/gfx/render_text_harfbuzz.cc index 916cdf3..a2610f3 100644 --- a/ui/gfx/render_text_harfbuzz.cc +++ b/ui/gfx/render_text_harfbuzz.cc @@ -639,6 +639,10 @@ scoped_ptr<RenderText> RenderTextHarfBuzz::CreateInstanceOfSameType() const { return make_scoped_ptr(new RenderTextHarfBuzz); } +bool RenderTextHarfBuzz::MultilineSupported() const { + return true; +} + const base::string16& RenderTextHarfBuzz::GetDisplayText() { // TODO(oshima): Consider supporting eliding multi-line text. // This requires max_line support first. diff --git a/ui/gfx/render_text_harfbuzz.h b/ui/gfx/render_text_harfbuzz.h index 6d78dfa..b247077 100644 --- a/ui/gfx/render_text_harfbuzz.h +++ b/ui/gfx/render_text_harfbuzz.h @@ -137,6 +137,7 @@ class GFX_EXPORT RenderTextHarfBuzz : public RenderText { // RenderText: scoped_ptr<RenderText> CreateInstanceOfSameType() const override; + bool MultilineSupported() const override; const base::string16& GetDisplayText() override; Size GetStringSize() override; SizeF GetStringSizeF() override; diff --git a/ui/gfx/render_text_mac.cc b/ui/gfx/render_text_mac.cc index 3c60460..1c6e78d 100644 --- a/ui/gfx/render_text_mac.cc +++ b/ui/gfx/render_text_mac.cc @@ -28,6 +28,10 @@ scoped_ptr<RenderText> RenderTextMac::CreateInstanceOfSameType() const { return make_scoped_ptr(new RenderTextMac); } +bool RenderTextMac::MultilineSupported() const { + return false; +} + const base::string16& RenderTextMac::GetDisplayText() { return text_elided() ? display_text() : layout_text(); } diff --git a/ui/gfx/render_text_mac.h b/ui/gfx/render_text_mac.h index ee2a665..5d3d00d 100644 --- a/ui/gfx/render_text_mac.h +++ b/ui/gfx/render_text_mac.h @@ -28,6 +28,7 @@ class GFX_EXPORT RenderTextMac : public RenderText { // RenderText: scoped_ptr<RenderText> CreateInstanceOfSameType() const override; + bool MultilineSupported() const override; const base::string16& GetDisplayText() override; Size GetStringSize() override; SizeF GetStringSizeF() override; diff --git a/ui/views/controls/button/label_button.cc b/ui/views/controls/button/label_button.cc index 860df6d..ee72e83 100644 --- a/ui/views/controls/button/label_button.cc +++ b/ui/views/controls/button/label_button.cc @@ -156,7 +156,7 @@ void LabelButton::SetElideBehavior(gfx::ElideBehavior elide_behavior) { } gfx::HorizontalAlignment LabelButton::GetHorizontalAlignment() const { - return label_->GetHorizontalAlignment(); + return label_->horizontal_alignment(); } void LabelButton::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc index 998c09a..911a4fd 100644 --- a/ui/views/controls/label.cc +++ b/ui/views/controls/label.cc @@ -13,24 +13,13 @@ #include "base/logging.h" #include "base/profiler/scoped_tracker.h" #include "base/strings/string_split.h" -#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_view_state.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/text_elider.h" -#include "ui/gfx/text_utils.h" -#include "ui/gfx/utf16_indexing.h" #include "ui/native_theme/native_theme.h" -#include "ui/views/background.h" - -namespace { - -const int kCachedSizeLimit = 10; -const base::char16 kPasswordReplacementChar = '*'; - -} // namespace namespace views { @@ -55,41 +44,29 @@ Label::~Label() { void Label::SetFontList(const gfx::FontList& font_list) { is_first_paint_text_ = true; - font_list_ = font_list; - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); -} - -void Label::SetText(const base::string16& text) { - if (text != text_) - SetTextInternal(text); + render_text_->SetFontList(font_list); + ResetLayout(); } -void Label::SetTextInternal(const base::string16& text) { +void Label::SetText(const base::string16& new_text) { + if (new_text == text()) + return; is_first_paint_text_ = true; - text_ = text; - - if (obscured_) { - size_t obscured_text_length = - static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, text_.length())); - layout_text_.assign(obscured_text_length, kPasswordReplacementChar); - } else { - layout_text_ = text_; - } - - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); + render_text_->SetText(new_text); + ResetLayout(); } void Label::SetAutoColorReadabilityEnabled(bool enabled) { + if (auto_color_readability_ == enabled) + return; is_first_paint_text_ = true; auto_color_readability_ = enabled; RecalculateColors(); } void Label::SetEnabledColor(SkColor color) { + if (enabled_color_set_ && requested_enabled_color_ == color) + return; is_first_paint_text_ = true; requested_enabled_color_ = color; enabled_color_set_ = true; @@ -97,6 +74,8 @@ void Label::SetEnabledColor(SkColor color) { } void Label::SetDisabledColor(SkColor color) { + if (disabled_color_set_ && requested_disabled_color_ == color) + return; is_first_paint_text_ = true; requested_disabled_color_ = color; disabled_color_set_ = true; @@ -104,97 +83,88 @@ void Label::SetDisabledColor(SkColor color) { } void Label::SetBackgroundColor(SkColor color) { + if (background_color_set_ && background_color_ == color) + return; is_first_paint_text_ = true; background_color_ = color; background_color_set_ = true; RecalculateColors(); - cached_draw_params_.text.clear(); } void Label::SetShadows(const gfx::ShadowValues& shadows) { + // TODO(mukai): early exit if the specified shadows are same. is_first_paint_text_ = true; - shadows_ = shadows; - ResetLayoutCache(); + render_text_->set_shadows(shadows); + ResetLayout(); } void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) { + if (subpixel_rendering_enabled_ == subpixel_rendering_enabled) + return; is_first_paint_text_ = true; subpixel_rendering_enabled_ = subpixel_rendering_enabled; + RecalculateColors(); } void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { - is_first_paint_text_ = true; // If the UI layout is right-to-left, flip the alignment direction. if (base::i18n::IsRTL() && (alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) { alignment = (alignment == gfx::ALIGN_LEFT) ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; } - if (horizontal_alignment_ != alignment) { - horizontal_alignment_ = alignment; - SchedulePaint(); - } -} - -gfx::HorizontalAlignment Label::GetHorizontalAlignment() const { - if (horizontal_alignment_ != gfx::ALIGN_TO_HEAD) - return horizontal_alignment_; - - const base::i18n::TextDirection dir = - base::i18n::GetFirstStrongCharacterDirection(layout_text_); - return dir == base::i18n::RIGHT_TO_LEFT ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; + if (horizontal_alignment() == alignment) + return; + is_first_paint_text_ = true; + render_text_->SetHorizontalAlignment(alignment); + ResetLayout(); } void Label::SetLineHeight(int height) { + if (line_height() == height) + return; is_first_paint_text_ = true; - if (height != line_height_) { - line_height_ = height; - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); - } + render_text_->SetMinLineHeight(height); + ResetLayout(); } void Label::SetMultiLine(bool multi_line) { - is_first_paint_text_ = true; DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL || elide_behavior_ == gfx::NO_ELIDE)); - if (multi_line != multi_line_) { - multi_line_ = multi_line; - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); - } + if (this->multi_line() == multi_line) + return; + is_first_paint_text_ = true; + multi_line_ = multi_line; + if (render_text_->MultilineSupported()) + render_text_->SetMultiline(multi_line); + render_text_->SetReplaceNewlineCharsWithSymbols(!multi_line); + ResetLayout(); } void Label::SetObscured(bool obscured) { + if (this->obscured() == obscured) + return; is_first_paint_text_ = true; - if (obscured != obscured_) { - obscured_ = obscured; - SetTextInternal(text_); - } + render_text_->SetObscured(obscured); + ResetLayout(); } void Label::SetAllowCharacterBreak(bool allow_character_break) { + if (allow_character_break_ == allow_character_break) + return; is_first_paint_text_ = true; - if (allow_character_break != allow_character_break_) { - allow_character_break_ = allow_character_break; - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); - } + allow_character_break_ = allow_character_break; + ResetLayout(); } void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) { + DCHECK(!multi_line() || (elide_behavior_ == gfx::ELIDE_TAIL || + elide_behavior_ == gfx::NO_ELIDE)); + if (elide_behavior_ == elide_behavior) + return; is_first_paint_text_ = true; - DCHECK(!multi_line_ || (elide_behavior_ == gfx::ELIDE_TAIL || - elide_behavior_ == gfx::NO_ELIDE)); - if (elide_behavior != elide_behavior_) { - elide_behavior_ = elide_behavior; - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); - } + elide_behavior_ = elide_behavior; + ResetLayout(); } void Label::SetTooltipText(const base::string16& tooltip_text) { @@ -207,28 +177,23 @@ void Label::SetHandlesTooltips(bool enabled) { } void Label::SizeToFit(int max_width) { - DCHECK(multi_line_); - - std::vector<base::string16> lines; - base::SplitString(layout_text_, '\n', &lines); - - int label_width = 0; - for (std::vector<base::string16>::const_iterator iter = lines.begin(); - iter != lines.end(); ++iter) { - label_width = std::max(label_width, gfx::GetStringWidth(*iter, font_list_)); - } - - label_width += GetInsets().width(); - - if (max_width > 0) - label_width = std::min(label_width, max_width); - - SetBounds(x(), y(), label_width, 0); + DCHECK(multi_line()); + max_width_ = max_width; SizeToPreferredSize(); } -const base::string16& Label::GetLayoutTextForTesting() const { - return layout_text_; +base::string16 Label::GetDisplayTextForTesting() { + lines_.clear(); + MaybeBuildRenderTextLines(); + base::string16 result; + if (lines_.empty()) + return result; + result.append(lines_[0]->GetDisplayText()); + for (size_t i = 1; i < lines_.size(); ++i) { + result.append(1, '\n'); + result.append(lines_[i]->GetDisplayText()); + } + return result; } gfx::Insets Label::GetInsets() const { @@ -241,7 +206,7 @@ gfx::Insets Label::GetInsets() const { } int Label::GetBaseline() const { - return GetInsets().top() + font_list_.GetBaseline(); + return GetInsets().top() + font_list().GetBaseline(); } gfx::Size Label::GetPreferredSize() const { @@ -250,56 +215,70 @@ gfx::Size Label::GetPreferredSize() const { FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::GetPreferredSize")); // Return a size of (0, 0) if the label is not visible and if the - // collapse_when_hidden_ flag is set. + // |collapse_when_hidden_| flag is set. // TODO(munjal): This logic probably belongs to the View class. But for now, // put it here since putting it in View class means all inheriting classes - // need ot respect the collapse_when_hidden_ flag. + // need to respect the |collapse_when_hidden_| flag. if (!visible() && collapse_when_hidden_) return gfx::Size(); + if (multi_line() && max_width_ != 0 && !text().empty()) + return gfx::Size(max_width_, GetHeightForWidth(max_width_)); + gfx::Size size(GetTextSize()); - gfx::Insets insets = GetInsets(); + const gfx::Insets insets = GetInsets(); size.Enlarge(insets.width(), insets.height()); return size; } gfx::Size Label::GetMinimumSize() const { - gfx::Size text_size(GetTextSize()); - if ((!visible() && collapse_when_hidden_) || text_size.IsEmpty()) + if (!visible() && collapse_when_hidden_) return gfx::Size(); - gfx::Size size(gfx::GetStringWidth(base::string16(gfx::kEllipsisUTF16), - font_list_), - font_list_.GetHeight()); - size.SetToMin(text_size); // The actual text may be shorter than an ellipsis. - gfx::Insets insets = GetInsets(); - size.Enlarge(insets.width(), insets.height()); + gfx::Size size(0, font_list().GetHeight()); + if (elide_behavior_ == gfx::ELIDE_HEAD || + elide_behavior_ == gfx::ELIDE_MIDDLE || + elide_behavior_ == gfx::ELIDE_TAIL || + elide_behavior_ == gfx::ELIDE_EMAIL) { + size.set_width(gfx::Canvas::GetStringWidth( + base::string16(gfx::kEllipsisUTF16), font_list())); + } + if (!multi_line()) + size.SetToMin(GetTextSize()); + size.Enlarge(GetInsets().width(), GetInsets().height()); return size; } int Label::GetHeightForWidth(int w) const { - if (!multi_line_) - return View::GetHeightForWidth(w); - - w = std::max(0, w - GetInsets().width()); - - for (size_t i = 0; i < cached_heights_.size(); ++i) { - const gfx::Size& s = cached_heights_[i]; - if (s.width() == w) - return s.height() + GetInsets().height(); + if (!visible() && collapse_when_hidden_) + return 0; + + w -= GetInsets().width(); + if (!multi_line() || text().empty() || w <= 0) + return std::max(line_height(), font_list().GetHeight()); + + int height = 0; + // RenderText doesn't support character breaks. + // TODO(mukai): remove this restriction. + if (render_text_->MultilineSupported() && !allow_character_break_) { + // SetDisplayRect() has a side effect for later calls of GetStringSize(). + // Be careful to invoke |render_text_->SetDisplayRect(gfx::Rect())| to + // cancel this effect before the next time GetStringSize() is called. + // It would be beneficial not to cancel here, considering that some layout + // managers invoke GetHeightForWidth() for the same width multiple times + // and |render_text_| can cache the height. + render_text_->SetDisplayRect(gfx::Rect(0, 0, w, 0)); + height = render_text_->GetStringSize().height(); + } else { + std::vector<base::string16> lines = GetLinesForWidth(w); + height = lines.size() * std::max(line_height(), font_list().GetHeight()); } + height -= gfx::ShadowValue::GetMargin(render_text_->shadows()).height(); + return height + GetInsets().height(); +} - int cache_width = w; - - int h = font_list_.GetHeight(); - // Flags returned in the cached |DrawStringParams| has a different value - // from the result of |ComputeDrawStringFlags()|. The latter is needed here. - const int flags = ComputeDrawStringFlags(); - gfx::Canvas::SizeStringInt( - layout_text_, font_list_, &w, &h, line_height_, flags); - cached_heights_[cached_heights_cursor_] = gfx::Size(cache_width, h); - cached_heights_cursor_ = (cached_heights_cursor_ + 1) % kCachedSizeLimit; - return h + GetInsets().height(); +void Label::Layout() { + lines_.clear(); } const char* Label::GetClassName() const { @@ -322,7 +301,8 @@ bool Label::CanProcessEventsWithinSubtree() const { void Label::GetAccessibleState(ui::AXViewState* state) { state->role = ui::AX_ROLE_STATIC_TEXT; state->AddStateFlag(ui::AX_STATE_READ_ONLY); - state->name = layout_text_; + // Note that |render_text_| is never elided (see the comment in Init() too). + state->name = render_text_->GetDisplayText(); } bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { @@ -335,99 +315,49 @@ bool Label::GetTooltipText(const gfx::Point& p, base::string16* tooltip) const { } if (ShouldShowDefaultTooltip()) { - *tooltip = layout_text_; + // Note that |render_text_| is never elided (see the comment in Init() too). + tooltip->assign(render_text_->GetDisplayText()); return true; } return false; } -void Label::PaintText(gfx::Canvas* canvas, - const base::string16& text, - const gfx::Rect& text_bounds, - int flags) { - SkColor color = enabled() ? actual_enabled_color_ : actual_disabled_color_; - if (elide_behavior_ == gfx::FADE_TAIL && - text_bounds.width() < GetTextSize().width()) { - canvas->DrawFadedString(text, font_list_, color, text_bounds, flags); - } else { - canvas->DrawStringRectWithShadows(text, font_list_, color, text_bounds, - line_height_, flags, shadows_); - } - - if (HasFocus()) { - gfx::Rect focus_bounds = text_bounds; - focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding); - canvas->DrawFocusRect(focus_bounds); - } +void Label::OnEnabledChanged() { + RecalculateColors(); } -gfx::Size Label::GetTextSize() const { - if (!text_size_valid_) { - // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed. - tracked_objects::ScopedTracker tracking_profile1( - FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::GetTextSize1")); - - // For single-line strings, we supply the largest possible width, because - // while adding NO_ELLIPSIS to the flags works on Windows for forcing - // SizeStringInt() to calculate the desired width, it doesn't seem to work - // on Linux. - int w = multi_line_ ? - GetAvailableRect().width() : std::numeric_limits<int>::max(); - int h = font_list_.GetHeight(); - // For single-line strings, ignore the available width and calculate how - // wide the text wants to be. - // Call |ComputeDrawStringFlags()| instead of |CalculateDrawStringParams()| - // here since the latter calls this function and causes infinite recursion. - int flags = ComputeDrawStringFlags(); - if (!multi_line_) - flags |= gfx::Canvas::NO_ELLIPSIS; - { - // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is - // fixed. - tracked_objects::ScopedTracker tracking_profile2( - FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::GetTextSize2")); - - gfx::Canvas::SizeStringInt(layout_text_, font_list_, &w, &h, line_height_, - flags); - } - text_size_.SetSize(w, h); - const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows_); - text_size_.Enlarge(shadow_margin.width(), shadow_margin.height()); - text_size_valid_ = true; - } - - return text_size_; +void Label::PaintText(gfx::Canvas* canvas) { + MaybeBuildRenderTextLines(); + for (size_t i = 0; i < lines_.size(); ++i) + lines_[i]->Draw(canvas); } void Label::OnBoundsChanged(const gfx::Rect& previous_bounds) { - text_size_valid_ &= !multi_line_; - cached_draw_params_.text.clear(); + if (previous_bounds.size() != size()) + InvalidateLayout(); } void Label::OnPaint(gfx::Canvas* canvas) { - OnPaintBackground(canvas); - // We skip painting the focus border because it is being handled seperately by - // some subclasses of Label. We do not want View's focus border painting to - // interfere with that. - OnPaintBorder(canvas); - if (layout_text_.empty()) - return; - - const DrawStringParams* params = CalculateDrawStringParams(); + View::OnPaint(canvas); if (is_first_paint_text_) { // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed. tracked_objects::ScopedTracker tracking_profile( FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::PaintText first")); is_first_paint_text_ = false; - PaintText(canvas, params->text, params->bounds, params->flags); + PaintText(canvas); } else { // TODO(vadimt): Remove ScopedTracker below once crbug.com/431326 is fixed. tracked_objects::ScopedTracker tracking_profile( FROM_HERE_WITH_EXPLICIT_FUNCTION("431326 Label::PaintText not first")); - PaintText(canvas, params->text, params->bounds, params->flags); + PaintText(canvas); + } + if (HasFocus()) { + gfx::Rect focus_bounds = GetLocalBounds(); + focus_bounds.Inset(-kFocusBorderPadding, -kFocusBorderPadding); + canvas->DrawFocusRect(focus_bounds); } } @@ -440,32 +370,152 @@ void Label::OnDeviceScaleFactorChanged(float device_scale_factor) { // When the device scale factor is changed, some font rendering parameters is // changed (especially, hinting). The bounding box of the text has to be // re-computed based on the new parameters. See crbug.com/441439 - ResetLayoutCache(); - PreferredSizeChanged(); - SchedulePaint(); + ResetLayout(); +} + +void Label::VisibilityChanged(View* starting_from, bool is_visible) { + if (!is_visible) + lines_.clear(); } void Label::Init(const base::string16& text, const gfx::FontList& font_list) { - font_list_ = font_list; + render_text_.reset(gfx::RenderText::CreateInstance()); + render_text_->SetHorizontalAlignment(gfx::ALIGN_CENTER); + render_text_->SetDirectionalityMode(gfx::DIRECTIONALITY_FROM_TEXT); + // NOTE: |render_text_| should not be elided at all. This is used to keep some + // properties and to compute the size of the string. + render_text_->SetElideBehavior(gfx::NO_ELIDE); + render_text_->SetFontList(font_list); + render_text_->SetCursorEnabled(false); + + elide_behavior_ = gfx::ELIDE_TAIL; enabled_color_set_ = disabled_color_set_ = background_color_set_ = false; subpixel_rendering_enabled_ = true; auto_color_readability_ = true; - UpdateColorsFromTheme(ui::NativeTheme::instance()); - horizontal_alignment_ = gfx::ALIGN_CENTER; - line_height_ = 0; multi_line_ = false; - obscured_ = false; - allow_character_break_ = false; - elide_behavior_ = gfx::ELIDE_TAIL; + UpdateColorsFromTheme(ui::NativeTheme::instance()); handles_tooltips_ = true; collapse_when_hidden_ = false; - cached_heights_.resize(kCachedSizeLimit); - ResetLayoutCache(); + allow_character_break_ = false; + max_width_ = 0; is_first_paint_text_ = true; - SetText(text); } +void Label::ResetLayout() { + InvalidateLayout(); + PreferredSizeChanged(); + SchedulePaint(); +} + +scoped_ptr<gfx::RenderText> Label::CreateRenderText( + const base::string16& text, + gfx::HorizontalAlignment alignment, + gfx::DirectionalityMode directionality, + gfx::ElideBehavior elide_behavior) { + scoped_ptr<gfx::RenderText> render_text( + render_text_->CreateInstanceOfSameType()); + render_text->SetHorizontalAlignment(alignment); + render_text->SetDirectionalityMode(directionality); + render_text->SetElideBehavior(elide_behavior); + render_text->SetObscured(obscured()); + render_text->SetMinLineHeight(line_height()); + render_text->SetFontList(font_list()); + render_text->set_shadows(shadows()); + render_text->SetCursorEnabled(false); + render_text->SetText(text); + return render_text.Pass(); +} + +void Label::MaybeBuildRenderTextLines() { + if (!lines_.empty()) + return; + + gfx::Rect rect = GetContentsBounds(); + if (rect.IsEmpty()) + return; + + gfx::HorizontalAlignment alignment = horizontal_alignment(); + gfx::DirectionalityMode directionality = render_text_->directionality_mode(); + if (multi_line()) { + // Force the directionality and alignment of the first line on other lines. + bool rtl = + render_text_->GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT; + if (alignment == gfx::ALIGN_TO_HEAD) + alignment = rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT; + directionality = + rtl ? gfx::DIRECTIONALITY_FORCE_RTL : gfx::DIRECTIONALITY_FORCE_LTR; + } + + // Text eliding is not supported for multi-lined Labels. + // TODO(mukai): Add multi-lined elided text support. + gfx::ElideBehavior elide_behavior = + multi_line() ? gfx::NO_ELIDE : elide_behavior_; + if (!multi_line() || + (render_text_->MultilineSupported() && !allow_character_break_)) { + scoped_ptr<gfx::RenderText> render_text = + CreateRenderText(text(), alignment, directionality, elide_behavior); + render_text->SetDisplayRect(rect); + render_text->SetMultiline(multi_line()); + lines_.push_back(render_text.release()); + } else { + std::vector<base::string16> lines = GetLinesForWidth(rect.width()); + if (lines.size() > 1) + rect.set_height(std::max(line_height(), font_list().GetHeight())); + + const int bottom = GetContentsBounds().bottom(); + for (size_t i = 0; i < lines.size() && rect.y() <= bottom; ++i) { + scoped_ptr<gfx::RenderText> line = + CreateRenderText(lines[i], alignment, directionality, elide_behavior); + line->SetDisplayRect(rect); + lines_.push_back(line.release()); + rect.set_y(rect.y() + rect.height()); + } + // Append the remaining text to the last visible line. + for (size_t i = lines_.size(); i < lines.size(); ++i) + lines_.back()->SetText(lines_.back()->text() + lines[i]); + } + RecalculateColors(); +} + +std::vector<base::string16> Label::GetLinesForWidth(int width) const { + std::vector<base::string16> lines; + const gfx::WordWrapBehavior wrap = + allow_character_break_ ? gfx::WRAP_LONG_WORDS : gfx::TRUNCATE_LONG_WORDS; + gfx::ElideRectangleText(render_text_->GetDisplayText(), font_list(), width, + std::numeric_limits<int>::max(), wrap, &lines); + return lines; +} + +gfx::Size Label::GetTextSize() const { + gfx::Size size; + if (text().empty()) { + size = gfx::Size(0, std::max(line_height(), font_list().GetHeight())); + } else if (!multi_line() || + (render_text_->MultilineSupported() && !allow_character_break_)) { + // Cancel the display rect of |render_text_|. The display rect may be + // specified in GetHeightForWidth(), and specifying empty Rect cancels + // its effect. See also the comment in GetHeightForWidth(). + render_text_->SetDisplayRect(gfx::Rect()); + size = render_text_->GetStringSize(); + } else { + // Get the natural text size, unelided and only wrapped on newlines. + std::vector<base::string16> lines; + base::SplitString(render_text_->GetDisplayText(), '\n', &lines); + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance()); + render_text->SetFontList(font_list()); + for (size_t i = 0; i < lines.size(); ++i) { + render_text->SetText(lines[i]); + const gfx::Size line = render_text->GetStringSize(); + size.set_width(std::max(size.width(), line.width())); + size.set_height(std::max(line_height(), size.height() + line.height())); + } + } + const gfx::Insets shadow_margin = -gfx::ShadowValue::GetMargin(shadows()); + size.Enlarge(shadow_margin.width(), shadow_margin.height()); + return size; +} + void Label::RecalculateColors() { actual_enabled_color_ = auto_color_readability_ ? color_utils::GetReadableColor(requested_enabled_color_, @@ -475,100 +525,15 @@ void Label::RecalculateColors() { color_utils::GetReadableColor(requested_disabled_color_, background_color_) : requested_disabled_color_; -} - -gfx::Rect Label::GetTextBounds() const { - gfx::Rect available(GetAvailableRect()); - gfx::Size text_size(GetTextSize()); - text_size.set_width(std::min(available.width(), text_size.width())); - gfx::Point origin(GetInsets().left(), GetInsets().top()); - switch (GetHorizontalAlignment()) { - case gfx::ALIGN_LEFT: - break; - case gfx::ALIGN_CENTER: - // Put any extra margin pixel on the left to match the legacy behavior - // from the use of GetTextExtentPoint32() on Windows. - origin.Offset((available.width() + 1 - text_size.width()) / 2, 0); - break; - case gfx::ALIGN_RIGHT: - origin.set_x(available.right() - text_size.width()); - break; - default: - NOTREACHED(); - break; - } - if (!multi_line_) - text_size.set_height(available.height()); - // Support vertical centering of multi-line labels: http://crbug.com/429595 - origin.Offset(0, std::max(0, (available.height() - text_size.height())) / 2); - return gfx::Rect(origin, text_size); -} - -int Label::ComputeDrawStringFlags() const { - int flags = 0; - - // We can't use subpixel rendering if the background is non-opaque. - if (SkColorGetA(background_color_) != 0xFF || !subpixel_rendering_enabled_) - flags |= gfx::Canvas::NO_SUBPIXEL_RENDERING; - - switch (GetHorizontalAlignment()) { - case gfx::ALIGN_LEFT: - flags |= gfx::Canvas::TEXT_ALIGN_LEFT; - break; - case gfx::ALIGN_CENTER: - flags |= gfx::Canvas::TEXT_ALIGN_CENTER; - break; - case gfx::ALIGN_RIGHT: - flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; - break; - default: - NOTREACHED(); - break; - } - if (!multi_line_) - return flags; - - flags |= gfx::Canvas::MULTI_LINE; -#if !defined(OS_WIN) - // Don't elide multiline labels on Linux. - // Todo(davemoore): Do we depend on eliding multiline text? - // Pango insists on limiting the number of lines to one if text is - // elided. You can get around this if you can pass a maximum height - // but we don't currently have that data when we call the pango code. - flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - if (allow_character_break_) - flags |= gfx::Canvas::CHARACTER_BREAK; - - return flags; -} - -gfx::Rect Label::GetAvailableRect() const { - gfx::Rect bounds(size()); - bounds.Inset(GetInsets()); - return bounds; -} - -const Label::DrawStringParams* Label::CalculateDrawStringParams() const { - if (cached_draw_params_.text.empty()) { - const bool forbid_ellipsis = elide_behavior_ == gfx::NO_ELIDE || - elide_behavior_ == gfx::FADE_TAIL; - if (multi_line_ || forbid_ellipsis) { - cached_draw_params_.text = layout_text_; - } else { - cached_draw_params_.text = gfx::ElideText(layout_text_, font_list_, - GetAvailableRect().width(), elide_behavior_); - } - - cached_draw_params_.bounds = GetTextBounds(); - cached_draw_params_.flags = ComputeDrawStringFlags(); - // TODO(msw): Elide multi-line text with ElideRectangleText instead. - if (!multi_line_ || forbid_ellipsis) - cached_draw_params_.flags |= gfx::Canvas::NO_ELLIPSIS; + SkColor color = enabled() ? actual_enabled_color_ : actual_disabled_color_; + bool subpixel_rendering_suppressed = + SkColorGetA(background_color_) != 0xFF || !subpixel_rendering_enabled_; + for (size_t i = 0; i < lines_.size(); ++i) { + lines_[i]->SetColor(color); + lines_[i]->set_subpixel_rendering_suppressed(subpixel_rendering_suppressed); } - - return &cached_draw_params_; + SchedulePaint(); } void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) { @@ -587,19 +552,11 @@ void Label::UpdateColorsFromTheme(const ui::NativeTheme* theme) { RecalculateColors(); } -void Label::ResetLayoutCache() { - cached_draw_params_.text.clear(); - text_size_valid_ = false; - cached_heights_cursor_ = 0; - for (int i = 0; i < kCachedSizeLimit; ++i) - cached_heights_[i] = gfx::Size(); -} - bool Label::ShouldShowDefaultTooltip() const { const gfx::Size text_size = GetTextSize(); const gfx::Size size = GetContentsBounds().size(); return !obscured() && (text_size.width() > size.width() || - (multi_line_ && text_size.height() > size.height())); + (multi_line() && text_size.height() > size.height())); } } // namespace views diff --git a/ui/views/controls/label.h b/ui/views/controls/label.h index 603323a..617ff78 100644 --- a/ui/views/controls/label.h +++ b/ui/views/controls/label.h @@ -5,16 +5,10 @@ #ifndef UI_VIEWS_CONTROLS_LABEL_H_ #define UI_VIEWS_CONTROLS_LABEL_H_ -#include <string> -#include <vector> - #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" -#include "base/strings/string16.h" -#include "third_party/skia/include/core/SkColor.h" -#include "ui/gfx/font_list.h" -#include "ui/gfx/shadow_value.h" -#include "ui/gfx/text_constants.h" +#include "base/memory/scoped_vector.h" +#include "ui/gfx/render_text.h" #include "ui/views/view.h" namespace views { @@ -34,11 +28,12 @@ class VIEWS_EXPORT Label : public View { ~Label() override; // Gets or sets the fonts used by this label. - const gfx::FontList& font_list() const { return font_list_; } + const gfx::FontList& font_list() const { return render_text_->font_list(); } + virtual void SetFontList(const gfx::FontList& font_list); // Get or set the label text. - const base::string16& text() const { return text_; } + const base::string16& text() const { return render_text_->text(); } virtual void SetText(const base::string16& text); // Enables or disables auto-color-readability (enabled by default). If this @@ -61,20 +56,24 @@ class VIEWS_EXPORT Label : public View { // Set drop shadows underneath the text. void SetShadows(const gfx::ShadowValues& shadows); - const gfx::ShadowValues& shadows() const { return shadows_; } + const gfx::ShadowValues& shadows() const { return render_text_->shadows(); } // Sets whether subpixel rendering is used; the default is true, but this // feature also requires an opaque background color. + // TODO(mukai): rename this as SetSubpixelRenderingSuppressed() to keep the + // consistency with RenderText field name. void SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled); // Sets the horizontal alignment; the argument value is mirrored in RTL UI. void SetHorizontalAlignment(gfx::HorizontalAlignment alignment); - gfx::HorizontalAlignment GetHorizontalAlignment() const; + gfx::HorizontalAlignment horizontal_alignment() const { + return render_text_->horizontal_alignment(); + } // Get or set the distance in pixels between baselines of multi-line text. // Default is 0, indicating the distance between lines should be the standard // one for the label's text, font list, and platform. - int line_height() const { return line_height_; } + int line_height() const { return render_text_->min_line_height(); } void SetLineHeight(int height); // Get or set if the label text can wrap on multiple lines; default is false. @@ -83,15 +82,16 @@ class VIEWS_EXPORT Label : public View { // Get or set if the label text should be obscured before rendering (e.g. // should "Password!" display as "*********"); default is false. - bool obscured() const { return obscured_; } + bool obscured() const { return render_text_->obscured(); } void SetObscured(bool obscured); // Sets whether multi-line text can wrap mid-word; the default is false. void SetAllowCharacterBreak(bool allow_character_break); // Sets the eliding or fading behavior, applied as necessary. The default is - // to elide at the end. Eliding is not well supported for multi-line labels. + // to elide at the end. Eliding is not well-supported for multi-line labels. void SetElideBehavior(gfx::ElideBehavior elide_behavior); + gfx::ElideBehavior elide_behavior() const { return elide_behavior_; } // Sets the tooltip text. Default behavior for a label (single-line) is to // show the full text if it is wider than its bounds. Calling this overrides @@ -117,7 +117,7 @@ class VIEWS_EXPORT Label : public View { void set_collapse_when_hidden(bool value) { collapse_when_hidden_ = value; } // Get the text as displayed to the user, respecting the obscured flag. - const base::string16& GetLayoutTextForTesting() const; + base::string16 GetDisplayTextForTesting(); // View: gfx::Insets GetInsets() const override; @@ -125,79 +125,64 @@ class VIEWS_EXPORT Label : public View { gfx::Size GetPreferredSize() const override; gfx::Size GetMinimumSize() const override; int GetHeightForWidth(int w) const override; + void Layout() override; const char* GetClassName() const override; View* GetTooltipHandlerForPoint(const gfx::Point& point) override; bool CanProcessEventsWithinSubtree() const override; void GetAccessibleState(ui::AXViewState* state) override; bool GetTooltipText(const gfx::Point& p, base::string16* tooltip) const override; + void OnEnabledChanged() override; protected: - // Called by Paint to paint the text. - void PaintText(gfx::Canvas* canvas, - const base::string16& text, - const gfx::Rect& text_bounds, - int flags); - - virtual gfx::Size GetTextSize() const; + void PaintText(gfx::Canvas* canvas); SkColor disabled_color() const { return actual_disabled_color_; } // View: void OnBoundsChanged(const gfx::Rect& previous_bounds) override; + void VisibilityChanged(View* starting_from, bool is_visible) override; void OnPaint(gfx::Canvas* canvas) override; - void OnNativeThemeChanged(const ui::NativeTheme* theme) override; void OnDeviceScaleFactorChanged(float device_scale_factor) override; + void OnNativeThemeChanged(const ui::NativeTheme* theme) override; private: - struct DrawStringParams { - DrawStringParams() : flags(0) {} - - base::string16 text; - gfx::Rect bounds; - int flags; - }; - - // These tests call CalculateDrawStringParams in order to verify the - // calculations done for drawing text. - FRIEND_TEST_ALL_PREFIXES(LabelTest, DrawSingleLineString); - FRIEND_TEST_ALL_PREFIXES(LabelTest, DrawMultiLineString); - FRIEND_TEST_ALL_PREFIXES(LabelTest, DrawSingleLineStringInRTL); - FRIEND_TEST_ALL_PREFIXES(LabelTest, DrawMultiLineStringInRTL); - FRIEND_TEST_ALL_PREFIXES(LabelTest, DirectionalityFromText); - FRIEND_TEST_ALL_PREFIXES(LabelTest, DisableSubpixelRendering); - - // Sets both |text_| and |layout_text_| to appropriate values, taking - // the label's 'obscured' status into account. - void SetTextInternal(const base::string16& text); + FRIEND_TEST_ALL_PREFIXES(LabelTest, ResetRenderTextData); + FRIEND_TEST_ALL_PREFIXES(LabelTest, MultilineSupportedRenderText); void Init(const base::string16& text, const gfx::FontList& font_list); - void RecalculateColors(); + void ResetLayout(); + + // Create a single RenderText instance to actually be painted. + scoped_ptr<gfx::RenderText> CreateRenderText( + const base::string16& text, + gfx::HorizontalAlignment alignment, + gfx::DirectionalityMode directionality, + gfx::ElideBehavior elide_behavior); - // Returns where the text is drawn, in the receivers coordinate system. - gfx::Rect GetTextBounds() const; + // Set up |lines_| to actually be painted. + void MaybeBuildRenderTextLines(); - int ComputeDrawStringFlags() const; + // Get the text broken into lines as needed to fit the given |width|. + std::vector<base::string16> GetLinesForWidth(int width) const; - gfx::Rect GetAvailableRect() const; + // Get the natural text size, unelided and only wrapped on newlines. + gfx::Size GetTextSize() const; - // Returns parameters to be used for the DrawString call. Returned value is a - // weak pointer, owned by and scoped to the label. - const DrawStringParams* CalculateDrawStringParams() const; + void RecalculateColors(); // Updates any colors that have not been explicitly set from the theme. void UpdateColorsFromTheme(const ui::NativeTheme* theme); - // Resets |cached_heights_|, |cached_heights_cursor_|, |cached_draw_params_| - // and mark |text_size_valid_| as false. - void ResetLayoutCache(); - bool ShouldShowDefaultTooltip() const; - base::string16 text_; - base::string16 layout_text_; - gfx::FontList font_list_; + // An un-elided and single-line RenderText object used for preferred sizing. + scoped_ptr<gfx::RenderText> render_text_; + + // The RenderText instances used to display elided and multi-line text. + ScopedVector<gfx::RenderText> lines_; + SkColor requested_enabled_color_; SkColor actual_enabled_color_; SkColor requested_disabled_color_; @@ -209,28 +194,18 @@ class VIEWS_EXPORT Label : public View { bool disabled_color_set_; bool background_color_set_; + gfx::ElideBehavior elide_behavior_; + bool subpixel_rendering_enabled_; bool auto_color_readability_; - mutable gfx::Size text_size_; - mutable bool text_size_valid_; - int line_height_; + // TODO(mukai): remove |multi_line_| when all RenderText can render multiline. bool multi_line_; - bool obscured_; - bool allow_character_break_; - gfx::ElideBehavior elide_behavior_; - gfx::HorizontalAlignment horizontal_alignment_; base::string16 tooltip_text_; bool handles_tooltips_; // Whether to collapse the label when it's not visible. bool collapse_when_hidden_; - gfx::ShadowValues shadows_; - - // The cached heights to avoid recalculation in GetHeightForWidth(). - mutable std::vector<gfx::Size> cached_heights_; - mutable int cached_heights_cursor_; - - // The cached results of CalculateDrawStringParams(). - mutable DrawStringParams cached_draw_params_; + bool allow_character_break_; + int max_width_; // TODO(vadimt): Remove is_first_paint_text_ before crbug.com/431326 is // closed. diff --git a/ui/views/controls/label_unittest.cc b/ui/views/controls/label_unittest.cc index a0cb97f..193a3db 100644 --- a/ui/views/controls/label_unittest.cc +++ b/ui/views/controls/label_unittest.cc @@ -77,25 +77,41 @@ TEST_F(LabelTest, AlignmentProperty) { // The alignment should be flipped in RTL UI. label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); EXPECT_EQ(reverse_alignment ? gfx::ALIGN_LEFT : gfx::ALIGN_RIGHT, - label.GetHorizontalAlignment()); + label.horizontal_alignment()); label.SetHorizontalAlignment(gfx::ALIGN_LEFT); EXPECT_EQ(reverse_alignment ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT, - label.GetHorizontalAlignment()); + label.horizontal_alignment()); label.SetHorizontalAlignment(gfx::ALIGN_CENTER); - EXPECT_EQ(gfx::ALIGN_CENTER, label.GetHorizontalAlignment()); + EXPECT_EQ(gfx::ALIGN_CENTER, label.horizontal_alignment()); for (size_t j = 0; j < 2; ++j) { label.SetHorizontalAlignment(gfx::ALIGN_TO_HEAD); const bool rtl = j == 0; label.SetText(rtl ? base::WideToUTF16(L"\x5d0") : ASCIIToUTF16("A")); - EXPECT_EQ(rtl ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT, - label.GetHorizontalAlignment()); + EXPECT_EQ(gfx::ALIGN_TO_HEAD, label.horizontal_alignment()); } } EXPECT_EQ(was_rtl, base::i18n::IsRTL()); } +TEST_F(LabelTest, ElideBehavior) { + Label label; + base::string16 text(ASCIIToUTF16("This is example text.")); + label.SetText(text); + EXPECT_EQ(gfx::ELIDE_TAIL, label.elide_behavior()); + gfx::Size size = label.GetPreferredSize(); + label.SetBoundsRect(gfx::Rect(size)); + EXPECT_EQ(text, label.GetDisplayTextForTesting()); + + size.set_width(size.width() / 2); + label.SetBoundsRect(gfx::Rect(size)); + EXPECT_GT(text.size(), label.GetDisplayTextForTesting().size()); + + label.SetElideBehavior(gfx::NO_ELIDE); + EXPECT_EQ(text, label.GetDisplayTextForTesting()); +} + TEST_F(LabelTest, MultiLineProperty) { Label label; EXPECT_FALSE(label.multi_line()); @@ -109,25 +125,29 @@ TEST_F(LabelTest, ObscuredProperty) { Label label; base::string16 test_text(ASCIIToUTF16("Password!")); label.SetText(test_text); + label.SizeToPreferredSize(); // The text should be unobscured by default. EXPECT_FALSE(label.obscured()); - EXPECT_EQ(test_text, label.GetLayoutTextForTesting()); + EXPECT_EQ(test_text, label.GetDisplayTextForTesting()); EXPECT_EQ(test_text, label.text()); label.SetObscured(true); + label.SizeToPreferredSize(); EXPECT_TRUE(label.obscured()); - EXPECT_EQ(ASCIIToUTF16("*********"), label.GetLayoutTextForTesting()); + EXPECT_EQ(ASCIIToUTF16("*********"), label.GetDisplayTextForTesting()); EXPECT_EQ(test_text, label.text()); label.SetText(test_text + test_text); + label.SizeToPreferredSize(); EXPECT_EQ(ASCIIToUTF16("******************"), - label.GetLayoutTextForTesting()); + label.GetDisplayTextForTesting()); EXPECT_EQ(test_text + test_text, label.text()); label.SetObscured(false); + label.SizeToPreferredSize(); EXPECT_FALSE(label.obscured()); - EXPECT_EQ(test_text + test_text, label.GetLayoutTextForTesting()); + EXPECT_EQ(test_text + test_text, label.GetDisplayTextForTesting()); EXPECT_EQ(test_text + test_text, label.text()); } @@ -137,9 +157,10 @@ TEST_F(LabelTest, ObscuredSurrogatePair) { Label label; base::string16 test_text = base::UTF8ToUTF16("\xF0\x9D\x84\x9E"); label.SetText(test_text); + label.SizeToPreferredSize(); label.SetObscured(true); - EXPECT_EQ(ASCIIToUTF16("*"), label.GetLayoutTextForTesting()); + EXPECT_EQ(ASCIIToUTF16("*"), label.GetDisplayTextForTesting()); EXPECT_EQ(test_text, label.text()); } @@ -357,483 +378,38 @@ TEST_F(LabelTest, MultiLineSizing) { required_size.width() + border.width()); } -TEST_F(LabelTest, DrawSingleLineString) { +// Verifies if the combination of text eliding and multiline doesn't cause +// any side effects of size / layout calculation. +TEST_F(LabelTest, MultiLineSizingWithElide) { + const base::string16 text = + ASCIIToUTF16("A random string\nwith multiple lines\nand returns!"); Label label; label.SetFocusable(false); - - label.SetText(ASCIIToUTF16("Here's a string with no returns.")); - gfx::Size required_size(label.GetPreferredSize()); - gfx::Size extra(22, 8); - label.SetBounds(0, 0, required_size.width() + extra.width(), - required_size.height() + extra.height()); - - // Do some basic verifications for all three alignments. - // Centered text. - const Label::DrawStringParams* params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be centered horizontally and vertically. - EXPECT_EQ(extra.width() / 2, params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_CENTER, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Left aligned text. - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be left aligned horizontally and centered vertically. - EXPECT_EQ(0, params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_LEFT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Right aligned text. - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be right aligned horizontally and centered vertically. - EXPECT_EQ(extra.width(), params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_RIGHT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Test single line drawing with a border. - gfx::Insets border(39, 34, 8, 96); - label.SetBorder(Border::CreateEmptyBorder( - border.top(), border.left(), border.bottom(), border.right())); - - gfx::Size required_size_with_border(label.GetPreferredSize()); - EXPECT_EQ(required_size.width() + border.width(), - required_size_with_border.width()); - EXPECT_EQ(required_size.height() + border.height(), - required_size_with_border.height()); - label.SetBounds(0, 0, required_size_with_border.width() + extra.width(), - required_size_with_border.height() + extra.height()); - - // Centered text with border. - label.SetHorizontalAlignment(gfx::ALIGN_CENTER); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be centered horizontally and vertically within the border. - EXPECT_EQ(border.left() + extra.width() / 2, params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_CENTER, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Left aligned text with border. - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be left aligned horizontally and centered vertically. - EXPECT_EQ(border.left(), params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_LEFT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Right aligned text with border. - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be right aligned horizontally and centered vertically. - EXPECT_EQ(border.left() + extra.width(), params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_RIGHT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); -} - -// Pango needs a max height to elide multiline text; that is not supported here. -TEST_F(LabelTest, DrawMultiLineString) { - Label label; - label.SetFocusable(false); - // Set a background color to prevent gfx::Canvas::NO_SUBPIXEL_RENDERING flags. - label.SetBackgroundColor(SK_ColorWHITE); - - label.SetText(ASCIIToUTF16("Another string\nwith returns\n\n!")); + label.SetText(text); label.SetMultiLine(true); - label.SizeToFit(0); - gfx::Size extra(50, 10); - label.SetBounds(label.x(), label.y(), - label.width() + extra.width(), - label.height() + extra.height()); - - // Do some basic verifications for all three alignments. - const Label::DrawStringParams* params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(extra.width() / 2, params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - int expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_CENTER; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); - gfx::Rect center_bounds(params->bounds); - - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(0, params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_LEFT; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); - - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(extra.width(), params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_RIGHT; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); - - // Test multiline drawing with a border. - gfx::Insets border(19, 92, 23, 2); - label.SetBorder(Border::CreateEmptyBorder( - border.top(), border.left(), border.bottom(), border.right())); - label.SizeToFit(0); - label.SetBounds(label.x(), label.y(), - label.width() + extra.width(), - label.height() + extra.height()); - - label.SetHorizontalAlignment(gfx::ALIGN_CENTER); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(border.left() + extra.width() / 2, params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_CENTER; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); - - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(border.left(), params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_LEFT; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); - - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(extra.width() + border.left(), params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - expected_flags = gfx::Canvas::MULTI_LINE | - gfx::Canvas::TEXT_ALIGN_RIGHT; -#if !defined(OS_WIN) - expected_flags |= gfx::Canvas::NO_ELLIPSIS; -#endif - EXPECT_EQ(expected_flags, expected_flags); -} -TEST_F(LabelTest, DrawSingleLineStringInRTL) { - Label label; - label.SetFocusable(false); - - std::string locale = l10n_util::GetApplicationLocale(""); - base::i18n::SetICUDefaultLocale("he"); - - label.SetText(ASCIIToUTF16("Here's a string with no returns.")); - gfx::Size required_size(label.GetPreferredSize()); - gfx::Size extra(22, 8); - label.SetBounds(0, 0, required_size.width() + extra.width(), - required_size.height() + extra.height()); - - // Do some basic verifications for all three alignments. - // Centered text. - const Label::DrawStringParams* params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be centered horizontally and vertically. - EXPECT_EQ(extra.width() / 2, params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_CENTER, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // ALIGN_LEFT label. - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be right aligned horizontally and centered vertically. - EXPECT_EQ(extra.width(), params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_RIGHT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // ALIGN_RIGHT label. - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be left aligned horizontally and centered vertically. - EXPECT_EQ(0, params->bounds.x()); - EXPECT_EQ(0, params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_LEFT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - - // Test single line drawing with a border. - gfx::Insets border(39, 34, 8, 96); - label.SetBorder(Border::CreateEmptyBorder( - border.top(), border.left(), border.bottom(), border.right())); - - gfx::Size required_size_with_border(label.GetPreferredSize()); - EXPECT_EQ(required_size.width() + border.width(), - required_size_with_border.width()); - EXPECT_EQ(required_size.height() + border.height(), - required_size_with_border.height()); - label.SetBounds(0, 0, required_size_with_border.width() + extra.width(), - required_size_with_border.height() + extra.height()); - - // Centered text with border. - label.SetHorizontalAlignment(gfx::ALIGN_CENTER); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be centered horizontally and vertically within the border. - EXPECT_EQ(border.left() + extra.width() / 2, params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_CENTER, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // ALIGN_LEFT text with border. - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be right aligned horizontally and centered vertically. - EXPECT_EQ(border.left() + extra.width(), params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_RIGHT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // ALIGN_RIGHT text. - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - // The text should be left aligned horizontally and centered vertically. - EXPECT_EQ(border.left(), params->bounds.x()); - EXPECT_EQ(border.top(), params->bounds.y()); - EXPECT_EQ(required_size.width(), params->bounds.width()); - EXPECT_EQ(label.GetContentsBounds().height(), params->bounds.height()); - EXPECT_EQ(gfx::Canvas::TEXT_ALIGN_LEFT, - params->flags & (gfx::Canvas::TEXT_ALIGN_LEFT | - gfx::Canvas::TEXT_ALIGN_CENTER | - gfx::Canvas::TEXT_ALIGN_RIGHT)); - - // Reset locale. - base::i18n::SetICUDefaultLocale(locale); -} - -// On Linux the underlying pango routines require a max height in order to -// ellide multiline text. So until that can be resolved, we set all -// multiline lables to not ellide in Linux only. -TEST_F(LabelTest, DrawMultiLineStringInRTL) { - Label label; - label.SetFocusable(false); - - // Test for RTL. - std::string locale = l10n_util::GetApplicationLocale(""); - base::i18n::SetICUDefaultLocale("he"); - - label.SetText(ASCIIToUTF16("Another string\nwith returns\n\n!")); - label.SetMultiLine(true); - label.SizeToFit(0); - gfx::Size extra(50, 10); - label.SetBounds(label.x(), label.y(), - label.width() + extra.width(), - label.height() + extra.height()); - - // Do some basic verifications for all three alignments. - const Label::DrawStringParams* params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(extra.width() / 2, params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_CENTER & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif - gfx::Rect center_bounds(params->bounds); - - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(extra.width(), params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_RIGHT & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif - - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(0, params->bounds.x()); - EXPECT_EQ(extra.height() / 2, params->bounds.y()); - EXPECT_GT(params->bounds.width(), kMinTextDimension); - EXPECT_GT(params->bounds.height(), kMinTextDimension); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_LEFT & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif - - // Test multiline drawing with a border. - gfx::Insets border(19, 92, 23, 2); - label.SetBorder(Border::CreateEmptyBorder( - border.top(), border.left(), border.bottom(), border.right())); - label.SizeToFit(0); - label.SetBounds(label.x(), label.y(), - label.width() + extra.width(), - label.height() + extra.height()); - - label.SetHorizontalAlignment(gfx::ALIGN_CENTER); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(border.left() + extra.width() / 2, params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_CENTER & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif + gfx::Size required_size = label.GetPreferredSize(); + EXPECT_GT(required_size.height(), kMinTextDimension); + EXPECT_GT(required_size.width(), kMinTextDimension); + label.SetBoundsRect(gfx::Rect(required_size)); - label.SetHorizontalAlignment(gfx::ALIGN_LEFT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(border.left() + extra.width(), params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_RIGHT & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif + label.SetElideBehavior(gfx::ELIDE_TAIL); + EXPECT_EQ(required_size.ToString(), label.GetPreferredSize().ToString()); + EXPECT_EQ(text, label.GetDisplayTextForTesting()); - label.SetHorizontalAlignment(gfx::ALIGN_RIGHT); - label.ResetLayoutCache(); - params = label.CalculateDrawStringParams(); - EXPECT_EQ(label.text(), params->text); - EXPECT_EQ(border.left(), params->bounds.x()); - EXPECT_EQ(border.top() + extra.height() / 2, params->bounds.y()); - EXPECT_EQ(center_bounds.width(), params->bounds.width()); - EXPECT_EQ(center_bounds.height(), params->bounds.height()); - EXPECT_TRUE(gfx::Canvas::MULTI_LINE & params->flags); - EXPECT_TRUE(gfx::Canvas::TEXT_ALIGN_LEFT & params->flags); -#if !defined(OS_WIN) - EXPECT_TRUE(gfx::Canvas::NO_ELLIPSIS & params->flags); -#endif + label.SizeToFit(required_size.width() - 1); + gfx::Size narrow_size = label.GetPreferredSize(); + EXPECT_GT(required_size.width(), narrow_size.width()); + EXPECT_LT(required_size.height(), narrow_size.height()); - // Reset Locale - base::i18n::SetICUDefaultLocale(locale); -} + // SetBounds() doesn't change the preferred size. + label.SetBounds(0, 0, narrow_size.width() - 1, narrow_size.height()); + EXPECT_EQ(narrow_size.ToString(), label.GetPreferredSize().ToString()); -// Ensure the subpixel rendering flag and background color alpha are respected. -TEST_F(LabelTest, DisableSubpixelRendering) { - Label label; - label.SetBackgroundColor(SK_ColorWHITE); - const int flag = gfx::Canvas::NO_SUBPIXEL_RENDERING; - EXPECT_EQ(0, label.ComputeDrawStringFlags() & flag); - label.SetSubpixelRenderingEnabled(false); - EXPECT_EQ(flag, label.ComputeDrawStringFlags() & flag); - label.SetSubpixelRenderingEnabled(true); - EXPECT_EQ(0, label.ComputeDrawStringFlags() & flag); - // Text cannot be drawn with subpixel rendering on transparent backgrounds. - label.SetBackgroundColor(SkColorSetARGB(64, 255, 255, 255)); - EXPECT_EQ(flag, label.ComputeDrawStringFlags() & flag); + // Paint() doesn't change the preferred size. + gfx::Canvas canvas; + label.Paint(&canvas, CullSet()); + EXPECT_EQ(narrow_size.ToString(), label.GetPreferredSize().ToString()); } // Check that labels support GetTooltipHandlerForPoint. @@ -894,4 +470,64 @@ TEST_F(LabelTest, GetTooltipHandlerForPoint) { EXPECT_FALSE(label.GetTooltipHandlerForPoint(gfx::Point(3, 11))); } +// Check that label releases its internal layout data when it's unnecessary. +TEST_F(LabelTest, ResetRenderTextData) { + Label label; + label.SetText(ASCIIToUTF16("Example")); + label.SizeToPreferredSize(); + gfx::Size preferred_size = label.GetPreferredSize(); + + EXPECT_NE(gfx::Size().ToString(), preferred_size.ToString()); + EXPECT_EQ(0u, label.lines_.size()); + + gfx::Canvas canvas(preferred_size, 1.0f, true); + label.Paint(&canvas, CullSet()); + EXPECT_EQ(1u, label.lines_.size()); + + // Label should recreate its RenderText object when it's invisible, to release + // the layout structures and data. + label.SetVisible(false); + EXPECT_EQ(0u, label.lines_.size()); + + // Querying fields or size information should not recompute the layout + // unnecessarily. + EXPECT_EQ(ASCIIToUTF16("Example"), label.text()); + EXPECT_EQ(0u, label.lines_.size()); + + EXPECT_EQ(preferred_size.ToString(), label.GetPreferredSize().ToString()); + EXPECT_EQ(0u, label.lines_.size()); + + // RenderText data should be back when it's necessary. + label.SetVisible(true); + EXPECT_EQ(0u, label.lines_.size()); + + label.Paint(&canvas, CullSet()); + EXPECT_EQ(1u, label.lines_.size()); + + // Changing layout just resets |lines_|. It'll recover next time it's drawn. + label.SetBounds(0, 0, 10, 10); + EXPECT_EQ(0u, label.lines_.size()); + + label.Paint(&canvas, CullSet()); + EXPECT_EQ(1u, label.lines_.size()); +} + +#if !defined(OS_MACOSX) +TEST_F(LabelTest, MultilineSupportedRenderText) { + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance()); + ASSERT_TRUE(render_text->MultilineSupported()); + + Label label; + label.SetText(ASCIIToUTF16("Example of\nmultilined label")); + label.SetMultiLine(true); + label.SizeToPreferredSize(); + + gfx::Canvas canvas(label.GetPreferredSize(), 1.0f, true); + label.Paint(&canvas, CullSet()); + + // There's only one 'line', RenderText itself supports multiple lines. + EXPECT_EQ(1u, label.lines_.size()); +} +#endif + } // namespace views |