1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_H_
6 #define UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_H_
7 
8 #include <array>
9 #include <memory>
10 
11 #include "base/bind.h"
12 #include "base/compiler_specific.h"
13 #include "base/gtest_prod_util.h"
14 #include "base/macros.h"
15 #include "base/optional.h"
16 #include "third_party/skia/include/core/SkColor.h"
17 #include "ui/gfx/image/image_skia.h"
18 #include "ui/views/controls/button/button.h"
19 #include "ui/views/controls/button/label_button_label.h"
20 #include "ui/views/controls/image_view.h"
21 #include "ui/views/controls/label.h"
22 #include "ui/views/layout/layout_provider.h"
23 #include "ui/views/metadata/view_factory.h"
24 #include "ui/views/native_theme_delegate.h"
25 #include "ui/views/style/typography.h"
26 #include "ui/views/widget/widget.h"
27 
28 namespace views {
29 
30 class InkDropContainerView;
31 class LabelButtonBorder;
32 
33 // LabelButton is a button with text and an icon.
34 class VIEWS_EXPORT LabelButton : public Button, public NativeThemeDelegate {
35  public:
36   METADATA_HEADER(LabelButton);
37 
38   // Creates a LabelButton with pressed events sent to |callback| and label
39   // |text|. |button_context| is a value from views::style::TextContext and
40   // determines the appearance of |text|.
41   explicit LabelButton(PressedCallback callback = PressedCallback(),
42                        const base::string16& text = base::string16(),
43                        int button_context = style::CONTEXT_BUTTON);
44   LabelButton(const LabelButton&) = delete;
45   LabelButton& operator=(const LabelButton&) = delete;
46   ~LabelButton() override;
47 
48   // Gets or sets the image shown for the specified button state.
49   // GetImage returns the image for STATE_NORMAL if the state's image is empty.
50   virtual gfx::ImageSkia GetImage(ButtonState for_state) const;
51   // TODO(http://crbug.com/1100034) prefer SetImageModel over SetImage().
52   void SetImage(ButtonState for_state, const gfx::ImageSkia& image);
53   void SetImageModel(ButtonState for_state, const ui::ImageModel& image_model);
54 
55   // Gets or sets the text shown on the button.
56   const base::string16& GetText() const;
57   virtual void SetText(const base::string16& text);
58 
59   // Makes the button report its preferred size without the label. This lets
60   // AnimatingLayoutManager gradually shrink the button until the text is
61   // invisible, at which point the text gets cleared. Think of this as
62   // transitioning from the current text to SetText("").
63   // Note that the layout manager (or manual SetBounds calls) need to be
64   // configured to eventually hit the the button's preferred size (or smaller)
65   // or the text will never be cleared.
66   void ShrinkDownThenClearText();
67 
68   // Sets the text color shown for the specified button |for_state| to |color|.
69   void SetTextColor(ButtonState for_state, SkColor color);
70 
71   // Sets the text colors shown for the non-disabled states to |color|.
72   virtual void SetEnabledTextColors(base::Optional<SkColor> color);
73 
74   // Gets the current state text color.
75   SkColor GetCurrentTextColor() const;
76 
77   // Sets drop shadows underneath the text.
78   void SetTextShadows(const gfx::ShadowValues& shadows);
79 
80   // Sets whether subpixel rendering is used on the label.
81   void SetTextSubpixelRenderingEnabled(bool enabled);
82 
83   // Sets the elide behavior of this button.
84   void SetElideBehavior(gfx::ElideBehavior elide_behavior);
85 
86   // Sets the horizontal alignment used for the button; reversed in RTL. The
87   // optional image will lead the text, unless the button is right-aligned.
88   void SetHorizontalAlignment(gfx::HorizontalAlignment alignment);
89   gfx::HorizontalAlignment GetHorizontalAlignment() const;
90 
91   gfx::Size GetMinSize() const;
92   void SetMinSize(const gfx::Size& min_size);
93 
94   gfx::Size GetMaxSize() const;
95   void SetMaxSize(const gfx::Size& max_size);
96 
97   // Gets or sets the option to handle the return key; false by default.
98   bool GetIsDefault() const;
99   void SetIsDefault(bool is_default);
100 
101   // Sets the spacing between the image and the text.
102   int GetImageLabelSpacing() const;
103   void SetImageLabelSpacing(int spacing);
104 
105   // Gets or sets the option to place the image aligned with the center of the
106   // the label. The image is not centered for CheckBox and RadioButton only.
107   bool GetImageCentered() const;
108   void SetImageCentered(bool image_centered);
109 
110   // Creates the default border for this button. This can be overridden by
111   // subclasses.
112   virtual std::unique_ptr<LabelButtonBorder> CreateDefaultBorder() const;
113 
114   // Button:
115   void SetBorder(std::unique_ptr<Border> border) override;
116   void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
117   gfx::Size CalculatePreferredSize() const override;
118   gfx::Size GetMinimumSize() const override;
119   int GetHeightForWidth(int w) const override;
120   void Layout() override;
121   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
122   void AddLayerBeneathView(ui::Layer* new_layer) override;
123   void RemoveLayerBeneathView(ui::Layer* old_layer) override;
124 
125   // NativeThemeDelegate:
126   ui::NativeTheme::Part GetThemePart() const override;
127   gfx::Rect GetThemePaintRect() const override;
128   ui::NativeTheme::State GetThemeState(
129       ui::NativeTheme::ExtraParams* params) const override;
130   const gfx::Animation* GetThemeAnimation() const override;
131   ui::NativeTheme::State GetBackgroundThemeState(
132       ui::NativeTheme::ExtraParams* params) const override;
133   ui::NativeTheme::State GetForegroundThemeState(
134       ui::NativeTheme::ExtraParams* params) const override;
135 
136  protected:
image()137   ImageView* image() const { return image_; }
label()138   Label* label() const { return label_; }
ink_drop_container()139   InkDropContainerView* ink_drop_container() const {
140     return ink_drop_container_;
141   }
142 
explicitly_set_normal_color()143   bool explicitly_set_normal_color() const {
144     return explicitly_set_colors_[STATE_NORMAL];
145   }
146 
explicitly_set_colors()147   const std::array<bool, STATE_COUNT>& explicitly_set_colors() const {
148     return explicitly_set_colors_;
149   }
set_explicitly_set_colors(const std::array<bool,STATE_COUNT> & colors)150   void set_explicitly_set_colors(const std::array<bool, STATE_COUNT>& colors) {
151     explicitly_set_colors_ = colors;
152   }
153 
154   // Updates the image view to contain the appropriate button state image.
155   void UpdateImage();
156 
157   // Updates the background color, if the background color is state-sensitive.
UpdateBackgroundColor()158   virtual void UpdateBackgroundColor() {}
159 
160   // Returns the current visual appearance of the button. This takes into
161   // account both the button's underlying state, the state of the containing
162   // widget, and the parent of the containing widget.
163   ButtonState GetVisualState() const;
164 
165   // Fills |params| with information about the button.
166   virtual void GetExtraParams(ui::NativeTheme::ExtraParams* params) const;
167 
168   // Changes the visual styling to match changes in the default state.  Returns
169   // the PropertyEffects triggered as a result.
170   virtual PropertyEffects UpdateStyleToIndicateDefaultStatus();
171 
172   // Button:
173   void ChildPreferredSizeChanged(View* child) override;
174   void AddedToWidget() override;
175   void RemovedFromWidget() override;
176   void OnFocus() override;
177   void OnBlur() override;
178   void OnThemeChanged() override;
179   void StateChanged(ButtonState old_state) override;
180 
181  private:
182   void SetTextInternal(const base::string16& text);
183 
184   void ClearTextIfShrunkDown();
185 
186   // Gets the preferred size (without respecting min_size_ or max_size_), but
187   // does not account for the label. This is shared between GetHeightForWidth
188   // and CalculatePreferredSize. GetHeightForWidth will subtract the width
189   // returned from this method to get width available for the label while
190   // CalculatePreferredSize will just add the preferred width from the label.
191   // Both methods will then use the max of inset height + label height and this
192   // height as total height, and clamp to min/max sizes as appropriate.
193   gfx::Size GetUnclampedSizeWithoutLabel() const;
194 
195   // Updates the portions of the object that might change in response to a
196   // change in the value returned by GetVisualState().
197   void VisualStateChanged();
198 
199   // Resets colors from the NativeTheme, explicitly set colors are unchanged.
200   void ResetColorsFromNativeTheme();
201 
202   // Updates additional state related to focus or default status, rather than
203   // merely the Button::state(). E.g. ensures the label text color is
204   // correct for the current background.
205   void ResetLabelEnabledColor();
206 
207   // Returns the state whose image is shown for |for_state|, by falling back to
208   // STATE_NORMAL when |for_state|'s image is empty.
209   ButtonState ImageStateForState(ButtonState for_state) const;
210 
211   void FlipCanvasOnPaintForRTLUIChanged();
212 
213   // The image and label shown in the button.
214   ImageView* image_;
215   internal::LabelButtonLabel* label_;
216 
217   // A separate view is necessary to hold the ink drop layer so that it can
218   // be stacked below |image_| and on top of |label_|, without resorting to
219   // drawing |label_| on a layer (which can mess with subpixel anti-aliasing).
220   InkDropContainerView* ink_drop_container_;
221 
222   // The cached font lists in the normal and default button style. The latter
223   // may be bold.
224   gfx::FontList cached_normal_font_list_;
225   gfx::FontList cached_default_button_font_list_;
226 
227   // The image models and colors for each button state.
228   ui::ImageModel button_state_image_models_[STATE_COUNT] = {};
229   SkColor button_state_colors_[STATE_COUNT] = {};
230 
231   // Used to track whether SetTextColor() has been invoked.
232   std::array<bool, STATE_COUNT> explicitly_set_colors_ = {};
233 
234   // |min_size_| and |max_size_| may be set to clamp the preferred size.
235   gfx::Size min_size_;
236   gfx::Size max_size_;
237 
238   // A flag indicating that this button should not include the label in its
239   // desired size. Furthermore, once the bounds of the button adapt to this
240   // desired size, the text in the label should get cleared.
241   bool shrinking_down_label_ = false;
242 
243   // Flag indicating default handling of the return key via an accelerator.
244   // Whether or not the button appears or behaves as the default button in its
245   // current context;
246   bool is_default_ = false;
247 
248   // True if current border was set by UpdateThemedBorder.
249   bool border_is_themed_border_ = true;
250 
251   // A flag indicating that this button's image should be aligned with the
252   // center of the label when multiline is enabled. This shouldn't be the case
253   // for a CheckBox or a RadioButton.
254   bool image_centered_ = true;
255 
256   // Spacing between the image and the text.
257   int image_label_spacing_ = LayoutProvider::Get()->GetDistanceMetric(
258       DISTANCE_RELATED_LABEL_HORIZONTAL);
259 
260   // Alignment of the button. This can be different from the alignment of the
261   // text; for example, the label may be set to ALIGN_TO_HEAD (alignment matches
262   // text direction) while |this| is laid out as ALIGN_LEFT (alignment matches
263   // UI direction).
264   gfx::HorizontalAlignment horizontal_alignment_ = gfx::ALIGN_LEFT;
265 
266   std::unique_ptr<Widget::PaintAsActiveCallbackList::Subscription>
267       paint_as_active_subscription_;
268 
269   PropertyChangedSubscription flip_canvas_on_paint_subscription_ =
270       AddFlipCanvasOnPaintForRTLUIChangedCallback(
271           base::BindRepeating(&LabelButton::FlipCanvasOnPaintForRTLUIChanged,
272                               base::Unretained(this)));
273 };
274 
275 BEGIN_VIEW_BUILDER(VIEWS_EXPORT, LabelButton, Button)
276 VIEW_BUILDER_PROPERTY(base::string16, Text)
277 VIEW_BUILDER_PROPERTY(gfx::HorizontalAlignment, HorizontalAlignment)
278 VIEW_BUILDER_PROPERTY(gfx::Size, MinSize)
279 VIEW_BUILDER_PROPERTY(gfx::Size, MaxSize)
280 VIEW_BUILDER_PROPERTY(bool, IsDefault)
281 VIEW_BUILDER_PROPERTY(int, ImageLabelSpacing)
282 VIEW_BUILDER_PROPERTY(bool, ImageCentered)
283 END_VIEW_BUILDER
284 
285 }  // namespace views
286 
287 DEFINE_VIEW_BUILDER(VIEWS_EXPORT, LabelButton)
288 
289 #endif  // UI_VIEWS_CONTROLS_BUTTON_LABEL_BUTTON_H_
290