OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/views/controls/button/toggle_button.h" | 5 #include "ui/views/controls/button/toggle_button.h" |
6 | 6 |
7 #include "third_party/skia/include/core/SkDrawLooper.h" | 7 #include "third_party/skia/include/core/SkDrawLooper.h" |
8 #include "third_party/skia/include/core/SkPaint.h" | 8 #include "third_party/skia/include/core/SkPaint.h" |
9 #include "ui/gfx/canvas.h" | 9 #include "ui/gfx/canvas.h" |
10 #include "ui/gfx/color_palette.h" | 10 #include "ui/gfx/color_palette.h" |
(...skipping 10 matching lines...) Expand all Loading... | |
21 const int kTrackWidth = 28; | 21 const int kTrackWidth = 28; |
22 // Margins from edge of track to edge of view. | 22 // Margins from edge of track to edge of view. |
23 const int kTrackVerticalMargin = 5; | 23 const int kTrackVerticalMargin = 5; |
24 const int kTrackHorizontalMargin = 6; | 24 const int kTrackHorizontalMargin = 6; |
25 // Margin from edge of thumb to closest edge of view. Note that the thumb | 25 // Margin from edge of thumb to closest edge of view. Note that the thumb |
26 // margins must be sufficiently large to allow space for the shadow. | 26 // margins must be sufficiently large to allow space for the shadow. |
27 const int kThumbHorizontalMargin = 4; | 27 const int kThumbHorizontalMargin = 4; |
28 // Margin from top/bottom edge of thumb to top/bottom edge of view. | 28 // Margin from top/bottom edge of thumb to top/bottom edge of view. |
29 const int kThumbVerticalMargin = 3; | 29 const int kThumbVerticalMargin = 3; |
30 | 30 |
31 // TODO(estade): get the base color (black) from the theme? | |
32 const SkColor kTrackOffColor = | |
33 SkColorSetA(SK_ColorBLACK, gfx::kDisabledControlAlpha); | |
34 | |
35 } // namespace | 31 } // namespace |
36 | 32 |
37 // Class representing the thumb. When the thumb is clicked it is separated into | 33 // Class representing the thumb (the circle that slides horizontally). |
38 // its own layer and the ink drop layer is made a child of the thumb layer | 34 class ToggleButton::ThumbView : public InkDropHostView { |
39 // allowing the two to animate in sync. | |
40 class ToggleButton::ThumbView : public views::View { | |
41 public: | 35 public: |
42 ThumbView() : color_ratio_(0.) {} | 36 ThumbView() : color_ratio_(0.) {} |
43 ~ThumbView() override {} | 37 ~ThumbView() override {} |
44 | 38 |
45 void AddInkDropLayer(ui::Layer* ink_drop_layer) { | |
46 SetPaintToLayer(true); | |
47 layer()->SetFillsBoundsOpaquely(false); | |
48 layer()->Add(ink_drop_layer); | |
49 } | |
50 | |
51 void RemoveInkDropLayer(ui::Layer* ink_drop_layer) { | |
52 layer()->Remove(ink_drop_layer); | |
53 SetPaintToLayer(false); | |
54 } | |
55 | |
56 void Update(const gfx::Rect& bounds, double color_ratio) { | 39 void Update(const gfx::Rect& bounds, double color_ratio) { |
57 SetBoundsRect(bounds); | 40 SetBoundsRect(bounds); |
58 color_ratio_ = color_ratio; | 41 color_ratio_ = color_ratio; |
59 SchedulePaint(); | 42 SchedulePaint(); |
60 } | 43 } |
61 | 44 |
45 // Returns the extra space needed to draw the shadows around the thumb. Since | |
46 // the extra space is around the thumb, the insets will be negative. | |
47 static gfx::Insets GetShadowOutsets() { | |
48 return gfx::Insets(-kShadowBlur) | |
49 .Offset(gfx::Vector2d(kShadowOffsetX, kShadowOffsetY)); | |
50 } | |
51 | |
62 private: | 52 private: |
53 static const int kShadowOffsetX = 0; | |
54 static const int kShadowOffsetY = 1; | |
55 static const int kShadowBlur = 2; | |
56 | |
63 // views::View: | 57 // views::View: |
64 const char* GetClassName() const override { | 58 const char* GetClassName() const override { |
65 return "ToggleButton::ThumbView"; | 59 return "ToggleButton::ThumbView"; |
66 } | 60 } |
67 | 61 |
68 void OnPaint(gfx::Canvas* canvas) override { | 62 void OnPaint(gfx::Canvas* canvas) override { |
63 const float dsf = canvas->UndoDeviceScaleFactor(); | |
69 std::vector<gfx::ShadowValue> shadows; | 64 std::vector<gfx::ShadowValue> shadows; |
70 shadows.emplace_back(gfx::Vector2d(0, 1), 4.f, | 65 gfx::ShadowValue shadow( |
71 SkColorSetA(SK_ColorBLACK, 0x99)); | 66 gfx::Vector2d(kShadowOffsetX, kShadowOffsetY), 2 * kShadowBlur, |
67 SkColorSetA(GetNativeTheme()->GetSystemColor( | |
68 ui::NativeTheme::kColorId_LabelEnabledColor), | |
69 0x99)); | |
70 shadows.push_back(shadow.Scale(dsf)); | |
72 SkPaint thumb_paint; | 71 SkPaint thumb_paint; |
73 thumb_paint.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadows)); | 72 thumb_paint.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadows)); |
74 thumb_paint.setStyle(SkPaint::kFill_Style); | |
75 thumb_paint.setAntiAlias(true); | 73 thumb_paint.setAntiAlias(true); |
76 const SkColor thumb_on_color = GetNativeTheme()->GetSystemColor( | 74 const SkColor thumb_on_color = GetNativeTheme()->GetSystemColor( |
77 ui::NativeTheme::kColorId_ProminentButtonColor); | 75 ui::NativeTheme::kColorId_ProminentButtonColor); |
78 // TODO(estade): get this color from the theme? | 76 const SkColor thumb_off_color = GetNativeTheme()->GetSystemColor( |
79 const SkColor thumb_off_color = SK_ColorWHITE; | 77 ui::NativeTheme::kColorId_DialogBackground); |
80 const SkAlpha blend = static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio_); | 78 const SkAlpha blend = static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio_); |
81 thumb_paint.setColor( | 79 thumb_paint.setColor( |
82 color_utils::AlphaBlend(thumb_on_color, thumb_off_color, blend)); | 80 color_utils::AlphaBlend(thumb_on_color, thumb_off_color, blend)); |
83 gfx::Rect thumb_bounds = GetLocalBounds(); | 81 |
84 thumb_bounds.Inset(gfx::Insets(kThumbVerticalMargin)); | 82 // We want the circle to have an integer pixel diameter and to be aligned |
85 canvas->DrawCircle(gfx::RectF(thumb_bounds).CenterPoint(), | 83 // with pixel boundaries, so we scale dip bounds to pixel bounds and round. |
86 thumb_bounds.height() / 2.f, thumb_paint); | 84 gfx::RectF thumb_bounds(GetLocalBounds()); |
85 thumb_bounds.Inset(-GetShadowOutsets()); | |
86 thumb_bounds.Inset(gfx::InsetsF(0.5f)); | |
87 thumb_bounds.Scale(dsf); | |
88 thumb_bounds = gfx::RectF(gfx::ToEnclosingRect(thumb_bounds)); | |
89 canvas->DrawCircle(thumb_bounds.CenterPoint(), thumb_bounds.height() / 2.f, | |
90 thumb_paint); | |
87 } | 91 } |
88 | 92 |
89 // Color ratio between 0 and 1 that controls the thumb color. | 93 // Color ratio between 0 and 1 that controls the thumb color. |
90 double color_ratio_; | 94 double color_ratio_; |
91 | 95 |
92 DISALLOW_COPY_AND_ASSIGN(ThumbView); | 96 DISALLOW_COPY_AND_ASSIGN(ThumbView); |
93 }; | 97 }; |
94 | 98 |
95 // static | 99 // static |
96 const char ToggleButton::kViewClassName[] = "ToggleButton"; | 100 const char ToggleButton::kViewClassName[] = "ToggleButton"; |
97 | 101 |
98 ToggleButton::ToggleButton(ButtonListener* listener) | 102 ToggleButton::ToggleButton(ButtonListener* listener) |
99 : CustomButton(listener), | 103 : CustomButton(listener), |
100 is_on_(false), | 104 is_on_(false), |
101 slide_animation_(this), | 105 slide_animation_(this), |
102 thumb_view_(new ToggleButton::ThumbView()) { | 106 thumb_view_(new ThumbView()) { |
103 slide_animation_.SetSlideDuration(80 /* ms */); | 107 slide_animation_.SetSlideDuration(80 /* ms */); |
104 slide_animation_.SetTweenType(gfx::Tween::LINEAR); | 108 slide_animation_.SetTweenType(gfx::Tween::LINEAR); |
105 SetBorder(Border::CreateEmptyBorder( | 109 SetBorder(Border::CreateEmptyBorder( |
106 gfx::Insets(kTrackVerticalMargin, kTrackHorizontalMargin))); | 110 gfx::Insets(kTrackVerticalMargin, kTrackHorizontalMargin))); |
107 AddChildView(thumb_view_.get()); | 111 AddChildView(thumb_view_); |
108 SetInkDropMode(InkDropMode::ON); | 112 SetInkDropMode(InkDropMode::ON); |
109 set_has_ink_drop_action_on_click(true); | 113 set_has_ink_drop_action_on_click(true); |
110 } | 114 } |
111 | 115 |
112 ToggleButton::~ToggleButton() { | 116 ToggleButton::~ToggleButton() { |
113 // Destroying ink drop early allows ink drop layer to be properly removed, | 117 // Destroying ink drop early allows ink drop layer to be properly removed, |
114 SetInkDropMode(InkDropMode::OFF); | 118 SetInkDropMode(InkDropMode::OFF); |
115 } | 119 } |
116 | 120 |
117 void ToggleButton::SetIsOn(bool is_on, bool animate) { | 121 void ToggleButton::SetIsOn(bool is_on, bool animate) { |
(...skipping 14 matching lines...) Expand all Loading... | |
132 | 136 |
133 gfx::Rect ToggleButton::GetThumbBounds() const { | 137 gfx::Rect ToggleButton::GetThumbBounds() const { |
134 gfx::Rect thumb_bounds = GetLocalBounds(); | 138 gfx::Rect thumb_bounds = GetLocalBounds(); |
135 thumb_bounds.Inset(gfx::Insets(kThumbVerticalMargin, kThumbHorizontalMargin)); | 139 thumb_bounds.Inset(gfx::Insets(kThumbVerticalMargin, kThumbHorizontalMargin)); |
136 thumb_bounds.set_x(thumb_bounds.x() + | 140 thumb_bounds.set_x(thumb_bounds.x() + |
137 slide_animation_.GetCurrentValue() * | 141 slide_animation_.GetCurrentValue() * |
138 (thumb_bounds.width() - thumb_bounds.height())); | 142 (thumb_bounds.width() - thumb_bounds.height())); |
139 // The thumb is a circle, so the width should match the height. | 143 // The thumb is a circle, so the width should match the height. |
140 thumb_bounds.set_width(thumb_bounds.height()); | 144 thumb_bounds.set_width(thumb_bounds.height()); |
141 thumb_bounds.set_x(GetMirroredXForRect(thumb_bounds)); | 145 thumb_bounds.set_x(GetMirroredXForRect(thumb_bounds)); |
146 thumb_bounds.Inset(ThumbView::GetShadowOutsets()); | |
142 return thumb_bounds; | 147 return thumb_bounds; |
143 } | 148 } |
144 | 149 |
145 void ToggleButton::UpdateThumb() { | 150 void ToggleButton::UpdateThumb() { |
146 gfx::Rect thumb_bounds = GetThumbBounds(); | 151 thumb_view_->Update(GetThumbBounds(), slide_animation_.GetCurrentValue()); |
147 thumb_bounds.Inset(gfx::Insets(-kThumbVerticalMargin)); | 152 } |
148 thumb_view_->Update(thumb_bounds, slide_animation_.GetCurrentValue()); | 153 |
154 SkColor ToggleButton::GetTrackColor(bool is_on) const { | |
155 const SkAlpha kOffTrackAlpha = 0x29; | |
156 const SkAlpha kOnTrackAlpha = kOffTrackAlpha * 2; | |
157 ui::NativeTheme::ColorId color_id = | |
158 is_on ? ui::NativeTheme::kColorId_ProminentButtonColor | |
159 : ui::NativeTheme::kColorId_LabelEnabledColor; | |
160 return SkColorSetA(GetNativeTheme()->GetSystemColor(color_id), | |
161 is_on ? kOnTrackAlpha : kOffTrackAlpha); | |
149 } | 162 } |
150 | 163 |
151 gfx::Size ToggleButton::GetPreferredSize() const { | 164 gfx::Size ToggleButton::GetPreferredSize() const { |
152 gfx::Rect rect(0, 0, kTrackWidth, kTrackHeight); | 165 gfx::Rect rect(0, 0, kTrackWidth, kTrackHeight); |
153 if (border()) | 166 if (border()) |
154 rect.Inset(-border()->GetInsets()); | 167 rect.Inset(-border()->GetInsets()); |
155 return rect.size(); | 168 return rect.size(); |
156 } | 169 } |
157 | 170 |
158 const char* ToggleButton::GetClassName() const { | 171 const char* ToggleButton::GetClassName() const { |
159 return kViewClassName; | 172 return kViewClassName; |
160 } | 173 } |
161 | 174 |
162 void ToggleButton::OnPaint(gfx::Canvas* canvas) { | 175 void ToggleButton::OnPaint(gfx::Canvas* canvas) { |
163 // Paint the toggle track. | 176 // Paint the toggle track. To look sharp even at fractional scale factors, |
177 // round up to pixel boundaries. | |
Evan Stade
2016/11/01 16:26:23
added this improvement as well (visible with dsf=1
| |
178 float dsf = canvas->UndoDeviceScaleFactor(); | |
164 gfx::RectF track_rect(GetContentsBounds()); | 179 gfx::RectF track_rect(GetContentsBounds()); |
180 track_rect.Scale(dsf); | |
181 track_rect = gfx::RectF(gfx::ToEnclosingRect(track_rect)); | |
165 SkPaint track_paint; | 182 SkPaint track_paint; |
166 track_paint.setAntiAlias(true); | 183 track_paint.setAntiAlias(true); |
167 const SkColor track_on_color = | |
168 SkColorSetA(GetNativeTheme()->GetSystemColor( | |
169 ui::NativeTheme::kColorId_ProminentButtonColor), | |
170 0xFF / 2); | |
171 const double color_ratio = slide_animation_.GetCurrentValue(); | 184 const double color_ratio = slide_animation_.GetCurrentValue(); |
172 track_paint.setColor(color_utils::AlphaBlend( | 185 track_paint.setColor(color_utils::AlphaBlend( |
173 track_on_color, kTrackOffColor, | 186 GetTrackColor(true), GetTrackColor(false), |
174 static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio))); | 187 static_cast<SkAlpha>(SK_AlphaOPAQUE * color_ratio))); |
175 canvas->DrawRoundRect(track_rect, track_rect.height() / 2, track_paint); | 188 canvas->DrawRoundRect(track_rect, track_rect.height() / 2, track_paint); |
176 } | 189 } |
177 | 190 |
178 void ToggleButton::NotifyClick(const ui::Event& event) { | 191 void ToggleButton::NotifyClick(const ui::Event& event) { |
179 SetIsOn(!is_on(), true); | 192 SetIsOn(!is_on(), true); |
180 CustomButton::NotifyClick(event); | 193 CustomButton::NotifyClick(event); |
181 } | 194 } |
182 | 195 |
183 void ToggleButton::OnBoundsChanged(const gfx::Rect& previous_bounds) { | 196 void ToggleButton::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
184 UpdateThumb(); | 197 UpdateThumb(); |
185 } | 198 } |
186 | 199 |
187 void ToggleButton::OnNativeThemeChanged(const ui::NativeTheme* theme) { | 200 void ToggleButton::OnNativeThemeChanged(const ui::NativeTheme* theme) { |
188 SchedulePaint(); | 201 SchedulePaint(); |
189 } | 202 } |
190 | 203 |
191 void ToggleButton::AddInkDropLayer(ui::Layer* ink_drop_layer) { | 204 void ToggleButton::AddInkDropLayer(ui::Layer* ink_drop_layer) { |
192 thumb_view_->AddInkDropLayer(ink_drop_layer); | 205 thumb_view_->AddInkDropLayer(ink_drop_layer); |
193 UpdateThumb(); | |
194 SchedulePaint(); | |
195 } | 206 } |
196 | 207 |
197 void ToggleButton::RemoveInkDropLayer(ui::Layer* ink_drop_layer) { | 208 void ToggleButton::RemoveInkDropLayer(ui::Layer* ink_drop_layer) { |
198 thumb_view_->RemoveInkDropLayer(ink_drop_layer); | 209 thumb_view_->RemoveInkDropLayer(ink_drop_layer); |
199 SchedulePaint(); | |
200 } | 210 } |
201 | 211 |
202 std::unique_ptr<InkDropRipple> ToggleButton::CreateInkDropRipple() const { | 212 std::unique_ptr<InkDropRipple> ToggleButton::CreateInkDropRipple() const { |
203 const int radius = (kTrackHeight + kTrackVerticalMargin * 2) / 2; | 213 gfx::Rect rect = thumb_view_->GetLocalBounds(); |
204 return CreateDefaultInkDropRipple(gfx::Point(radius, radius)); | 214 rect.Inset(-ThumbView::GetShadowOutsets()); |
215 return CreateDefaultInkDropRipple(rect.CenterPoint()); | |
205 } | 216 } |
206 | 217 |
207 SkColor ToggleButton::GetInkDropBaseColor() const { | 218 SkColor ToggleButton::GetInkDropBaseColor() const { |
208 return is_on() | 219 return GetTrackColor(is_on()); |
209 ? GetNativeTheme()->GetSystemColor( | |
210 ui::NativeTheme::kColorId_ProminentButtonColor) | |
211 : kTrackOffColor; | |
212 } | 220 } |
213 | 221 |
214 bool ToggleButton::ShouldShowInkDropHighlight() const { | 222 bool ToggleButton::ShouldShowInkDropHighlight() const { |
215 return false; | 223 return false; |
216 } | 224 } |
217 | 225 |
218 void ToggleButton::AnimationProgressed(const gfx::Animation* animation) { | 226 void ToggleButton::AnimationProgressed(const gfx::Animation* animation) { |
219 if (animation == &slide_animation_) { | 227 if (animation == &slide_animation_) { |
220 // TODO(varkha, estade): The thumb is using its own view. Investigate if | 228 // TODO(varkha, estade): The thumb is using its own view. Investigate if |
221 // repainting in every animation step to update colors could be avoided. | 229 // repainting in every animation step to update colors could be avoided. |
222 UpdateThumb(); | 230 UpdateThumb(); |
223 SchedulePaint(); | 231 SchedulePaint(); |
224 return; | 232 return; |
225 } | 233 } |
226 CustomButton::AnimationProgressed(animation); | 234 CustomButton::AnimationProgressed(animation); |
227 } | 235 } |
228 | 236 |
229 } // namespace views | 237 } // namespace views |
OLD | NEW |