Commit dd394d24 authored by wutao's avatar wutao Committed by Commit Bot

Styled label with mixed views

This cl implementation allows the client to provide custom views to
styled label and set the horizontal alignment.

Features:
1. The client can provide any view.
2. The client can set the horizontal alignment.
3. All views in a line are center aligned.
4. The custom view is one unit, cannot be wrapped into different line.
5. If width is not wide for the custom view, the custom view will be cutoff.

bug.

Bug: 793414
Test: Tested with different views and layout. Examples are posted in the
Change-Id: Icf7d70da812c9e40362e20cbc30dc118cdf6d77b
Reviewed-on: https://chromium-review.googlesource.com/888543
Commit-Queue: Tao Wu <wutao@chromium.org>
Reviewed-by: default avatarMichael Wasserman <msw@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534200}
parent 7274caf9
......@@ -7,6 +7,7 @@
#include <stdint.h>
#include "base/i18n/char_iterator.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "third_party/icu/source/common/unicode/uchar.h"
......@@ -108,4 +109,13 @@ size_t FindValidBoundaryAfter(const base::string16& text, size_t index) {
return static_cast<size_t>(text_index);
}
HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment) {
if (base::i18n::IsRTL() &&
(alignment == gfx::ALIGN_LEFT || alignment == gfx::ALIGN_RIGHT)) {
alignment =
(alignment == gfx::ALIGN_LEFT) ? gfx::ALIGN_RIGHT : gfx::ALIGN_LEFT;
}
return alignment;
}
} // namespace gfx
......@@ -47,6 +47,9 @@ FindValidBoundaryBefore(const base::string16& text, size_t index);
GFX_EXPORT size_t
FindValidBoundaryAfter(const base::string16& text, size_t index);
// If the UI layout is right-to-left, flip the alignment direction.
GFX_EXPORT HorizontalAlignment MaybeFlipForRTL(HorizontalAlignment alignment);
} // namespace gfx
#endif // UI_GFX_TEXT_UTILS_H_
......@@ -25,6 +25,7 @@
#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/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/background.h"
......@@ -150,12 +151,7 @@ void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
}
void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
// 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;
}
alignment = gfx::MaybeFlipForRTL(alignment);
if (horizontal_alignment() == alignment)
return;
is_first_paint_text_ = true;
......
......@@ -6,12 +6,16 @@
#include <stddef.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
#include "base/i18n/rtl.h"
#include "base/strings/string_util.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/text_elider.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/link.h"
......@@ -65,6 +69,16 @@ std::unique_ptr<Label> CreateLabelRange(
return result;
}
// Returns the horizontal offset to align views in a line.
int HorizontalAdjustment(int used_width,
int width,
gfx::HorizontalAlignment alignment) {
const int space = width - used_width;
return alignment == gfx::ALIGN_LEFT
? 0
: alignment == gfx::ALIGN_CENTER ? space / 2 : space;
}
} // namespace
// StyledLabel::RangeStyleInfo ------------------------------------------------
......@@ -108,7 +122,9 @@ StyledLabel::StyledLabel(const base::string16& text,
width_at_last_layout_(0),
displayed_on_background_color_(SkColorSetRGB(0xFF, 0xFF, 0xFF)),
displayed_on_background_color_set_(false),
auto_color_readability_enabled_(true) {
auto_color_readability_enabled_(true),
horizontal_alignment_(base::i18n::IsRTL() ? gfx::ALIGN_RIGHT
: gfx::ALIGN_LEFT) {
base::TrimWhitespace(text, base::TRIM_TRAILING, &text_);
}
......@@ -139,6 +155,11 @@ void StyledLabel::AddStyleRange(const gfx::Range& range,
PreferredSizeChanged();
}
void StyledLabel::AddCustomView(std::unique_ptr<View> custom_view) {
DCHECK(custom_view->owned_by_client());
custom_views_.insert(std::move(custom_view));
}
void StyledLabel::SetTextContext(int text_context) {
if (text_context_ == text_context)
return;
......@@ -232,6 +253,17 @@ void StyledLabel::LinkClicked(Link* source, int event_flags) {
listener_->StyledLabelLinkClicked(this, link_targets_[source], event_flags);
}
// TODO(wutao): support gfx::ALIGN_TO_HEAD alignment.
void StyledLabel::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
DCHECK_NE(gfx::ALIGN_TO_HEAD, alignment);
alignment = gfx::MaybeFlipForRTL(alignment);
if (horizontal_alignment_ == alignment)
return;
horizontal_alignment_ = alignment;
SchedulePaint();
}
int StyledLabel::GetDefaultLineHeight() const {
return specified_line_height_ > 0
? specified_line_height_
......@@ -271,13 +303,13 @@ gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
if (width <= 0 || text_.empty())
return gfx::Size();
const int line_height = GetDefaultLineHeight();
const int default_line_height = GetDefaultLineHeight();
// The index of the line we're on.
int line = 0;
// The x position (in pixels) of the line we're on, relative to content
// bounds.
int x = 0;
const gfx::Insets insets = GetInsets();
// The current child view's position, relative to content bounds, in pixels.
gfx::Point offset(0, insets.top());
int total_height = 0;
// The width that was actually used. Guaranteed to be no larger than |width|.
int used_width = 0;
......@@ -290,10 +322,16 @@ gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
bool first_loop_iteration = true;
// Max height of the views in a line.
int max_line_height = default_line_height;
// Temporary references to the views in a line, used for alignment.
std::vector<View*> views_in_a_line;
// Iterate over the text, creating a bunch of labels and links and laying them
// out in the appropriate positions.
while (!remaining_string.empty()) {
if (x == 0 && !first_loop_iteration) {
if (offset.x() == 0 && !first_loop_iteration) {
if (remaining_string.front() == L'\n') {
// Wrapped to the next line on \n, remove it. Other whitespace,
// eg, spaces to indent next line, are preserved.
......@@ -312,75 +350,104 @@ gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
range = current_range->range;
const size_t position = text_.size() - remaining_string.size();
const gfx::Rect chunk_bounds(x, 0, width - x, 2 * line_height);
std::vector<base::string16> substrings;
// If the start of the remaining text is inside a styled range, the font
// style may differ from the base font. The font specified by the range
// should be used when eliding text.
gfx::FontList text_font_list = position >= range.start()
? GetFontListForRange(current_range)
: GetDefaultFontList();
int elide_result = gfx::ElideRectangleText(
remaining_string, text_font_list, chunk_bounds.width(),
chunk_bounds.height(), gfx::WRAP_LONG_WORDS, &substrings);
if (substrings.empty()) {
// There is no room for anything; abort. Since wrapping is enabled, this
// should only occur if there is insufficient vertical space remaining.
// ElideRectangleText always adds a single character, even if there is no
// room horizontally.
DCHECK_NE(0, elide_result & gfx::INSUFFICIENT_SPACE_VERTICAL);
break;
}
// If the current range is not a custom_view, then we use ElideRectangleText
// to determine the line wrapping. Note: if it is a custom_view, then the
// |position| should equal |range.start()| because the custom_view is
// treated as one unit.
if (position != range.start() || (current_range != style_ranges_.end() &&
!current_range->style_info.custom_view)) {
const gfx::Rect chunk_bounds(offset.x(), 0, width - offset.x(),
default_line_height);
// If the start of the remaining text is inside a styled range, the font
// style may differ from the base font. The font specified by the range
// should be used when eliding text.
gfx::FontList text_font_list = position >= range.start()
? GetFontListForRange(current_range)
: GetDefaultFontList();
int elide_result = gfx::ElideRectangleText(
remaining_string, text_font_list, chunk_bounds.width(),
chunk_bounds.height(), gfx::WRAP_LONG_WORDS, &substrings);
if (substrings.empty()) {
// There is no room for anything; abort. Since wrapping is enabled, this
// should only occur if there is insufficient vertical space remaining.
// ElideRectangleText always adds a single character, even if there is
// no room horizontally.
DCHECK_NE(0, elide_result & gfx::INSUFFICIENT_SPACE_VERTICAL);
break;
}
// Views are aligned to integer coordinates, but typesetting is not. This
// means that it's possible for an ElideRectangleText on a prior iteration
// to fit a word on the current line, which does not fit after that word is
// wrapped in a View for its chunk at the end of the line. In most cases,
// this will just wrap more words on to the next line. However, if the
// remaining chunk width is insufficient for the very _first_ word, that
// word will be incorrectly split. In this case, start a new line instead.
bool truncated_chunk =
x != 0 && (elide_result & gfx::INSUFFICIENT_SPACE_FOR_FIRST_WORD) != 0;
if (substrings[0].empty() || truncated_chunk) {
// The entire line is \n, or nothing fits on this line. Start a new line.
// As for the first line, don't advance line number so that it will be
// handled again at the beginning of the loop.
if (x != 0 || line > 0) {
++line;
// Views are aligned to integer coordinates, but typesetting is not. This
// means that it's possible for an ElideRectangleText on a prior iteration
// to fit a word on the current line, which does not fit after that word
// is wrapped in a View for its chunk at the end of the line. In most
// cases, this will just wrap more words on to the next line. However, if
// the remaining chunk width is insufficient for the very _first_ word,
// that word will be incorrectly split. In this case, start a new line
// instead.
bool truncated_chunk =
offset.x() != 0 &&
(elide_result & gfx::INSUFFICIENT_SPACE_FOR_FIRST_WORD) != 0;
if (substrings[0].empty() || truncated_chunk) {
// The entire line is \n, or nothing fits on this line. Start a new
// line. As for the first line, don't advance line number so that it
// will be handled again at the beginning of the loop.
AdvanceOneLine(&line, &offset, &max_line_height, width,
&views_in_a_line,
offset.x() != 0 || line > 0 /* new_line */);
continue;
}
x = 0;
continue;
}
base::string16 chunk = substrings[0];
base::string16 chunk;
View* custom_view = nullptr;
std::unique_ptr<Label> label;
if (position >= range.start()) {
const RangeStyleInfo& style_info = current_range->style_info;
if (style_info.disable_line_wrapping && chunk.size() < range.length() &&
position == range.start() && x != 0) {
if (style_info.custom_view) {
custom_view = style_info.custom_view;
// Ownership of the custom view must be passed to StyledLabel.
DCHECK(
std::find_if(custom_views_.cbegin(), custom_views_.cend(),
[custom_view](const std::unique_ptr<View>& view_ptr) {
return view_ptr.get() == custom_view;
}) != custom_views_.cend());
// Do not allow wrap in custom view.
DCHECK_EQ(position, range.start());
chunk = remaining_string.substr(0, range.end() - position);
} else {
chunk = substrings[0];
}
if (((custom_view &&
offset.x() + custom_view->GetPreferredSize().width() > width) ||
(style_info.disable_line_wrapping &&
chunk.size() < range.length())) &&
position == range.start() && offset.x() != 0) {
// If the chunk should not be wrapped, try to fit it entirely on the
// next line.
x = 0;
++line;
AdvanceOneLine(&line, &offset, &max_line_height, width,
&views_in_a_line);
continue;
}
if (chunk.size() > range.end() - position)
chunk = chunk.substr(0, range.end() - position);
label = CreateLabelRange(chunk, text_context_, default_text_style_,
style_info, this);
if (style_info.IsLink() && !dry_run)
link_targets_[label.get()] = range;
if (!custom_view) {
label = CreateLabelRange(chunk, text_context_, default_text_style_,
style_info, this);
if (style_info.IsLink() && !dry_run)
link_targets_[label.get()] = range;
}
if (position + chunk.size() >= range.end())
++current_range;
} else {
chunk = substrings[0];
// This chunk is normal text.
if (position + chunk.size() > range.start())
chunk = chunk.substr(0, range.start() - position);
......@@ -388,53 +455,86 @@ gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) {
default_style, this);
}
if (displayed_on_background_color_set_)
label->SetBackgroundColor(displayed_on_background_color_);
label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
if (label) {
if (displayed_on_background_color_set_)
label->SetBackgroundColor(displayed_on_background_color_);
label->SetAutoColorReadabilityEnabled(auto_color_readability_enabled_);
}
const gfx::Size view_size = label->GetPreferredSize();
const gfx::Insets insets = GetInsets();
gfx::Point view_origin(insets.left() + x,
insets.top() + line * line_height);
if (Link::GetDefaultFocusStyle() == Link::FocusStyle::RING) {
View* child_view = custom_view ? custom_view : label.get();
gfx::Size view_size = child_view->GetPreferredSize();
// |offset.y()| already contains |insets.top()|.
gfx::Point view_origin(insets.left() + offset.x(), offset.y());
gfx::Insets focus_border_insets;
if (Link::GetDefaultFocusStyle() == Link::FocusStyle::RING && label) {
// Calculate the size of the optional focus border, and overlap by that
// amount. Otherwise, "<a>link</a>," will render as "link ,".
const gfx::Insets focus_border_insets = FocusBorderInsets(*label);
view_origin.Offset(-focus_border_insets.left(),
-focus_border_insets.top());
label->SetBoundsRect(gfx::Rect(view_origin, view_size));
x += view_size.width() - focus_border_insets.width();
used_width = std::max(used_width, x);
total_height =
std::max(total_height, label->bounds().bottom() + insets.bottom() -
focus_border_insets.bottom());
} else {
label->SetBoundsRect(gfx::Rect(view_origin, view_size));
x += view_size.width();
total_height =
std::max(total_height, label->bounds().bottom() + insets.bottom());
focus_border_insets = FocusBorderInsets(*label);
}
view_origin.Offset(-focus_border_insets.left(), -focus_border_insets.top());
// The custom view could be wider than the available width; clamp as needed.
if (custom_view) {
view_size.set_width(std::min(
view_size.width(), width - offset.x() + focus_border_insets.width()));
}
child_view->SetBoundsRect(gfx::Rect(view_origin, view_size));
offset.set_x(offset.x() + view_size.width() - focus_border_insets.width());
total_height =
std::max(total_height, child_view->bounds().bottom() + insets.bottom() -
focus_border_insets.bottom());
used_width = std::max(used_width, offset.x());
max_line_height = std::max(
max_line_height, view_size.height() - focus_border_insets.height());
if (!dry_run) {
views_in_a_line.push_back(child_view);
if (label)
AddChildView(label.release());
else
AddChildView(child_view);
}
used_width = std::max(used_width, x);
if (!dry_run)
AddChildView(label.release());
// If |gfx::ElideRectangleText| returned more than one substring, that
// means the whole text did not fit into remaining line width, with text
// after |susbtring[0]| spilling into next line. If whole |substring[0]|
// was added to the current line (this may not be the case if part of the
// substring has different style), proceed to the next line.
if (substrings.size() > 1 && chunk.size() == substrings[0].size()) {
x = 0;
++line;
if (!custom_view && substrings.size() > 1 &&
chunk.size() == substrings[0].size()) {
AdvanceOneLine(&line, &offset, &max_line_height, width, &views_in_a_line);
}
remaining_string = remaining_string.substr(chunk.size());
}
AdvanceOneLine(&line, &offset, &max_line_height, width, &views_in_a_line,
false);
DCHECK_LE(used_width, width);
calculated_size_ = gfx::Size(used_width + GetInsets().width(), total_height);
return calculated_size_;
}
void StyledLabel::AdvanceOneLine(int* line_number,
gfx::Point* offset,
int* max_line_height,
int width,
std::vector<View*>* views_in_a_line,
bool new_line) {
const int x_delta =
HorizontalAdjustment(offset->x(), width, horizontal_alignment_);
for (auto* view : *views_in_a_line) {
gfx::Rect bounds = view->bounds();
bounds.set_x(bounds.x() + x_delta);
bounds.set_y(offset->y() + (*max_line_height - bounds.height()) / 2.0f);
view->SetBoundsRect(bounds);
}
views_in_a_line->clear();
if (new_line) {
++(*line_number);
offset->set_y(offset->y() + *max_line_height);
*max_line_height = GetDefaultLineHeight();
}
offset->set_x(0);
}
} // namespace views
......@@ -7,6 +7,8 @@
#include <list>
#include <map>
#include <memory>
#include <set>
#include "base/macros.h"
#include "base/optional.h"
......@@ -15,6 +17,7 @@
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/link_listener.h"
#include "ui/views/style/typography.h"
#include "ui/views/view.h"
......@@ -62,6 +65,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// If set, the whole range will be put on a single line.
bool disable_line_wrapping = false;
// A custom view shown instead of the underlying text. Ownership of custom
// views must be passed to StyledLabel via AddCustomView().
View* custom_view = nullptr;
};
// Note that any trailing whitespace in |text| will be trimmed.
......@@ -81,6 +88,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// |range| must be contained in |text_|.
void AddStyleRange(const gfx::Range& range, const RangeStyleInfo& style_info);
// Passes ownership of a custom view for use by RangeStyleInfo structs.
void AddCustomView(std::unique_ptr<View> custom_view);
// Set the context of this text. All ranges have the same context.
// |text_context| must be a value from views::style::TextContext.
void SetTextContext(int text_context);
......@@ -125,6 +135,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// LinkListener implementation:
void LinkClicked(Link* source, int event_flags) override;
// Sets the horizontal alignment; the argument value is mirrored in RTL UI.
void SetHorizontalAlignment(gfx::HorizontalAlignment alignment);
private:
struct StyleRange {
StyleRange(const gfx::Range& range,
......@@ -155,6 +168,15 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// |width_at_last_size_calculation_|. Returns the needed size.
gfx::Size CalculateAndDoLayout(int width, bool dry_run);
// Adjusts the offsets of the views in a line for alignment and other line
// parameters.
void AdvanceOneLine(int* line_number,
gfx::Point* offset,
int* max_line_height,
int width,
std::vector<View*>* views_in_a_line,
bool new_line = true);
// The text to display.
base::string16 text_;
......@@ -174,6 +196,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// that correspond to ranges with is_link style set will be added to the map.
std::map<View*, gfx::Range> link_targets_;
// Owns the custom views used to replace ranges of text with icons, etc.
std::set<std::unique_ptr<View>> custom_views_;
// This variable saves the result of the last GetHeightForWidth call in order
// to avoid repeated calculation.
mutable gfx::Size calculated_size_;
......@@ -188,6 +213,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// background.
bool auto_color_readability_enabled_;
// The horizontal alignment. This value is flipped for RTL. The default
// behavior is to align left in LTR UI and right in RTL UI.
gfx::HorizontalAlignment horizontal_alignment_;
DISALLOW_COPY_AND_ASSIGN(StyledLabel);
};
......
......@@ -22,6 +22,7 @@
#include "ui/views/controls/styled_label_listener.h"
#include "ui/views/style/typography.h"
#include "ui/views/test/test_layout_provider.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
......@@ -282,8 +283,7 @@ TEST_P(MDStyledLabelTest, DontBreakLinks) {
const std::string link_text("and this should be a link");
InitStyledLabel(text + link_text);
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()),
static_cast<uint32_t>(text.size() + link_text.size())),
gfx::Range(text.size(), text.size() + link_text.size()),
StyledLabel::RangeStyleInfo::CreateForLink());
Label label(ASCIIToUTF16(text + link_text.substr(0, link_text.size() / 2)));
......@@ -316,8 +316,7 @@ TEST_F(StyledLabelTest, StyledRangeWithDisabledLineWrapping) {
StyledLabel::RangeStyleInfo style_info;
style_info.disable_line_wrapping = true;
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()),
static_cast<uint32_t>(text.size() + unbreakable_text.size())),
gfx::Range(text.size(), text.size() + unbreakable_text.size()),
style_info);
Label label(ASCIIToUTF16(
......@@ -343,8 +342,7 @@ TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) {
style_info.custom_font =
styled()->GetDefaultFontList().DeriveWithStyle(gfx::Font::UNDERLINE);
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()),
static_cast<uint32_t>(text.size() + underlined_text.size())),
gfx::Range(text.size(), text.size() + underlined_text.size()),
style_info);
styled()->SetBounds(0, 0, 1000, 1000);
......@@ -372,8 +370,7 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) {
StyledLabel::RangeStyleInfo style_info;
style_info.text_style = style::STYLE_DISABLED;
styled()->AddStyleRange(
gfx::Range(0u, static_cast<uint32_t>(bold_text.size())), style_info);
styled()->AddStyleRange(gfx::Range(0u, bold_text.size()), style_info);
// Calculate the bold text width if it were a pure label view, both with bold
// and normal style.
......@@ -434,14 +431,12 @@ TEST_F(StyledLabelTest, Color) {
StyledLabel::RangeStyleInfo style_info_red;
style_info_red.override_color = SK_ColorRED;
styled()->AddStyleRange(
gfx::Range(0u, static_cast<uint32_t>(text_red.size())), style_info_red);
styled()->AddStyleRange(gfx::Range(0u, text_red.size()), style_info_red);
StyledLabel::RangeStyleInfo style_info_link =
StyledLabel::RangeStyleInfo::CreateForLink();
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text_red.size()),
static_cast<uint32_t>(text_red.size() + text_link.size())),
gfx::Range(text_red.size(), text_red.size() + text_link.size()),
style_info_link);
styled()->SetBounds(0, 0, 1000, 1000);
......@@ -498,13 +493,10 @@ TEST_P(MDStyledLabelTest, StyledRangeWithTooltip) {
StyledLabel::RangeStyleInfo tooltip_style;
tooltip_style.tooltip = ASCIIToUTF16("tooltip");
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(tooltip_start),
static_cast<uint32_t>(tooltip_start + tooltip_text.size())),
gfx::Range(tooltip_start, tooltip_start + tooltip_text.size()),
tooltip_style);
styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(link_start),
static_cast<uint32_t>(link_start + link_text.size())),
StyledLabel::RangeStyleInfo::CreateForLink());
styled()->AddStyleRange(gfx::Range(link_start, link_start + link_text.size()),
StyledLabel::RangeStyleInfo::CreateForLink());
// Break line inside the range with the tooltip.
Label label(ASCIIToUTF16(
......@@ -652,6 +644,147 @@ TEST_F(StyledLabelTest, Border) {
styled()->GetPreferredSize().width());
}
TEST_F(StyledLabelTest, LineHeightWithShorterCustomView) {
const std::string text("one ");
InitStyledLabel(text);
int default_height = styled()->GetHeightForWidth(1000);
const std::string custom_view_text("with custom view");
const int less_height = 10;
std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>(
gfx::Size(20, default_height - less_height));
custom_view->set_owned_by_client();
StyledLabel::RangeStyleInfo style_info;
style_info.custom_view = custom_view.get();
InitStyledLabel(text + custom_view_text);
styled()->AddStyleRange(
gfx::Range(text.size(), text.size() + custom_view_text.size()),
style_info);
styled()->AddCustomView(std::move(custom_view));
EXPECT_EQ(default_height, styled()->GetHeightForWidth(100));
}
TEST_F(StyledLabelTest, LineHeightWithTallerCustomView) {
const std::string text("one ");
InitStyledLabel(text);
int default_height = styled()->GetHeightForWidth(100);
const std::string custom_view_text("with custom view");
const int more_height = 10;
std::unique_ptr<View> custom_view = std::make_unique<StaticSizedView>(
gfx::Size(20, default_height + more_height));
custom_view->set_owned_by_client();
StyledLabel::RangeStyleInfo style_info;
style_info.custom_view = custom_view.get();
InitStyledLabel(text + custom_view_text);
styled()->AddStyleRange(
gfx::Range(text.size(), text.size() + custom_view_text.size()),
style_info);
styled()->AddCustomView(std::move(custom_view));
EXPECT_EQ(default_height + more_height, styled()->GetHeightForWidth(100));
}
TEST_F(StyledLabelTest, LineWrapperWithCustomView) {
const std::string text_before("one ");
InitStyledLabel(text_before);
int default_height = styled()->GetHeightForWidth(100);
const std::string custom_view_text("two with custom view ");
const std::string text_after("three");
int custom_view_height = 25;
std::unique_ptr<View> custom_view =
std::make_unique<StaticSizedView>(gfx::Size(200, custom_view_height));
custom_view->set_owned_by_client();
StyledLabel::RangeStyleInfo style_info;
style_info.custom_view = custom_view.get();
InitStyledLabel(text_before + custom_view_text + text_after);
styled()->AddStyleRange(
gfx::Range(text_before.size(),
text_before.size() + custom_view_text.size()),
style_info);
styled()->AddCustomView(std::move(custom_view));
EXPECT_EQ(default_height * 2 + custom_view_height,
styled()->GetHeightForWidth(100));
}
TEST_F(StyledLabelTest, LeftAlignment) {
const std::string multiline_text("one\ntwo\nthree");
InitStyledLabel(multiline_text);
styled()->SetHorizontalAlignment(gfx::ALIGN_LEFT);
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
ASSERT_EQ(3, styled()->child_count());
EXPECT_EQ(0, styled()->child_at(0)->bounds().x());
EXPECT_EQ(0, styled()->child_at(1)->bounds().x());
EXPECT_EQ(0, styled()->child_at(2)->bounds().x());
}
TEST_F(StyledLabelTest, RightAlignment) {
const std::string multiline_text("one\ntwo\nthree");
InitStyledLabel(multiline_text);
styled()->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
ASSERT_EQ(3, styled()->child_count());
EXPECT_EQ(1000, styled()->child_at(0)->bounds().right());
EXPECT_EQ(1000, styled()->child_at(1)->bounds().right());
EXPECT_EQ(1000, styled()->child_at(2)->bounds().right());
}
TEST_F(StyledLabelTest, CenterAlignment) {
const std::string multiline_text("one\ntwo\nthree");
InitStyledLabel(multiline_text);
styled()->SetHorizontalAlignment(gfx::ALIGN_CENTER);
styled()->SetBounds(0, 0, 1000, 1000);
styled()->Layout();
Label label_one(ASCIIToUTF16("one"));
Label label_two(ASCIIToUTF16("two"));
Label label_three(ASCIIToUTF16("three"));
ASSERT_EQ(3, styled()->child_count());
EXPECT_EQ((1000 - label_one.GetPreferredSize().width()) / 2,
styled()->child_at(0)->bounds().x());
EXPECT_EQ((1000 - label_two.GetPreferredSize().width()) / 2,
styled()->child_at(1)->bounds().x());
EXPECT_EQ((1000 - label_three.GetPreferredSize().width()) / 2,
styled()->child_at(2)->bounds().x());
}
TEST_P(MDStyledLabelTest, ViewsCenteredWithLinkAndCustomView) {
const std::string text("This is a test block of text, ");
const std::string link_text("and this should be a link");
const std::string custom_view_text("And this is a custom view");
InitStyledLabel(text + link_text + custom_view_text);
styled()->AddStyleRange(
gfx::Range(text.size(), text.size() + link_text.size()),
StyledLabel::RangeStyleInfo::CreateForLink());
int custom_view_height = 25;
std::unique_ptr<View> custom_view =
std::make_unique<StaticSizedView>(gfx::Size(20, custom_view_height));
custom_view->set_owned_by_client();
StyledLabel::RangeStyleInfo style_info;
style_info.custom_view = custom_view.get();
styled()->AddStyleRange(
gfx::Range(text.size() + link_text.size(),
text.size() + link_text.size() + custom_view_text.size()),
style_info);
styled()->AddCustomView(std::move(custom_view));
styled()->SetBounds(0, 0, 1000, 500);
styled()->Layout();
int height = styled()->GetPreferredSize().height();
ASSERT_EQ(3, styled()->child_count());
EXPECT_EQ((height - styled()->child_at(0)->bounds().height()) / 2,
styled()->child_at(0)->bounds().y());
EXPECT_EQ((height - styled()->child_at(1)->bounds().height()) / 2,
styled()->child_at(1)->bounds().y());
EXPECT_EQ((height - styled()->child_at(2)->bounds().height()) / 2,
styled()->child_at(2)->bounds().y());
}
INSTANTIATE_TEST_CASE_P(,
MDStyledLabelTest,
::testing::Values(SecondaryUiMode::MD,
......
......@@ -264,6 +264,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate,
// By default a View is owned by its parent unless specified otherwise here.
void set_owned_by_client() { owned_by_client_ = true; }
bool owned_by_client() const { return owned_by_client_; }
// Tree operations -----------------------------------------------------------
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment