Skip to content

Commit

Permalink
Initial <input type=checkbox switch> animation support
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=265108

Reviewed by Aditya Keerthi.

This adds the infrastructure needed to start the animation, somewhat
based on our <progress> implementation. And also implements the macOS
animation.

Thanks to Aditya, smfr, Tim Horton, Antoine, and Kimmo for much needed
guidance.

* Source/WebCore/dom/EventDispatcher.cpp:
(WebCore::EventDispatcher::dispatchEvent):
* Source/WebCore/html/CheckboxInputType.cpp:
(WebCore::CheckboxInputType::willDispatchClick):
(WebCore::switchCheckedChangeAnimationUpdateInterval):
(WebCore::CheckboxInputType::performSwitchCheckedChangeAnimation):
(WebCore::CheckboxInputType::switchCheckedChangeAnimationProgress const):
(WebCore::CheckboxInputType::switchCheckedChangeAnimationTimerFired):
* Source/WebCore/html/CheckboxInputType.h:
* Source/WebCore/html/HTMLInputElement.cpp:
(WebCore::HTMLInputElement::setChecked):
(WebCore::HTMLInputElement::switchCheckedChangeAnimationProgress const):
* Source/WebCore/html/HTMLInputElement.h:
* Source/WebCore/platform/graphics/controls/SwitchThumbPart.h:
* Source/WebCore/platform/graphics/controls/SwitchTrackPart.h:
* Source/WebCore/platform/graphics/mac/controls/SwitchMacUtilities.h:
* Source/WebCore/platform/graphics/mac/controls/SwitchMacUtilities.mm:
(WebCore::SwitchMacUtilities::easeInOut):
* Source/WebCore/platform/graphics/mac/controls/SwitchThumbMac.h:
* Source/WebCore/platform/graphics/mac/controls/SwitchThumbMac.mm:
(WebCore::SwitchThumbMac::cellSize const):
(WebCore::SwitchThumbMac::draw):
* Source/WebCore/platform/graphics/mac/controls/SwitchTrackMac.h:
* Source/WebCore/platform/graphics/mac/controls/SwitchTrackMac.mm:
(WebCore::trackMaskImage):
(WebCore::trackImage):
(WebCore::SwitchTrackMac::draw):
* Source/WebCore/rendering/RenderTheme.cpp:
(WebCore::updateSwitchThumbPartForRenderer):
(WebCore::updateSwitchTrackPartForRenderer):
(WebCore::RenderTheme::updateControlPartForRenderer const):
* Source/WebCore/rendering/RenderTheme.h:
(WebCore::RenderTheme::switchCheckedChangeAnimationDuration const):
* Source/WebCore/rendering/RenderThemeMac.h:
* Source/WebKit/Shared/WebCoreArgumentCoders.cpp:
(IPC::ArgumentCoder<ControlPart>::encode):
(IPC::ArgumentCoder<ControlPart>::decode):
* Source/WebKit/Shared/WebCoreArgumentCoders.serialization.in:

Canonical link: https://commits.webkit.org/271118@main
  • Loading branch information
annevk committed Nov 26, 2023
1 parent 7b39c63 commit 0a64dd5
Show file tree
Hide file tree
Showing 18 changed files with 315 additions and 56 deletions.
1 change: 1 addition & 0 deletions Source/WebCore/dom/EventDispatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ void EventDispatcher::dispatchEvent(Node& node, Event& event)
return;

InputElementClickState clickHandlingState;
clickHandlingState.trusted = event.isTrusted();

RefPtr inputForLegacyPreActivationBehavior = dynamicDowncast<HTMLInputElement>(node);
if (!inputForLegacyPreActivationBehavior && event.bubbles() && event.type() == eventNames().clickEvent)
Expand Down
79 changes: 78 additions & 1 deletion Source/WebCore/html/CheckboxInputType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
#include "InputTypeNames.h"
#include "KeyboardEvent.h"
#include "LocalizedStrings.h"
#include "Page.h"
#include "RenderElement.h"
#include "RenderTheme.h"
#include "ScopedEventQueue.h"
#include "ScriptDisallowedScope.h"
#include "ShadowRoot.h"
Expand Down Expand Up @@ -97,7 +99,7 @@ void CheckboxInputType::willDispatchClick(InputElementClickState& state)
if (state.indeterminate)
element()->setIndeterminate(false);

element()->setChecked(!state.checked);
element()->setChecked(!state.checked, state.trusted ? WasSetByJavaScript::No : WasSetByJavaScript::Yes);
}

void CheckboxInputType::didDispatchClick(Event& event, const InputElementClickState& state)
Expand All @@ -124,4 +126,79 @@ bool CheckboxInputType::shouldAppearIndeterminate() const
return element()->indeterminate() && !isSwitch();
}

