aboutsummaryrefslogtreecommitdiff
path: root/src/ui/controls/toggle_button.cpp
blob: 3cd5d3efb9d846782f8769909d16b381fe802b03 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#include "toggle_button.h"

#include <fmt/format.h>

#include "graph/graph.h"
#include "ui/animations/animation.h"

namespace cru::ui::controls
{
    using graph::CreateSolidBrush;
    using animations::AnimationBuilder;

    // ui length parameters of toggle button.
    constexpr float half_height = 15;
    constexpr float half_width = half_height * 2;
    constexpr float stroke_width = 3;
    constexpr float inner_circle_radius = half_height - stroke_width;
    constexpr float inner_circle_x = half_width - half_height;

    ToggleButton::ToggleButton() : current_circle_position_(-inner_circle_x)
    {
        graph::GraphManager::GetInstance()->GetD2D1Factory()->CreateRoundedRectangleGeometry(D2D1::RoundedRect(D2D1::RectF(-half_width, -half_height, half_width, half_height), half_height, half_height), &frame_path_);

        on_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::DeepSkyBlue));
        off_brush_ = CreateSolidBrush(D2D1::ColorF(D2D1::ColorF::LightGray));
    }

    inline D2D1_POINT_2F ConvertPoint(const Point& point)
    {
        return D2D1::Point2F(point.x, point.y);
    }

    bool ToggleButton::IsPointInside(const Point& point)
    {
        const auto size = GetSize();
        const auto transform = D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2);
        BOOL contains;
        frame_path_->FillContainsPoint(ConvertPoint(point), transform, &contains);
        if (!contains)
            frame_path_->StrokeContainsPoint(ConvertPoint(point), stroke_width, nullptr, transform, &contains);
        return contains != 0;
    }

    void ToggleButton::SetState(const bool state)
    {
        if (state != state_)
        {
            state_ = state;
            float destination_x;

            if (state)
                destination_x = inner_circle_x;
            else
                destination_x = -inner_circle_x;

            const auto previous_position = current_circle_position_;
            const auto delta = destination_x - current_circle_position_;

            constexpr auto total_time = FloatSecond(0.2);

            const auto time = total_time * (std::abs(delta) / (inner_circle_x * 2));

            // ReSharper disable once CppExpressionWithoutSideEffects
            AnimationBuilder(fmt::format(L"ToggleButton {}", reinterpret_cast<size_t>(this)), time)
            .AddStepHandler(CreatePtr<animations::AnimationStepHandlerPtr>([=](animations::AnimationDelegatePtr, const double percentage)
            {
                current_circle_position_ = static_cast<float>(previous_position + delta * percentage);
                Repaint();
            })).Start();

            RaiseToggleEvent(state);
            Repaint();
        }
    }

    void ToggleButton::Toggle()
    {
        SetState(!GetState());
    }

    void ToggleButton::OnToggle(events::ToggleEventArgs& args)
    {

    }

    void ToggleButton::OnDrawContent(ID2D1DeviceContext* device_context)
    {
        Control::OnDrawContent(device_context);
        const auto size = GetSize();
        graph::WithTransform(device_context, D2D1::Matrix3x2F::Translation(size.width / 2, size.height / 2), [this](ID2D1DeviceContext* device_context)
        {
            if (state_)
            {
                device_context->DrawGeometry(frame_path_.Get(), on_brush_.Get(), stroke_width);
                device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), on_brush_.Get());
            }
            else
            {
                device_context->DrawGeometry(frame_path_.Get(), off_brush_.Get(), stroke_width);
                device_context->FillEllipse(D2D1::Ellipse(D2D1::Point2F(current_circle_position_, 0), inner_circle_radius, inner_circle_radius), off_brush_.Get());
            }
        });
    }

    void ToggleButton::OnMouseClickCore(events::MouseButtonEventArgs& args)
    {
        Control::OnMouseClickCore(args);
        Toggle();
    }

    Size ToggleButton::OnMeasureContent(const Size& available_size)
    {
        const Size result_size(
            half_width * 2 + stroke_width,
            half_height * 2 + stroke_width
        );

        return result_size;
    }

    void ToggleButton::RaiseToggleEvent(bool new_state)
    {
        events::ToggleEventArgs args(this, this, new_state);
        OnToggle(args);
        toggle_event.Raise(args);
    }
}