diff options
author | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-07 23:46:53 +0000 |
---|---|---|
committer | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-07 23:46:53 +0000 |
commit | 7995319dd2575f5f8e9b63f1ad1e6b36480cef4d (patch) | |
tree | e5a77eac3a85bcc9f1a9a822f5d0e64cc9247180 | |
parent | c90aeec875b69a034e64240b3d2892302a4fdc59 (diff) | |
download | chromium_src-7995319dd2575f5f8e9b63f1ad1e6b36480cef4d.zip chromium_src-7995319dd2575f5f8e9b63f1ad1e6b36480cef4d.tar.gz chromium_src-7995319dd2575f5f8e9b63f1ad1e6b36480cef4d.tar.bz2 |
Fade tab titles
This change fades tab titles instead of truncating them with a "...".
I tried to copy the Mac implemenation from here:
http://code.google.com/p/google-toolbox-for-mac/source/browse/trunk/AppKit/GTMFadeTruncatingTextFieldCell.m
but my version is not as good since I couldn't figure out how to draw text on a transparent background.
BUG=69301
TEST=Ran Chromium and verified that things looked ok.
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=80416
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=80748
Review URL: http://codereview.chromium.org/6695043
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@80870 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/ui/views/tabs/base_tab.cc | 25 | ||||
-rw-r--r-- | ui/gfx/canvas_skia.h | 19 | ||||
-rw-r--r-- | ui/gfx/canvas_skia_win.cc | 284 |
3 files changed, 317 insertions, 11 deletions
diff --git a/chrome/browser/ui/views/tabs/base_tab.cc b/chrome/browser/ui/views/tabs/base_tab.cc index 327fd8e..2e9f222 100644 --- a/chrome/browser/ui/views/tabs/base_tab.cc +++ b/chrome/browser/ui/views/tabs/base_tab.cc @@ -472,6 +472,9 @@ void BaseTab::PaintTitle(gfx::Canvas* canvas, SkColor title_color) { // Paint the Title. const gfx::Rect& title_bounds = GetTitleBounds(); string16 title = data().title; + bool should_truncate = false; + int prefix_length = 0; + if (title.empty()) { title = data().loading ? l10n_util::GetStringUTF16(IDS_TAB_LOADING_TITLE) : @@ -481,18 +484,26 @@ void BaseTab::PaintTitle(gfx::Canvas* canvas, SkColor title_color) { // If we'll need to truncate, check if we should also truncate // a common prefix, but only if there is enough room for it. // We arbitrarily choose to request enough room for 6 average chars. - if (data().common_prefix_length > TitlePrefixMatcher::kMinElidingLength && + should_truncate = + data().common_prefix_length > TitlePrefixMatcher::kMinElidingLength && font_->GetExpectedTextWidth(6) < title_bounds.width() && - font_->GetStringWidth(title) > title_bounds.width()) { - title.replace(0, - data().common_prefix_length - - TitlePrefixMatcher::kCommonCharsToShow, - UTF8ToUTF16(ui::kEllipsis)); - } + font_->GetStringWidth(title) > title_bounds.width(); + prefix_length = data().common_prefix_length - + TitlePrefixMatcher::kCommonCharsToShow; } + +#if defined(OS_WIN) + canvas->AsCanvasSkia()->DrawFadeTruncatingString(title, + should_truncate ? gfx::CanvasSkia::TruncateFadeHeadAndTail : + gfx::CanvasSkia::TruncateFadeTail, + prefix_length, *font_, title_color, title_bounds); +#else + if (should_truncate) + title.replace(0, prefix_length, UTF8ToUTF16(ui::kEllipsis)); canvas->DrawStringInt(title, *font_, title_color, title_bounds.x(), title_bounds.y(), title_bounds.width(), title_bounds.height()); +#endif } void BaseTab::AnimationProgressed(const ui::Animation* animation) { diff --git a/ui/gfx/canvas_skia.h b/ui/gfx/canvas_skia.h index 9516e8a3..4811a81 100644 --- a/ui/gfx/canvas_skia.h +++ b/ui/gfx/canvas_skia.h @@ -37,6 +37,12 @@ class Canvas; class CanvasSkia : public skia::PlatformCanvas, public Canvas { public: + enum TruncateFadeMode { + TruncateFadeTail, + TruncateFadeHead, + TruncateFadeHeadAndTail, + }; + // Creates an empty Canvas. Callers must use initialize before using the // canvas. CanvasSkia(); @@ -132,6 +138,19 @@ class CanvasSkia : public skia::PlatformCanvas, const SkColor& color, int x, int y, int w, int h, int flags); +#if defined(OS_WIN) + // Draws the given string with the beginning and/or the end using a fade + // gradient. When truncating the head + // |desired_characters_to_truncate_from_head| specifies the maximum number of + // characters that can be truncated. + virtual void DrawFadeTruncatingString( + const string16& text, + TruncateFadeMode truncate_mode, + size_t desired_characters_to_truncate_from_head, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect); +#endif virtual void DrawFocusRect(int x, int y, int width, int height); virtual void TileImageInt(const SkBitmap& bitmap, int x, int y, int w, int h); virtual void TileImageInt(const SkBitmap& bitmap, diff --git a/ui/gfx/canvas_skia_win.cc b/ui/gfx/canvas_skia_win.cc index b343234..41a4f44 100644 --- a/ui/gfx/canvas_skia_win.cc +++ b/ui/gfx/canvas_skia_win.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,12 +7,21 @@ #include <limits> #include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "skia/ext/bitmap_platform_device.h" +#include "skia/ext/skia_utils_win.h" #include "third_party/skia/include/core/SkShader.h" #include "ui/gfx/font.h" #include "ui/gfx/rect.h" namespace { +static inline int Round(double x) { + // Why oh why is this not in a standard header? + return static_cast<int>(floor(x + 0.5)); +} + // We make sure that LTR text we draw in an RTL context is modified // appropriately to make sure it maintains it LTR orientation. void DoDrawText(HDC hdc, @@ -124,6 +133,143 @@ int ComputeFormatFlags(int flags, const string16& text) { return f; } +// Changes the alpha of the given bitmap. +// If |fade_to_right| is true then the rect fades from opaque to clear, +// otherwise the rect fades from clear to opaque. +void FadeBitmapRect(skia::BitmapPlatformDevice& bmp_device, + const gfx::Rect& rect, + bool fade_to_right) { + SkBitmap bmp = bmp_device.accessBitmap(true); + DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config()); + SkAutoLockPixels lock(bmp); + float total_width = static_cast<float>(rect.width()); + + for (int x = rect.x(); x < rect.right(); x++) { + float cur_width = static_cast<float>(fade_to_right ? + rect.right() - x : x - rect.x()); + // We want the fade effect to go from 0.2 to 1.0. + float alpha_percent = ((cur_width / total_width) * 0.8f) + 0.2f; + + for (int y = rect.y(); y < rect.bottom(); y++) { + SkColor color = bmp.getColor(x, y); + SkAlpha alpha = static_cast<SkAlpha>(SkColorGetA(color) * alpha_percent); + *bmp.getAddr32(x, y) = SkPreMultiplyColor(SkColorSetA(color, alpha)); + } + } +} + +// DrawText() doesn't support alpha channels. To create a transparent background +// this function draws black on white. It then uses the intensity of black +// to determine how much alpha to use. The text is drawn in |gfx_text_rect| and +// clipped to |gfx_draw_rect|. +void DrawTextAndClearBackground(skia::BitmapPlatformDevice& bmp_device, + HFONT font, + COLORREF text_color, + const string16& text, + int flags, + const gfx::Rect& gfx_text_rect, + const gfx::Rect& gfx_draw_rect) { + HDC hdc = bmp_device.BeginPlatformPaint(); + + // Clear the background by filling with white. + HBRUSH fill_brush = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)); + HANDLE old_brush = SelectObject(hdc, fill_brush); + RECT draw_rect = gfx_draw_rect.ToRECT(); + FillRect(hdc, &draw_rect, fill_brush); + SelectObject(hdc, old_brush); + + // Set black text with trasparent background. + SetBkMode(hdc, TRANSPARENT); + SetTextColor(hdc, 0); + + // Draw the text. + int save_dc_id = SaveDC(hdc); + // Clip the text to the draw destination. + IntersectClipRect(hdc, draw_rect.left, draw_rect.top, + draw_rect.right, draw_rect.bottom); + SelectObject(hdc, font); + RECT text_rect = gfx_text_rect.ToRECT(); + DoDrawText(hdc, text, &text_rect, + ComputeFormatFlags(flags, text)); + RestoreDC(hdc, save_dc_id); + + BYTE text_color_r = GetRValue(text_color); + BYTE text_color_g = GetGValue(text_color); + BYTE text_color_b = GetBValue(text_color); + + SkBitmap bmp = bmp_device.accessBitmap(true); + DCHECK_EQ(SkBitmap::kARGB_8888_Config, bmp.config()); + SkAutoLockPixels lock(bmp); + + // At this point the bitmap has black text on white. + // The intensity of black tells us the alpha value of the text. + for (int y = draw_rect.top; y < draw_rect.bottom; y++) { + for (int x = draw_rect.left; x < draw_rect.right; x++) { + // Gets the color directly. DrawText doesn't premultiply alpha so + // using SkBitmap::getColor() won't work here. + SkColor color = *bmp.getAddr32(x, y); + // Use any of the color channels as alpha. + BYTE alpha = 0xFF - SkColorGetB(color); + *bmp.getAddr32(x, y) = SkPreMultiplyColor( + SkColorSetARGB(alpha, text_color_r, text_color_b, text_color_g)); + } + } + + bmp_device.EndPlatformPaint(); +} + +// Draws the given text with a fade out gradient. |bmp_device| is a bitmap +// that is used to temporary drawing. The text is drawn in |text_rect| and +// clipped to |draw_rect|. +void DrawTextGradientPart(HDC hdc, + skia::BitmapPlatformDevice& bmp_device, + const string16& text, + const SkColor& color, + HFONT font, + const gfx::Rect& text_rect, + const gfx::Rect& draw_rect, + bool fade_to_right, + int flags) { + DrawTextAndClearBackground(bmp_device, font, skia::SkColorToCOLORREF(color), + text, flags, text_rect, draw_rect); + FadeBitmapRect(bmp_device, draw_rect, fade_to_right); + BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + + HDC bmp_hdc = bmp_device.BeginPlatformPaint(); + AlphaBlend(hdc, draw_rect.x(), draw_rect.y(), draw_rect.width(), + draw_rect.height(), bmp_hdc, draw_rect.x(), draw_rect.y(), + draw_rect.width(), draw_rect.height(), blend); + bmp_device.EndPlatformPaint(); +} + +enum PrimarySide { + PrimaryOnLeft, + PrimaryOnRight, +}; + +// Divides |rect| horizontally into a |primary| of width |primary_width| and a +// |secondary| taking up the remainder. +void DivideRect(const gfx::Rect& rect, + PrimarySide primary_side, + int primary_width, + gfx::Rect* primary, + gfx::Rect* secondary) { + *primary = rect; + *secondary = rect; + int remainder = rect.width() - primary_width; + + switch (primary_side) { + case PrimaryOnLeft: + primary->Inset(0, 0, remainder, 0); + secondary->Inset(primary_width, 0, 0, 0); + break; + case PrimaryOnRight: + primary->Inset(remainder, 0, 0, 0); + secondary->Inset(0, 0, primary_width, 0); + break; + } +} + } // anonymous namespace namespace gfx { @@ -182,7 +328,13 @@ void CanvasSkia::DrawStringInt(const string16& text, const SkColor& color, int x, int y, int w, int h, int flags) { - if (!IntersectsClipRectInt(x, y, w, h)) + SkRect fclip; + if (!getClipBounds(&fclip)) + return; + RECT text_bounds = { x, y, x + w, y + h }; + SkIRect clip; + fclip.round(&clip); + if (!clip.intersect(skia::RECTToSkIRect(text_bounds))) return; // Clamp the max amount of text we'll draw to 32K. There seem to be bugs in @@ -192,7 +344,6 @@ void CanvasSkia::DrawStringInt(const string16& text, const int kMaxStringLength = 32768 - 1; // So the trailing \0 fits in 32K. string16 clamped_string(text.substr(0, kMaxStringLength)); - RECT text_bounds = { x, y, x + w, y + h }; HDC dc = beginPlatformPaint(); SetBkMode(dc, TRANSPARENT); HFONT old_font = (HFONT)SelectObject(dc, font); @@ -211,7 +362,8 @@ void CanvasSkia::DrawStringInt(const string16& text, // Windows will have cleared the alpha channel of the text we drew. Assume // we're drawing to an opaque surface, or at least the text rect area is // opaque. - getTopPlatformDevice().makeOpaque(x, y, w, h); + getTopPlatformDevice().makeOpaque(clip.fLeft, clip.fTop, + clip.width(), clip.height()); } void CanvasSkia::DrawStringInt(const string16& text, @@ -303,4 +455,128 @@ ui::TextureID CanvasSkia::GetTextureID() { return 0; } +void CanvasSkia::DrawFadeTruncatingString( + const string16& text, + CanvasSkia::TruncateFadeMode truncate_mode, + size_t desired_characters_to_truncate_from_head, + const gfx::Font& font, + const SkColor& color, + const gfx::Rect& display_rect) { + int flags = NO_ELLIPSIS; + + // If the whole string fits in the destination then just draw it directly. + int total_string_width; + int total_string_height; + SizeStringInt(text, font, &total_string_width, &total_string_height, + flags | TEXT_VALIGN_TOP); + if (total_string_width <= display_rect.width()) { + DrawStringInt(text, font, color, display_rect.x(), display_rect.y(), + display_rect.width(), display_rect.height(), flags); + return; + } + + int average_character_width = font.GetAverageCharacterWidth(); + int clipped_string_width = total_string_width - display_rect.width(); + + // Clip the string by drawing it to the left by |offset_x|. + int offset_x = 0; + switch (truncate_mode) { + case TruncateFadeHead: + offset_x = clipped_string_width; + break; + case TruncateFadeHeadAndTail: + DCHECK_GT(desired_characters_to_truncate_from_head, 0u); + // Get the width of the beginning of the string we're clipping. + string16 clipped_head_string = + text.substr(0, desired_characters_to_truncate_from_head); + int clipped_width; + int clipped_height; + SizeStringInt(clipped_head_string, font, + &clipped_width, &clipped_height, flags); + + // This is the offset at which we start drawing. This causes the + // beginning of the string to get clipped. + offset_x = clipped_width; + + // Due to the fade effect the first character is hard to see. + // We want to make sure that the first character starting at + // |desired_characters_to_truncate_from_head| is readable so we reduce + // the offset by a little bit. + offset_x = std::max(0, Round(offset_x - average_character_width * 2)); + + // If the offset is so large that there's empty space at the tail + // then reduce the offset so we can use up the empty space. + offset_x = std::min(offset_x, clipped_string_width); + break; + } + bool is_truncating_head = offset_x > 0; + bool is_truncating_tail = clipped_string_width > offset_x; + + bool is_rtl = (ComputeFormatFlags(flags, text) & DT_RTLREADING) != 0; + // |is_rtl| tells us if the given text is right to left or not. |is_rtl| can + // be false even if the UI is set to a right to left language. + // Now, normally, we right align all text if the UI is set to a right to + // left language. In this case though we don't want that because we render + // the ends of the string ourselves. + if (!is_rtl) + flags |= TEXT_ALIGN_LEFT; + + // Fade in/out about 2.5 characters of the beginning/end of the string. + // The .5 here is helpful if one of the characters is a space. + // Use a quarter of the display width if the string is very short. + int gradient_width = Round(std::min(average_character_width * 2.5, + display_rect.width() / 4.0)); + + // Move the origin to |display_rect.origin()|. This simplifies all the + // drawing so that both the source and destination can be (0,0). + save(kMatrix_SaveFlag); + TranslateInt(display_rect.x(), display_rect.y()); + + gfx::Rect solid_part(gfx::Point(), display_rect.size()); + gfx::Rect head_part; + gfx::Rect tail_part; + if (is_truncating_head) + DivideRect(solid_part, is_rtl ? PrimaryOnRight : PrimaryOnLeft, + gradient_width, &head_part, &solid_part); + if (is_truncating_tail) + DivideRect(solid_part, is_rtl ? PrimaryOnLeft : PrimaryOnRight, + gradient_width, &tail_part, &solid_part); + + // Grow |display_rect| by |offset_x|. + gfx::Rect text_rect(gfx::Point(), display_rect.size()); + if (!is_rtl) + text_rect.set_x(text_rect.x() - offset_x); + text_rect.set_width(text_rect.width() + offset_x); + + // Disable clear type. This makes the text render in gray scale which + // makes it easier to compute the alpha value. + LOGFONT font_info; + GetObject(font.GetNativeFont(), sizeof(font_info), &font_info); + font_info.lfQuality = ANTIALIASED_QUALITY; + HFONT gray_scale_font = CreateFontIndirect(&font_info); + + HDC hdc = beginPlatformPaint(); + scoped_ptr<skia::BitmapPlatformDevice> gradient_bitmap( + skia::BitmapPlatformDevice::create(NULL, display_rect.width(), + display_rect.height(), false, NULL)); + if (is_truncating_head) + DrawTextGradientPart(hdc, *gradient_bitmap, text, color, gray_scale_font, + text_rect, head_part, is_rtl, flags); + if (is_truncating_tail) + DrawTextGradientPart(hdc, *gradient_bitmap, text, color, gray_scale_font, + text_rect, tail_part, !is_rtl, flags); + endPlatformPaint(); + + // Draw the solid part. + save(kClip_SaveFlag); + ClipRectInt(solid_part.x(), solid_part.y(), + solid_part.width(), solid_part.height()); + DrawStringInt(text, font, color, + text_rect.x(), text_rect.y(), + text_rect.width(), text_rect.height(), + flags); + restore(); + restore(); +} + } // namespace gfx |