// FIXME: ideally CheckboxInputType would not be responsible for the timer specifics and instead
// ask a more knowledgable system for a refresh callback (perhaps passing a desired FPS).
static Seconds switchCheckedChangeAnimationUpdateInterval(HTMLInputElement* element)
{
if (auto* page = element->document().page())
return page->preferredRenderingUpdateInterval();
return 0_s;
}

void CheckboxInputType::performSwitchCheckedChangeAnimation(WasSetByJavaScript wasCheckedByJavaScript)
{
ASSERT(isSwitch());
ASSERT(element());
ASSERT(element()->renderer());
ASSERT(element()->renderer()->style().hasEffectiveAppearance());

if (wasCheckedByJavaScript == WasSetByJavaScript::Yes) {
m_switchCheckedChangeAnimationStartTime = 0_s;
return;
}

auto updateInterval = switchCheckedChangeAnimationUpdateInterval(element());
auto duration = RenderTheme::singleton().switchCheckedChangeAnimationDuration();

if (!m_switchCheckedChangeAnimationTimer) {
if (!(duration > 0_s && updateInterval > 0_s))
return;
m_switchCheckedChangeAnimationTimer = makeUnique<Timer>(*this, &CheckboxInputType::switchCheckedChangeAnimationTimerFired);
}
ASSERT(duration > 0_s);
ASSERT(updateInterval > 0_s);
ASSERT(m_switchCheckedChangeAnimationTimer);

auto isAnimating = m_switchCheckedChangeAnimationStartTime != 0_s;
auto currentTime = MonotonicTime::now().secondsSinceEpoch();
auto remainingTime = currentTime - m_switchCheckedChangeAnimationStartTime;
auto startTimeOffset = 0_s;
if (isAnimating && remainingTime < duration)
startTimeOffset = duration - remainingTime;

m_switchCheckedChangeAnimationStartTime = MonotonicTime::now().secondsSinceEpoch() - startTimeOffset;
m_switchCheckedChangeAnimationTimer->startOneShot(updateInterval);
}

float CheckboxInputType::switchCheckedChangeAnimationProgress() const
{
ASSERT(isSwitch());

auto isAnimating = m_switchCheckedChangeAnimationStartTime != 0_s;
if (!isAnimating)
return 1.0f;
auto duration = RenderTheme::singleton().switchCheckedChangeAnimationDuration();
return std::min((float)((MonotonicTime::now().secondsSinceEpoch() - m_switchCheckedChangeAnimationStartTime) / duration), 1.0f);
}

void CheckboxInputType::switchCheckedChangeAnimationTimerFired()
{
ASSERT(m_switchCheckedChangeAnimationTimer);
if (!isSwitch() || !element() || !element()->renderer())
return;

auto updateInterval = switchCheckedChangeAnimationUpdateInterval(element());
if (!(updateInterval > 0_s))
return;

auto currentTime = MonotonicTime::now().secondsSinceEpoch();
auto duration = RenderTheme::singleton().switchCheckedChangeAnimationDuration();
if (currentTime - m_switchCheckedChangeAnimationStartTime < duration)
m_switchCheckedChangeAnimationTimer->startOneShot(updateInterval);
else
m_switchCheckedChangeAnimationStartTime = 0_s;

element()->renderer()->repaint();
}

} // namespace WebCore
10 changes: 10 additions & 0 deletions Source/WebCore/html/CheckboxInputType.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

