Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -652,4 +652,105 @@ namespace Microsoft { namespace UI { namespace Xaml { namespace Tests { namespac
RunOnUIThread([&]() { VERIFY_IS_FALSE(toggleSwitch->IsOn); });
}

void ToggleSwitchIntegrationTests::KnobMaintainsMarginsFromTrackDuringDrag()
{
TestCleanupWrapper cleanup;
xaml_controls::ToggleSwitch^ toggleSwitch = nullptr;
xaml::FrameworkElement^ knob = nullptr;
xaml::FrameworkElement^ knobBounds = nullptr;
xaml::FrameworkElement^ knobOff = nullptr;
xaml::FrameworkElement^ knobOn = nullptr;

RunOnUIThread([&]()
{
toggleSwitch = ref new xaml_controls::ToggleSwitch();
toggleSwitch->IsOn = false;
toggleSwitch->HorizontalAlignment = xaml::HorizontalAlignment::Center;
TestServices::WindowHelper->WindowContent = toggleSwitch;
});
TestServices::WindowHelper->WaitForIdle();

RunOnUIThread([&]()
{
knob = TreeHelper::GetVisualChildByName(toggleSwitch, L"SwitchKnob");
knobBounds = TreeHelper::GetVisualChildByName(toggleSwitch, L"SwitchKnobBounds");
knobOff = TreeHelper::GetVisualChildByName(toggleSwitch, L"SwitchKnobOff");
knobOn = TreeHelper::GetVisualChildByName(toggleSwitch, L"SwitchKnobOn");
VERIFY_IS_NOT_NULL(knob);
VERIFY_IS_NOT_NULL(knobBounds);
VERIFY_IS_NOT_NULL(knobOff);
VERIFY_IS_NOT_NULL(knobOn);
});

auto dragAndVerifyMargin = [&](bool isOn, int dragDeltaX)
{
RunOnUIThread([&]()
{
toggleSwitch->IsOn = isOn;
});
TestServices::WindowHelper->WaitForIdle();

bool isMouseButtonDown = false;
TestCleanupWrapper releaseMouse([&]()
{
if (isMouseButtonDown)
{
TestServices::InputHelper->MouseButtonUp(knob, 0, 0, MouseButton::Left);
TestServices::WindowHelper->WaitForIdle();
}
});

TestServices::InputHelper->MouseButtonDown(knob, 0, 0, MouseButton::Left);
isMouseButtonDown = true;
TestServices::WindowHelper->WaitForIdle();

TestServices::InputHelper->MoveMouse(knob, dragDeltaX, 0);
TestServices::WindowHelper->WaitForIdle();

RunOnUIThread([&]()
{
VERIFY_IS_TRUE(ControlHelper::IsInVisualState(toggleSwitch, L"CommonStates", L"Pressed"));
VERIFY_IS_TRUE(ControlHelper::IsInVisualState(toggleSwitch, L"ToggleStates", L"Dragging"));

auto knobVisual = isOn ? knobOn : knobOff;
const auto knobVisualOrigin =
knobVisual->TransformToVisual(knobBounds)->TransformPoint({ 0, 0 });
const double knobVisualWidth = knobVisual->ActualWidth;
const double trackWidth = knobBounds->ActualWidth;

LOG_OUTPUT(
L"IsOn: %d, knob visual origin: %f, knob visual width: %f, track width: %f",
isOn,
knobVisualOrigin.X,
knobVisualWidth,
trackWidth);

if (isOn)
{
VERIFY_IS_GREATER_THAN(
knobVisualOrigin.X,
0.0,
L"Knob left edge should not touch the track boundary while dragging from On.");
}
else
{
VERIFY_IS_LESS_THAN(
knobVisualOrigin.X + knobVisualWidth,
trackWidth,
L"Knob right edge should not touch the track boundary while dragging from Off.");
}
});

TestServices::InputHelper->MouseButtonUp(knob, 0, 0, MouseButton::Left);
isMouseButtonDown = false;
TestServices::WindowHelper->WaitForIdle();
};

LOG_OUTPUT(L"Dragging from Off toward the right track boundary.");
dragAndVerifyMargin(false /*isOn*/, 100 /*dragDeltaX*/);

LOG_OUTPUT(L"Dragging from On toward the left track boundary.");
dragAndVerifyMargin(true /*isOn*/, -100 /*dragDeltaX*/);
}

} } } } } } // Microsoft::UI::Xaml::Tests::Controls::ToggleSwitch
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ namespace Microsoft { namespace UI { namespace Xaml { namespace Tests { namespac
BEGIN_TEST_METHOD(DoesNotToggleUsingDirectionalKeys)
TEST_METHOD_PROPERTY(L"Description", L"Validates that pressing Home, End, Up, Down, Left, and Right does not toggle the state of the control.")
END_TEST_METHOD()

BEGIN_TEST_METHOD(KnobMaintainsMarginsFromTrackDuringDrag)
TEST_METHOD_PROPERTY(L"Description", L"Validates that the knob maintains proper margins from track boundaries when dragged in its expanded/pressed state.")
TEST_METHOD_PROPERTY(L"TestPass:IncludeOnlyOn", L"Desktop")
END_TEST_METHOD()
};

} } } } } }

92 changes: 90 additions & 2 deletions dxaml/xcp/dxaml/lib/ToggleSwitch_Partial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ ToggleSwitch::ToggleSwitch() :

m_maxCurtainTranslation(0),
m_maxKnobTranslation(0),
m_dragVisualClampMax(-1),
m_dragVisualClampMin(-1),

m_minCurtainTranslation(0),
m_minKnobTranslation(0),
Expand Down Expand Up @@ -456,6 +458,20 @@ ToggleSwitch::SetTranslations()
translation = std::min(m_knobTranslation, m_maxKnobTranslation);
translation = std::max(translation, m_minKnobTranslation);

