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 @@ ...@@ -7,6 +7,7 @@
#include <stdint.h> #include <stdint.h>
#include "base/i18n/char_iterator.h" #include "base/i18n/char_iterator.h"
#include "base/i18n/rtl.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/safe_conversions.h" #include "base/numerics/safe_conversions.h"
#include "third_party/icu/source/common/unicode/uchar.h" #include "third_party/icu/source/common/unicode/uchar.h"
...@@ -108,4 +109,13 @@ size_t FindValidBoundaryAfter(const base::string16& text, size_t index) { ...@@ -108,4 +109,13 @@ size_t FindValidBoundaryAfter(const base::string16& text, size_t index) {
return static_cast<size_t>(text_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 } // namespace gfx
...@@ -47,6 +47,9 @@ FindValidBoundaryBefore(const base::string16& text, size_t index); ...@@ -47,6 +47,9 @@ FindValidBoundaryBefore(const base::string16& text, size_t index);
GFX_EXPORT size_t GFX_EXPORT size_t
FindValidBoundaryAfter(const base::string16& text, size_t index); 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 } // namespace gfx
#endif // UI_GFX_TEXT_UTILS_H_ #endif // UI_GFX_TEXT_UTILS_H_
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "ui/gfx/color_utils.h" #include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/insets.h"
#include "ui/gfx/text_elider.h" #include "ui/gfx/text_elider.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h" #include "ui/strings/grit/ui_strings.h"
#include "ui/views/background.h" #include "ui/views/background.h"
...@@ -150,12 +151,7 @@ void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) { ...@@ -150,12 +151,7 @@ void Label::SetSubpixelRenderingEnabled(bool subpixel_rendering_enabled) {
} }
void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { void Label::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) {
// If the UI layout is right-to-left, flip the alignment direction. alignment = gfx::MaybeFlipForRTL(alignment);
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) if (horizontal_alignment() == alignment)
return; return;
is_first_paint_text_ = true; is_first_paint_text_ = true;
......
This diff is collapsed.
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
#include <list> #include <list>
#include <map> #include <map>
#include <memory>
#include <set>
#include "base/macros.h" #include "base/macros.h"
#include "base/optional.h" #include "base/optional.h"
...@@ -15,6 +17,7 @@ ...@@ -15,6 +17,7 @@
#include "ui/gfx/font_list.h" #include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
#include "ui/gfx/range/range.h" #include "ui/gfx/range/range.h"
#include "ui/gfx/text_constants.h"
#include "ui/views/controls/link_listener.h" #include "ui/views/controls/link_listener.h"
#include "ui/views/style/typography.h" #include "ui/views/style/typography.h"
#include "ui/views/view.h" #include "ui/views/view.h"
...@@ -62,6 +65,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -62,6 +65,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// If set, the whole range will be put on a single line. // If set, the whole range will be put on a single line.
bool disable_line_wrapping = false; 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. // Note that any trailing whitespace in |text| will be trimmed.
...@@ -81,6 +88,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -81,6 +88,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// |range| must be contained in |text_|. // |range| must be contained in |text_|.
void AddStyleRange(const gfx::Range& range, const RangeStyleInfo& style_info); 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. // Set the context of this text. All ranges have the same context.
// |text_context| must be a value from views::style::TextContext. // |text_context| must be a value from views::style::TextContext.
void SetTextContext(int text_context); void SetTextContext(int text_context);
...@@ -125,6 +135,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -125,6 +135,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// LinkListener implementation: // LinkListener implementation:
void LinkClicked(Link* source, int event_flags) override; 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: private:
struct StyleRange { struct StyleRange {
StyleRange(const gfx::Range& range, StyleRange(const gfx::Range& range,
...@@ -155,6 +168,15 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -155,6 +168,15 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// |width_at_last_size_calculation_|. Returns the needed size. // |width_at_last_size_calculation_|. Returns the needed size.
gfx::Size CalculateAndDoLayout(int width, bool dry_run); 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. // The text to display.
base::string16 text_; base::string16 text_;
...@@ -174,6 +196,9 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -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. // that correspond to ranges with is_link style set will be added to the map.
std::map<View*, gfx::Range> link_targets_; 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 // This variable saves the result of the last GetHeightForWidth call in order
// to avoid repeated calculation. // to avoid repeated calculation.
mutable gfx::Size calculated_size_; mutable gfx::Size calculated_size_;
...@@ -188,6 +213,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { ...@@ -188,6 +213,10 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener {
// background. // background.
bool auto_color_readability_enabled_; 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); DISALLOW_COPY_AND_ASSIGN(StyledLabel);
}; };
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "ui/views/controls/styled_label_listener.h" #include "ui/views/controls/styled_label_listener.h"
#include "ui/views/style/typography.h" #include "ui/views/style/typography.h"
#include "ui/views/test/test_layout_provider.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/test/views_test_base.h"
#include "ui/views/widget/widget.h" #include "ui/views/widget/widget.h"
...@@ -282,8 +283,7 @@ TEST_P(MDStyledLabelTest, DontBreakLinks) { ...@@ -282,8 +283,7 @@ TEST_P(MDStyledLabelTest, DontBreakLinks) {
const std::string link_text("and this should be a link"); const std::string link_text("and this should be a link");
InitStyledLabel(text + link_text); InitStyledLabel(text + link_text);
styled()->AddStyleRange( styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()), gfx::Range(text.size(), text.size() + link_text.size()),
static_cast<uint32_t>(text.size() + link_text.size())),
StyledLabel::RangeStyleInfo::CreateForLink()); StyledLabel::RangeStyleInfo::CreateForLink());
Label label(ASCIIToUTF16(text + link_text.substr(0, link_text.size() / 2))); Label label(ASCIIToUTF16(text + link_text.substr(0, link_text.size() / 2)));
...@@ -316,8 +316,7 @@ TEST_F(StyledLabelTest, StyledRangeWithDisabledLineWrapping) { ...@@ -316,8 +316,7 @@ TEST_F(StyledLabelTest, StyledRangeWithDisabledLineWrapping) {
StyledLabel::RangeStyleInfo style_info; StyledLabel::RangeStyleInfo style_info;
style_info.disable_line_wrapping = true; style_info.disable_line_wrapping = true;
styled()->AddStyleRange( styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()), gfx::Range(text.size(), text.size() + unbreakable_text.size()),
static_cast<uint32_t>(text.size() + unbreakable_text.size())),
style_info); style_info);
Label label(ASCIIToUTF16( Label label(ASCIIToUTF16(
...@@ -343,8 +342,7 @@ TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) { ...@@ -343,8 +342,7 @@ TEST_F(StyledLabelTest, StyledRangeCustomFontUnderlined) {
style_info.custom_font = style_info.custom_font =
styled()->GetDefaultFontList().DeriveWithStyle(gfx::Font::UNDERLINE); styled()->GetDefaultFontList().DeriveWithStyle(gfx::Font::UNDERLINE);
styled()->AddStyleRange( styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text.size()), gfx::Range(text.size(), text.size() + underlined_text.size()),
static_cast<uint32_t>(text.size() + underlined_text.size())),
style_info); style_info);
styled()->SetBounds(0, 0, 1000, 1000); styled()->SetBounds(0, 0, 1000, 1000);
...@@ -372,8 +370,7 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) { ...@@ -372,8 +370,7 @@ TEST_F(StyledLabelTest, StyledRangeTextStyleBold) {
StyledLabel::RangeStyleInfo style_info; StyledLabel::RangeStyleInfo style_info;
style_info.text_style = style::STYLE_DISABLED; style_info.text_style = style::STYLE_DISABLED;
styled()->AddStyleRange( styled()->AddStyleRange(gfx::Range(0u, bold_text.size()), style_info);
gfx::Range(0u, static_cast<uint32_t>(bold_text.size())), style_info);
// Calculate the bold text width if it were a pure label view, both with bold // Calculate the bold text width if it were a pure label view, both with bold
// and normal style. // and normal style.
...@@ -434,14 +431,12 @@ TEST_F(StyledLabelTest, Color) { ...@@ -434,14 +431,12 @@ TEST_F(StyledLabelTest, Color) {
StyledLabel::RangeStyleInfo style_info_red; StyledLabel::RangeStyleInfo style_info_red;
style_info_red.override_color = SK_ColorRED; style_info_red.override_color = SK_ColorRED;
styled()->AddStyleRange( styled()->AddStyleRange(gfx::Range(0u, text_red.size()), style_info_red);
gfx::Range(0u, static_cast<uint32_t>(text_red.size())), style_info_red);
StyledLabel::RangeStyleInfo style_info_link = StyledLabel::RangeStyleInfo style_info_link =
StyledLabel::RangeStyleInfo::CreateForLink(); StyledLabel::RangeStyleInfo::CreateForLink();
styled()->AddStyleRange( styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(text_red.size()), gfx::Range(text_red.size(), text_red.size() + text_link.size()),
static_cast<uint32_t>(text_red.size() + text_link.size())),
style_info_link); style_info_link);
styled()->SetBounds(0, 0, 1000, 1000); styled()->SetBounds(0, 0, 1000, 1000);
...@@ -498,13 +493,10 @@ TEST_P(MDStyledLabelTest, StyledRangeWithTooltip) { ...@@ -498,13 +493,10 @@ TEST_P(MDStyledLabelTest, StyledRangeWithTooltip) {
StyledLabel::RangeStyleInfo tooltip_style; StyledLabel::RangeStyleInfo tooltip_style;
tooltip_style.tooltip = ASCIIToUTF16("tooltip"); tooltip_style.tooltip = ASCIIToUTF16("tooltip");
styled()->AddStyleRange( styled()->AddStyleRange(
gfx::Range(static_cast<uint32_t>(tooltip_start), gfx::Range(tooltip_start, tooltip_start + tooltip_text.size()),
static_cast<uint32_t>(tooltip_start + tooltip_text.size())),
tooltip_style); tooltip_style);
styled()->AddStyleRange( styled()->AddStyleRange(gfx::Range(link_start, link_start + link_text.size()),
gfx::Range(static_cast<uint32_t>(link_start), StyledLabel::RangeStyleInfo::CreateForLink());
static_cast<uint32_t>(link_start + link_text.size())),
StyledLabel::RangeStyleInfo::CreateForLink());
// Break line inside the range with the tooltip. // Break line inside the range with the tooltip.
Label label(ASCIIToUTF16( Label label(ASCIIToUTF16(
...@@ -652,6 +644,147 @@ TEST_F(StyledLabelTest, Border) { ...@@ -652,6 +644,147 @@ TEST_F(StyledLabelTest, Border) {
styled()->GetPreferredSize().width()); 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(, INSTANTIATE_TEST_CASE_P(,
MDStyledLabelTest, MDStyledLabelTest,
::testing::Values(SecondaryUiMode::MD, ::testing::Values(SecondaryUiMode::MD,
......
...@@ -264,6 +264,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, ...@@ -264,6 +264,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate,
// By default a View is owned by its parent unless specified otherwise here. // By default a View is owned by its parent unless specified otherwise here.
void set_owned_by_client() { owned_by_client_ = true; } void set_owned_by_client() { owned_by_client_ = true; }
bool owned_by_client() const { return owned_by_client_; }
// Tree operations ----------------------------------------------------------- // 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