namespace WebCore {

enum class WasSetByJavaScript : bool;

class CheckboxInputType final : public BaseCheckableInputType {
public:
static Ref<CheckboxInputType> create(HTMLInputElement& element)
Expand All @@ -43,6 +45,9 @@ class CheckboxInputType final : public BaseCheckableInputType {

bool valueMissing(const String&) const final;

void performSwitchCheckedChangeAnimation(WasSetByJavaScript);
float switchCheckedChangeAnimationProgress() const;

private:
explicit CheckboxInputType(HTMLInputElement& element)
: BaseCheckableInputType(Type::Checkbox, element)
Expand All @@ -57,6 +62,11 @@ class CheckboxInputType final : public BaseCheckableInputType {
void didDispatchClick(Event&, const InputElementClickState&) final;
bool matchesIndeterminatePseudoClass() const final;
bool shouldAppearIndeterminate() const final;

void switchCheckedChangeAnimationTimerFired();

Seconds m_switchCheckedChangeAnimationStartTime { 0_s };
std::unique_ptr<Timer> m_switchCheckedChangeAnimationTimer;
};

} // namespace WebCore
Expand Down
14 changes: 12 additions & 2 deletions Source/WebCore/html/HTMLInputElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "CSSGradientValue.h"
#include "CSSPropertyNames.h"
#include "CSSValuePool.h"
#include "CheckboxInputType.h"
#include "Chrome.h"
#include "ChromeClient.h"
#include "ColorInputType.h"
Expand Down Expand Up @@ -1029,7 +1030,7 @@ void HTMLInputElement::setDefaultCheckedState(bool isDefaultChecked)
m_isDefaultChecked = isDefaultChecked;
}

void HTMLInputElement::setChecked(bool isChecked)
void HTMLInputElement::setChecked(bool isChecked, WasSetByJavaScript wasCheckedByJavaScript)
{
m_dirtyCheckednessFlag = true;
if (checked() == isChecked)
Expand All @@ -1043,8 +1044,11 @@ void HTMLInputElement::setChecked(bool isChecked)

if (auto* buttons = radioButtonGroups())
buttons->updateCheckedState(*this);
if (auto* renderer = this->renderer(); renderer && renderer->style().hasEffectiveAppearance())
if (auto* renderer = this->renderer(); renderer && renderer->style().hasEffectiveAppearance()) {
if (isSwitch())
downcast<CheckboxInputType>(*m_inputType).performSwitchCheckedChangeAnimation(wasCheckedByJavaScript);
renderer->theme().stateChanged(*renderer, ControlStates::States::Checked);
}
updateValidity();

// Ideally we'd do this from the render tree (matching
Expand Down Expand Up @@ -2311,4 +2315,10 @@ bool HTMLInputElement::dirAutoUsesValue() const
return m_inputType->dirAutoUsesValue();
}

float HTMLInputElement::switchCheckedChangeAnimationProgress() const
{
ASSERT(isSwitch());
return downcast<CheckboxInputType>(*m_inputType).switchCheckedChangeAnimationProgress();
}

} // namespace
5 changes: 4 additions & 1 deletion Source/WebCore/html/HTMLInputElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct InputElementClickState {
bool stateful { false };
bool checked { false };
bool indeterminate { false };
bool trusted { false };
RefPtr<HTMLInputElement> checkedRadioButton;
};

Expand All @@ -64,7 +65,7 @@ class HTMLInputElement : public HTMLTextFormControlElement {
virtual ~HTMLInputElement();

bool checked() const { return m_isChecked; }
WEBCORE_EXPORT void setChecked(bool);
WEBCORE_EXPORT void setChecked(bool, WasSetByJavaScript = WasSetByJavaScript::Yes);
WEBCORE_EXPORT FileList* files();
WEBCORE_EXPORT void setFiles(RefPtr<FileList>&&, WasSetByJavaScript = WasSetByJavaScript::No);
FileList* filesForBindings() { return files(); }
Expand Down Expand Up @@ -341,6 +342,8 @@ class HTMLInputElement : public HTMLTextFormControlElement {

bool hasEverBeenPasswordField() const { return m_hasEverBeenPasswordField; }

float switchCheckedChangeAnimationProgress() const;

protected:
HTMLInputElement(const QualifiedName&, Document&, HTMLFormElement*, bool createdByParser);

Expand Down
20 changes: 19 additions & 1 deletion Source/WebCore/platform/graphics/controls/SwitchThumbPart.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,23 @@ class SwitchThumbPart final : public ControlPart {
public:
static Ref<SwitchThumbPart> create()
{
return adoptRef(*new SwitchThumbPart());
return adoptRef(*new SwitchThumbPart(0.0f));
}

static Ref<SwitchThumbPart> create(float progress)
{
return adoptRef(*new SwitchThumbPart(progress));
}

SwitchThumbPart(float progress)
: ControlPart(StyleAppearance::SwitchThumb)
, m_progress(progress)
{
}

float progress() const { return m_progress; }
void setProgress(float progress) { m_progress = progress; }

private:
SwitchThumbPart()
: ControlPart(StyleAppearance::SwitchThumb)
Expand All @@ -46,6 +60,10 @@ class SwitchThumbPart final : public ControlPart {
{
return controlFactory().createPlatformSwitchThumb(*this);
}

float m_progress;
};

} // namespace WebCore

SPECIALIZE_TYPE_TRAITS_CONTROL_PART(SwitchThumb)
20 changes: 19 additions & 1 deletion Source/WebCore/platform/graphics/controls/SwitchTrackPart.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,23 @@ class SwitchTrackPart final : public ControlPart {
public:
static Ref<SwitchTrackPart> create()
{
return adoptRef(*new SwitchTrackPart());
return adoptRef(*new SwitchTrackPart(0.0f));
}

static Ref<SwitchTrackPart> create(float progress)
{
return adoptRef(*new SwitchTrackPart(progress));
}

SwitchTrackPart(float progress)
: ControlPart(StyleAppearance::SwitchTrack)
, m_progress(progress)
{
}

float progress() const { return m_progress; }
void setProgress(float progress) { m_progress = progress; }

private:
SwitchTrackPart()
: ControlPart(StyleAppearance::SwitchTrack)
Expand All @@ -46,6 +60,10 @@ class SwitchTrackPart final : public ControlPart {
{
return controlFactory().createPlatformSwitchTrack(*this);
}

float m_progress;
};

} // namespace WebCore

SPECIALIZE_TYPE_TRAITS_CONTROL_PART(SwitchTrack)
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static IntSize cellSize(NSControlSize);
static IntOutsets cellOutsets(NSControlSize);
static FloatRect rectForBounds(const FloatRect&);
static NSString *coreUISizeForControlSize(const NSControlSize);
static float easeInOut(float);

} // namespace WebCore::SwitchMacUtilities

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ static FloatRect rectForBounds(const FloatRect& bounds)
return (__bridge NSString *)kCUISizeRegular;
}