// Keep the expanded knob visual inside the track without changing the
// translation range used for toggle thresholds and animations.
if (m_isDragging)
{
if (m_dragVisualClampMax >= 0)
{
translation = std::min(translation, m_dragVisualClampMax);
}
if (m_dragVisualClampMin >= 0)
{
translation = std::max(translation, m_dragVisualClampMin);
}
}

IFC(m_spKnobTransform->put_X(translation));

if (pToggleSwitchTemplateSettingsNoRef)
Expand Down Expand Up @@ -813,6 +829,7 @@ ToggleSwitch::DragStartedHandler(
IFC(Focus(xaml::FocusState_Pointer, &isFocused));
IFC(GetTranslations());
IFC(UpdateVisualState(TRUE));
IFC(AdjustTranslationBoundsForDrag());
IFC(SetTranslations());

Cleanup:
Expand Down Expand Up @@ -852,13 +869,15 @@ ToggleSwitch::DragCompletedHandler(

IFC(pArgs->get_Canceled(&isCanceled));

m_isDragging = FALSE;
m_dragVisualClampMax = -1;
m_dragVisualClampMin = -1;

if (isCanceled)
{
goto Cleanup;
}

m_isDragging = FALSE;

IFC(MoveCompleted(m_wasDragged));

Cleanup:
Expand Down Expand Up @@ -997,6 +1016,71 @@ ToggleSwitch::SizeChangedHandler(
RRETURN(hr);
}

_Check_return_ HRESULT
ToggleSwitch::AdjustTranslationBoundsForDrag()
{
m_dragVisualClampMax = -1;
m_dragVisualClampMin = -1;

if (!m_tpKnob || !m_tpKnobBounds)
{
return S_OK;
}

BOOLEAN isOn = FALSE;
IFC_RETURN(get_IsOn(&isOn));

ctl::ComPtr<xaml::IDependencyObject> spKnobVisualDO;
if (!isOn)
{
IFC_RETURN(GetTemplateChild(wrl_wrappers::HStringReference(STR_LEN_PAIR(L"SwitchKnobOff")).Get(), &spKnobVisualDO));
}
else
{
IFC_RETURN(GetTemplateChild(wrl_wrappers::HStringReference(STR_LEN_PAIR(L"SwitchKnobOn")).Get(), &spKnobVisualDO));
}

auto spKnobVisualElement = spKnobVisualDO.AsOrNull<xaml::IFrameworkElement>();
auto spKnobVisualUIElement = spKnobVisualDO.AsOrNull<xaml::IUIElement>();

if (spKnobVisualElement && spKnobVisualUIElement)
{
DOUBLE knobWidth = 0;
DOUBLE knobVisualWidth = 0;
ctl::ComPtr<xaml::IUIElement> spKnobUIElement;
ctl::ComPtr<xaml_media::IGeneralTransform> spTransform;
wf::Point knobVisualOrigin = {};

IFC_RETURN(m_tpKnob->get_ActualWidth(&knobWidth));
IFC_RETURN(spKnobVisualElement->get_ActualWidth(&knobVisualWidth));
IFC_RETURN(m_tpKnob.As(&spKnobUIElement));
IFC_RETURN(spKnobVisualUIElement->TransformToVisual(spKnobUIElement.Get(), &spTransform));
IFC_RETURN(spTransform->TransformPoint({}, &knobVisualOrigin));

if (!isOn)
{
const DOUBLE restingMargin = knobVisualOrigin.X;
const DOUBLE clampMax = m_maxKnobTranslation - restingMargin;
if (restingMargin > 0 && clampMax >= m_minKnobTranslation)
{
m_dragVisualClampMax = clampMax;
}
}
else
{
const DOUBLE restingMargin =
knobWidth - knobVisualOrigin.X - knobVisualWidth;
const DOUBLE clampMin = m_minKnobTranslation + restingMargin;
if (restingMargin > 0 && clampMin <= m_maxKnobTranslation)
{
m_dragVisualClampMin = clampMin;
}
}
}

return S_OK;
}

// Whether the given key may cause the ToggleSwitch to toggle.
BOOLEAN
ToggleSwitch::HandlesKey(
Expand All @@ -1018,6 +1102,8 @@ _Check_return_ HRESULT ToggleSwitch::OnIsEnabledChanged(_In_ IsEnabledChangedEve
if (!bIsEnabled)
{
m_isDragging = FALSE;
m_dragVisualClampMax = -1;
m_dragVisualClampMin = -1;
m_isPointerOver = FALSE;
}
IFC(UpdateVisualState());
Expand All @@ -1037,6 +1123,8 @@ ToggleSwitch::OnVisibilityChanged()
if (xaml::Visibility_Visible != visibility)
{
m_isDragging = FALSE;
m_dragVisualClampMax = -1;
m_dragVisualClampMin = -1;
m_isPointerOver = FALSE;
}

Expand Down
7 changes: 7 additions & 0 deletions dxaml/xcp/dxaml/lib/ToggleSwitch_Partial.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ namespace DirectUI
_In_ IInspectable *pSender,
_In_ xaml::ISizeChangedEventArgs *pArgs);

_Check_return_ HRESULT
AdjustTranslationBoundsForDrag();

_Check_return_ HRESULT
TapHandler(
_In_ IInspectable *pSender,
Expand Down Expand Up @@ -156,6 +159,10 @@ namespace DirectUI

DOUBLE m_maxKnobTranslation;

// Visual-only drag limits; -1 means no additional clamp.
DOUBLE m_dragVisualClampMax;
DOUBLE m_dragVisualClampMin;

DOUBLE m_curtainTranslation;

DOUBLE m_minCurtainTranslation;
Expand Down
Loading