Coverage Report

Created: 2026-02-14 08:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org>
3
 * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#include <LibGfx/StylePainter.h>
9
#include <LibWeb/DOM/Document.h>
10
#include <LibWeb/HTML/BrowsingContext.h>
11
#include <LibWeb/HTML/HTMLInputElement.h>
12
#include <LibWeb/Layout/Label.h>
13
#include <LibWeb/Layout/RadioButton.h>
14
#include <LibWeb/Painting/InputColors.h>
15
#include <LibWeb/Painting/RadioButtonPaintable.h>
16
17
namespace Web::Painting {
18
19
JS_DEFINE_ALLOCATOR(RadioButtonPaintable);
20
21
JS::NonnullGCPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box)
22
0
{
23
0
    return layout_box.heap().allocate_without_realm<RadioButtonPaintable>(layout_box);
24
0
}
25
26
RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
27
0
    : LabelablePaintable(layout_box)
28
0
{
29
0
}
30
31
void RadioButtonPaintable::paint(PaintContext& context, PaintPhase phase) const
32
0
{
33
0
    if (!is_visible())
34
0
        return;
35
36
0
    PaintableBox::paint(context, phase);
37
38
0
    if (phase != PaintPhase::Foreground)
39
0
        return;
40
41
0
    auto draw_circle = [&](auto const& rect, Color color) {
42
        // Note: Doing this is a bit more forgiving than draw_circle() which will round to the nearset even radius.
43
        // This will fudge it (which works better here).
44
0
        context.display_list_recorder().fill_rect_with_rounded_corners(rect, color, rect.width() / 2);
45
0
    };
46
47
0
    auto shrink_all = [&](auto const& rect, int amount) {
48
0
        return rect.shrunken(amount, amount, amount, amount);
49
0
    };
50
51
0
    auto const& radio_button = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node());
52
53
0
    auto& palette = context.palette();
54
0
    bool enabled = layout_box().dom_node().enabled();
55
0
    auto input_colors = compute_input_colors(palette, computed_values().accent_color());
56
57
0
    auto background_color = input_colors.background_color(enabled);
58
0
    auto accent = input_colors.accent;
59
60
0
    auto radio_color = [&] {
61
0
        if (radio_button.checked()) {
62
            // Handle the awkward case where a light color has been used for the accent color.
63
0
            if (accent.contrast_ratio(background_color) < 2 && accent.contrast_ratio(input_colors.dark_gray) > 2)
64
0
                background_color = input_colors.dark_gray;
65
0
            return accent;
66
0
        }
67
0
        return input_colors.gray;
68
0
    };
69
70
0
    auto fill_color = [&] {
71
0
        if (!enabled)
72
0
            return input_colors.mid_gray;
73
0
        auto color = radio_color();
74
0
        if (being_pressed())
75
0
            color = InputColors::get_shade(color, 0.3f, palette.is_dark());
76
0
        return color;
77
0
    }();
78
79
    // This is based on a 1px outer border and 2px inner border when drawn at 13x13.
80
0
    auto radio_button_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>();
81
0
    auto outer_border_width = max(1, static_cast<int>(ceilf(radio_button_rect.width() / 13.0f)));
82
0
    auto inner_border_width = max(2, static_cast<int>(ceilf(radio_button_rect.width() / 4.0f)));
83
84
0
    draw_circle(radio_button_rect, fill_color);
85
0
    draw_circle(shrink_all(radio_button_rect, outer_border_width), background_color);
86
0
    if (radio_button.checked())
87
0
        draw_circle(shrink_all(radio_button_rect, inner_border_width), fill_color);
88
0
}
89
90
}