static float easeInOut(const float progress)
{
return -2.0f * pow(progress, 3.0f) + 3.0f * pow(progress, 2.0f);
}

} // namespace WebCore::SwitchMacUtilities

#endif // PLATFORM(MAC)
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@
#if PLATFORM(MAC)

#import "ControlMac.h"
#import "SwitchThumbPart.h"

namespace WebCore {

class SwitchThumbPart;

class SwitchThumbMac final : public ControlMac {
public:
SwitchThumbMac(SwitchThumbPart&, ControlFactoryMac&);

private:
const SwitchThumbPart& owningPart() const { return downcast<SwitchThumbPart>(m_owningPart); }

IntSize cellSize(NSControlSize, const ControlStyle&) const override;
IntOutsets cellOutsets(NSControlSize, const ControlStyle&) const override;
FloatRect rectForBounds(const FloatRect&, const ControlStyle&) const override;
Expand Down
11 changes: 7 additions & 4 deletions Source/WebCore/platform/graphics/mac/controls/SwitchThumbMac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#if PLATFORM(MAC)

#import "SwitchMacUtilities.h"
#import "SwitchThumbPart.h"

namespace WebCore {

Expand All @@ -41,7 +40,7 @@
IntSize SwitchThumbMac::cellSize(NSControlSize controlSize, const ControlStyle&) const
{
// For now we need the track sizes to paint the thumb. As it happens the thumb is the square
// of the track's height. We (ab)use that fact later on.
// of the track's height. We (ab)use that fact with drawingThumbLength below.
return SwitchMacUtilities::cellSize(controlSize);
}

Expand All @@ -63,6 +62,7 @@
bool isRTL = style.states.contains(ControlStyle::State::RightToLeft);
bool isEnabled = style.states.contains(ControlStyle::State::Enabled);
bool isPressed = style.states.contains(ControlStyle::State::Pressed);
auto progress = SwitchMacUtilities::easeInOut(owningPart().progress());

auto borderRectRect = borderRect.rect();
auto controlSize = controlSizeForSize(borderRectRect.size(), style);
Expand All @@ -76,9 +76,12 @@

auto inflatedTrackRect = inflatedRect(trackRect, size, outsets, style);

auto drawingThumbLength = inflatedTrackRect.height();
auto drawingThumbIsLeft = (!isRTL && !isOn) || (isRTL && isOn);
auto drawingThumbX = drawingThumbIsLeft ? 0 : inflatedTrackRect.width() - inflatedTrackRect.height();
auto drawingThumbRect = NSMakeRect(drawingThumbX, 0, inflatedTrackRect.height(), inflatedTrackRect.height());
auto drawingThumbXAxis = inflatedTrackRect.width() - drawingThumbLength;
auto drawingThumbXAxisProgress = drawingThumbXAxis * progress;
auto drawingThumbX = drawingThumbIsLeft ? drawingThumbXAxis - drawingThumbXAxisProgress : drawingThumbXAxisProgress;
auto drawingThumbRect = NSMakeRect(drawingThumbX, 0, drawingThumbLength, drawingThumbLength);

auto trackBuffer = context.createImageBuffer(inflatedTrackRect.size(), deviceScaleFactor);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,17 @@
#if PLATFORM(MAC)

#import "ControlMac.h"
#import "SwitchTrackPart.h"

namespace WebCore {

class SwitchTrackMac;

class SwitchTrackMac final : public ControlMac {
public:
SwitchTrackMac(SwitchTrackPart&, ControlFactoryMac&);

private:
const SwitchTrackPart& owningPart() const { return downcast<SwitchTrackPart>(m_owningPart); }

IntSize cellSize(NSControlSize, const ControlStyle&) const override;
IntOutsets cellOutsets(NSControlSize, const ControlStyle&) const override;
FloatRect rectForBounds(const FloatRect&, const ControlStyle&) const override;
Expand Down
Loading

0 comments on commit 0a64dd5

Please sign in to comment.