summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp
blob: 08d8f97d9d335b7daf85082b644bceb9be5cc937 (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
/*
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
 * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibGUI/Event.h>
#include <LibGfx/StylePainter.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/Layout/Label.h>
#include <LibWeb/Layout/RadioButton.h>
#include <LibWeb/Painting/InputColors.h>
#include <LibWeb/Painting/RadioButtonPaintable.h>

namespace Web::Painting {

JS::NonnullGCPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box)
{
    return layout_box.heap().allocate_without_realm<RadioButtonPaintable>(layout_box);
}

RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
    : LabelablePaintable(layout_box)
{
}

void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
{
    if (!is_visible())
        return;

    PaintableBox::paint(context, phase);

    if (phase != PaintPhase::Foreground)
        return;

    Gfx::AntiAliasingPainter painter { context.painter() };

    auto draw_circle = [&](auto const& rect, Color color) {
        // Note: Doing this is a bit more forgiving than draw_circle() which will round to the nearset even radius.
        // This will fudge it (which works better here).
        painter.fill_rect_with_rounded_corners(rect, color, rect.width() / 2);
    };

    auto shrink_all = [&](auto const& rect, int amount) {
        return rect.shrunken(amount, amount, amount, amount);
    };

    auto const& radio_button = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node());

    auto& palette = context.palette();
    bool enabled = layout_box().dom_node().enabled();
    auto input_colors = compute_input_colors(palette, computed_values().accent_color());

    auto background_color = input_colors.background_color(enabled);
    auto accent = input_colors.accent;

    auto radio_color = [&] {
        if (radio_button.checked()) {
            // Handle the awkward case where a light color has been used for the accent color.
            if (accent.contrast_ratio(background_color) < 2 && accent.contrast_ratio(input_colors.dark_gray) > 2)
                background_color = input_colors.dark_gray;
            return accent;
        }
        return input_colors.gray;
    };

    auto fill_color = [&] {
        if (!enabled)
            return input_colors.mid_gray;
        auto color = radio_color();
        if (being_pressed())
            color = InputColors::get_shade(color, 0.3f, palette.is_dark());
        return color;
    }();

    // This is based on a 1px outer border and 2px inner border when drawn at 13x13.
    auto radio_button_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>();
    auto outer_border_width = max(1, static_cast<int>(ceilf(radio_button_rect.width() / 13.0f)));
    auto inner_border_width = max(2, static_cast<int>(ceilf(radio_button_rect.width() / 4.0f)));

    draw_circle(radio_button_rect, fill_color);
    draw_circle(shrink_all(radio_button_rect, outer_border_width), background_color);
    if (radio_button.checked())
        draw_circle(shrink_all(radio_button_rect, inner_border_width), fill_color);
}

}