1 | /* |
2 | * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
4 | * |
5 | * Portions are Copyright (C) 1998 Netscape Communications Corporation. |
6 | * |
7 | * Other contributors: |
8 | * Robert O'Callahan <roc+@cs.cmu.edu> |
9 | * David Baron <dbaron@fas.harvard.edu> |
10 | * Christian Biesinger <cbiesinger@web.de> |
11 | * Randall Jesup <rjesup@wgate.com> |
12 | * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> |
13 | * Josh Soref <timeless@mac.com> |
14 | * Boris Zbarsky <bzbarsky@mit.edu> |
15 | * |
16 | * This library is free software; you can redistribute it and/or |
17 | * modify it under the terms of the GNU Lesser General Public |
18 | * License as published by the Free Software Foundation; either |
19 | * version 2.1 of the License, or (at your option) any later version. |
20 | * |
21 | * This library is distributed in the hope that it will be useful, |
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
24 | * Lesser General Public License for more details. |
25 | * |
26 | * You should have received a copy of the GNU Lesser General Public |
27 | * License along with this library; if not, write to the Free Software |
28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
29 | * |
30 | * Alternatively, the contents of this file may be used under the terms |
31 | * of either the Mozilla Public License Version 1.1, found at |
32 | * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
33 | * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
34 | * (the "GPL"), in which case the provisions of the MPL or the GPL are |
35 | * applicable instead of those above. If you wish to allow use of your |
36 | * version of this file only under the terms of one of those two |
37 | * licenses (the MPL or the GPL) and not to allow others to use your |
38 | * version of this file under the LGPL, indicate your decision by |
39 | * deletingthe provisions above and replace them with the notice and |
40 | * other provisions required by the MPL or the GPL, as the case may be. |
41 | * If you do not delete the provisions above, a recipient may use your |
42 | * version of this file under any of the LGPL, the MPL or the GPL. |
43 | */ |
44 | |
45 | #include "config.h" |
46 | |
47 | #include "RenderMarquee.h" |
48 | |
49 | #include "FrameView.h" |
50 | #include "HTMLMarqueeElement.h" |
51 | #include "HTMLNames.h" |
52 | #include "RenderLayer.h" |
53 | #include "RenderView.h" |
54 | |
55 | namespace WebCore { |
56 | |
57 | using namespace HTMLNames; |
58 | |
59 | RenderMarquee::RenderMarquee(RenderLayer* layer) |
60 | : m_layer(layer) |
61 | , m_timer(*this, &RenderMarquee::timerFired) |
62 | { |
63 | layer->setConstrainsScrollingToContentEdge(false); |
64 | } |
65 | |
66 | RenderMarquee::~RenderMarquee() = default; |
67 | |
68 | int RenderMarquee::marqueeSpeed() const |
69 | { |
70 | int result = m_layer->renderer().style().marqueeSpeed(); |
71 | Element* element = m_layer->renderer().element(); |
72 | if (is<HTMLMarqueeElement>(element)) |
73 | result = std::max(result, downcast<HTMLMarqueeElement>(*element).minimumDelay()); |
74 | return result; |
75 | } |
76 | |
77 | static MarqueeDirection reverseDirection(MarqueeDirection direction) |
78 | { |
79 | switch (direction) { |
80 | case MarqueeDirection::Auto: |
81 | return MarqueeDirection::Auto; |
82 | case MarqueeDirection::Left: |
83 | return MarqueeDirection::Right; |
84 | case MarqueeDirection::Right: |
85 | return MarqueeDirection::Left; |
86 | case MarqueeDirection::Up: |
87 | return MarqueeDirection::Down; |
88 | case MarqueeDirection::Down: |
89 | return MarqueeDirection::Up; |
90 | case MarqueeDirection::Backward: |
91 | return MarqueeDirection::Forward; |
92 | case MarqueeDirection::Forward: |
93 | return MarqueeDirection::Backward; |
94 | } |
95 | return MarqueeDirection::Auto; |
96 | } |
97 | |
98 | MarqueeDirection RenderMarquee::direction() const |
99 | { |
100 | // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee. |
101 | // For now just map MarqueeDirection::Auto to MarqueeDirection::Backward |
102 | MarqueeDirection result = m_layer->renderer().style().marqueeDirection(); |
103 | TextDirection dir = m_layer->renderer().style().direction(); |
104 | if (result == MarqueeDirection::Auto) |
105 | result = MarqueeDirection::Backward; |
106 | if (result == MarqueeDirection::Forward) |
107 | result = (dir == TextDirection::LTR) ? MarqueeDirection::Right : MarqueeDirection::Left; |
108 | if (result == MarqueeDirection::Backward) |
109 | result = (dir == TextDirection::LTR) ? MarqueeDirection::Left : MarqueeDirection::Right; |
110 | |
111 | // Now we have the real direction. Next we check to see if the increment is negative. |
112 | // If so, then we reverse the direction. |
113 | Length increment = m_layer->renderer().style().marqueeIncrement(); |
114 | if (increment.isNegative()) |
115 | result = reverseDirection(result); |
116 | |
117 | return result; |
118 | } |
119 | |
120 | bool RenderMarquee::isHorizontal() const |
121 | { |
122 | return direction() == MarqueeDirection::Left || direction() == MarqueeDirection::Right; |
123 | } |
124 | |
125 | int RenderMarquee::computePosition(MarqueeDirection dir, bool stopAtContentEdge) |
126 | { |
127 | RenderBox* box = m_layer->renderBox(); |
128 | ASSERT(box); |
129 | auto& boxStyle = box->style(); |
130 | if (isHorizontal()) { |
131 | bool ltr = boxStyle.isLeftToRightDirection(); |
132 | LayoutUnit clientWidth = box->clientWidth(); |
133 | LayoutUnit contentWidth = ltr ? box->maxPreferredLogicalWidth() : box->minPreferredLogicalWidth(); |
134 | if (ltr) |
135 | contentWidth += (box->paddingRight() - box->borderLeft()); |
136 | else { |
137 | contentWidth = box->width() - contentWidth; |
138 | contentWidth += (box->paddingLeft() - box->borderRight()); |
139 | } |
140 | if (dir == MarqueeDirection::Right) { |
141 | if (stopAtContentEdge) |
142 | return std::max<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); |
143 | |
144 | return ltr ? contentWidth : clientWidth; |
145 | } |
146 | |
147 | if (stopAtContentEdge) |
148 | return std::min<LayoutUnit>(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); |
149 | |
150 | return ltr ? -clientWidth : -contentWidth; |
151 | } |
152 | |
153 | // Vertical |
154 | int contentHeight = box->layoutOverflowRect().maxY() - box->borderTop() + box->paddingBottom(); |
155 | int clientHeight = roundToInt(box->clientHeight()); |
156 | if (dir == MarqueeDirection::Up) { |
157 | if (stopAtContentEdge) |
158 | return std::min(contentHeight - clientHeight, 0); |
159 | |
160 | return -clientHeight; |
161 | } |
162 | |
163 | if (stopAtContentEdge) |
164 | return std::max(contentHeight - clientHeight, 0); |
165 | |
166 | return contentHeight; |
167 | } |
168 | |
169 | void RenderMarquee::start() |
170 | { |
171 | if (m_timer.isActive() || m_layer->renderer().style().marqueeIncrement().isZero()) |
172 | return; |
173 | |
174 | if (!m_suspended && !m_stopped) { |
175 | if (isHorizontal()) |
176 | m_layer->scrollToOffset(ScrollOffset(m_start, 0), ScrollType::Programmatic, ScrollClamping::Unclamped); |
177 | else |
178 | m_layer->scrollToOffset(ScrollOffset(0, m_start), ScrollType::Programmatic, ScrollClamping::Unclamped); |
179 | } else { |
180 | m_suspended = false; |
181 | m_stopped = false; |
182 | } |
183 | |
184 | m_timer.startRepeating(1_ms * speed()); |
185 | } |
186 | |
187 | void RenderMarquee::suspend() |
188 | { |
189 | m_timer.stop(); |
190 | m_suspended = true; |
191 | } |
192 | |
193 | void RenderMarquee::stop() |
194 | { |
195 | m_timer.stop(); |
196 | m_stopped = true; |
197 | } |
198 | |
199 | void RenderMarquee::updateMarqueePosition() |
200 | { |
201 | bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); |
202 | if (activate) { |
203 | MarqueeBehavior behavior = m_layer->renderer().style().marqueeBehavior(); |
204 | m_start = computePosition(direction(), behavior == MarqueeBehavior::Alternate); |
205 | m_end = computePosition(reverseDirection(direction()), behavior == MarqueeBehavior::Alternate || behavior == MarqueeBehavior::Slide); |
206 | if (!m_stopped) |
207 | start(); |
208 | } |
209 | } |
210 | |
211 | void RenderMarquee::updateMarqueeStyle() |
212 | { |
213 | auto& style = m_layer->renderer().style(); |
214 | |
215 | if (m_direction != style.marqueeDirection() || (m_totalLoops != style.marqueeLoopCount() && m_currentLoop >= m_totalLoops)) |
216 | m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop. |
217 | |
218 | m_totalLoops = style.marqueeLoopCount(); |
219 | m_direction = style.marqueeDirection(); |
220 | |
221 | if (m_layer->renderer().isHTMLMarquee()) { |
222 | // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do |
223 | // one loop. |
224 | if (m_totalLoops <= 0 && style.marqueeBehavior() == MarqueeBehavior::Slide) |
225 | m_totalLoops = 1; |
226 | } |
227 | |
228 | if (speed() != marqueeSpeed()) { |
229 | m_speed = marqueeSpeed(); |
230 | if (m_timer.isActive()) |
231 | m_timer.startRepeating(1_ms * speed()); |
232 | } |
233 | |
234 | // Check the loop count to see if we should now stop. |
235 | bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); |
236 | if (activate && !m_timer.isActive()) |
237 | m_layer->renderer().setNeedsLayout(); |
238 | else if (!activate && m_timer.isActive()) |
239 | m_timer.stop(); |
240 | } |
241 | |
242 | void RenderMarquee::timerFired() |
243 | { |
244 | if (m_layer->renderer().view().needsLayout()) |
245 | return; |
246 | |
247 | if (m_reset) { |
248 | m_reset = false; |
249 | if (isHorizontal()) |
250 | m_layer->scrollToXOffset(m_start); |
251 | else |
252 | m_layer->scrollToYOffset(m_start); |
253 | return; |
254 | } |
255 | |
256 | const RenderStyle& style = m_layer->renderer().style(); |
257 | |
258 | int endPoint = m_end; |
259 | int range = m_end - m_start; |
260 | int newPos; |
261 | if (range == 0) |
262 | newPos = m_end; |
263 | else { |
264 | bool addIncrement = direction() == MarqueeDirection::Up || direction() == MarqueeDirection::Left; |
265 | bool isReversed = style.marqueeBehavior() == MarqueeBehavior::Alternate && m_currentLoop % 2; |
266 | if (isReversed) { |
267 | // We're going in the reverse direction. |
268 | endPoint = m_start; |
269 | range = -range; |
270 | addIncrement = !addIncrement; |
271 | } |
272 | bool positive = range > 0; |
273 | int clientSize = (isHorizontal() ? roundToInt(m_layer->renderBox()->clientWidth()) : roundToInt(m_layer->renderBox()->clientHeight())); |
274 | int increment = abs(intValueForLength(m_layer->renderer().style().marqueeIncrement(), clientSize)); |
275 | int currentPos = (isHorizontal() ? m_layer->scrollOffset().x() : m_layer->scrollOffset().y()); |
276 | newPos = currentPos + (addIncrement ? increment : -increment); |
277 | if (positive) |
278 | newPos = std::min(newPos, endPoint); |
279 | else |
280 | newPos = std::max(newPos, endPoint); |
281 | } |
282 | |
283 | if (newPos == endPoint) { |
284 | m_currentLoop++; |
285 | if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops) |
286 | m_timer.stop(); |
287 | else if (style.marqueeBehavior() != MarqueeBehavior::Alternate) |
288 | m_reset = true; |
289 | } |
290 | |
291 | if (isHorizontal()) |
292 | m_layer->scrollToXOffset(newPos); |
293 | else |
294 | m_layer->scrollToYOffset(newPos); |
295 | } |
296 | |
297 | } // namespace WebCore |
298 | |