Coverage Report

Created: 2025-09-05 06:52

/src/serenity/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp
Line
Count
Source (jump to first uncovered line)
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/AntiAliasingPainter.h>
9
#include <LibGfx/Bitmap.h>
10
#include <LibGfx/GrayscaleBitmap.h>
11
#include <LibWeb/HTML/BrowsingContext.h>
12
#include <LibWeb/HTML/HTMLImageElement.h>
13
#include <LibWeb/HTML/HTMLInputElement.h>
14
#include <LibWeb/Layout/CheckBox.h>
15
#include <LibWeb/Layout/Label.h>
16
#include <LibWeb/Painting/CheckBoxPaintable.h>
17
#include <LibWeb/Painting/InputColors.h>
18
19
namespace Web::Painting {
20
21
JS_DEFINE_ALLOCATOR(CheckBoxPaintable);
22
23
static Gfx::Path check_mark_path(Gfx::IntRect checkbox_rect)
24
0
{
25
0
    Gfx::Path path;
26
0
    path.move_to({ 72, 14 });
27
0
    path.line_to({ 37, 64 });
28
0
    path.line_to({ 19, 47 });
29
0
    path.line_to({ 8, 58 });
30
0
    path.line_to({ 40, 89 });
31
0
    path.line_to({ 85, 24 });
32
0
    path.close();
33
34
0
    float const checkmark_width = 100;
35
0
    float const checkmark_height = 100;
36
0
    Gfx::AffineTransform scale_checkmark_to_fit;
37
0
    scale_checkmark_to_fit.scale(checkbox_rect.width() / checkmark_width, checkbox_rect.height() / checkmark_height);
38
0
    return path.copy_transformed(scale_checkmark_to_fit);
39
0
}
40
41
JS::NonnullGCPtr<CheckBoxPaintable>
42
CheckBoxPaintable::create(Layout::CheckBox const& layout_box)
43
0
{
44
0
    return layout_box.heap().allocate_without_realm<CheckBoxPaintable>(layout_box);
45
0
}
46
47
CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box)
48
0
    : LabelablePaintable(layout_box)
49
0
{
50
0
}
51
52
Layout::CheckBox const& CheckBoxPaintable::layout_box() const
53
0
{
54
0
    return static_cast<Layout::CheckBox const&>(layout_node());
55
0
}
56
57
Layout::CheckBox& CheckBoxPaintable::layout_box()
58
0
{
59
0
    return static_cast<Layout::CheckBox&>(layout_node());
60
0
}
61
62
void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const
63
0
{
64
0
    if (!is_visible())
65
0
        return;
66
67
0
    PaintableBox::paint(context, phase);
68
69
0
    if (phase != PaintPhase::Foreground)
70
0
        return;
71
72
0
    auto const& checkbox = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node());
73
0
    bool enabled = layout_box().dom_node().enabled();
74
0
    auto checkbox_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>();
75
0
    auto checkbox_radius = checkbox_rect.width() / 5;
76
77
0
    auto& palette = context.palette();
78
79
0
    auto shade = [&](Color color, float amount) {
80
0
        return InputColors::get_shade(color, amount, palette.is_dark());
81
0
    };
82
83
0
    auto modify_color = [&](Color color) {
84
0
        if (being_pressed() && enabled)
85
0
            return shade(color, 0.3f);
86
0
        return color;
87
0
    };
88
89
0
    auto input_colors = compute_input_colors(palette, computed_values().accent_color());
90
91
0
    auto increase_contrast = [&](Color color, Color background) {
92
0
        auto constexpr min_contrast = 2;
93
0
        if (color.contrast_ratio(background) < min_contrast) {
94
0
            color = color.inverted();
95
0
            if (color.contrast_ratio(background) > min_contrast)
96
0
                return color;
97
0
        }
98
0
        return color;
99
0
    };
100
101
    // Little heuristic that smaller things look better with more smoothness.
102
0
    if (checkbox.checked() && !checkbox.indeterminate()) {
103
0
        auto background_color = enabled ? input_colors.accent : input_colors.mid_gray;
104
0
        context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect, modify_color(background_color), checkbox_radius);
105
0
        auto tick_color = increase_contrast(input_colors.base, background_color);
106
0
        if (!enabled)
107
0
            tick_color = shade(tick_color, 0.5f);
108
0
        context.display_list_recorder().fill_path({
109
0
            .path = check_mark_path(checkbox_rect),
110
0
            .color = tick_color,
111
0
            .translation = checkbox_rect.location().to_type<float>(),
112
0
        });
113
0
    } else {
114
0
        auto background_color = input_colors.background_color(enabled);
115
0
        auto border_thickness = max(1, checkbox_rect.width() / 10);
116
0
        context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect, modify_color(input_colors.border_color(enabled)), checkbox_radius);
117
0
        context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect.shrunken(border_thickness, border_thickness, border_thickness, border_thickness),
118
0
            background_color, max(0, checkbox_radius - border_thickness));
119
0
        if (checkbox.indeterminate()) {
120
0
            int radius = 0.05 * checkbox_rect.width();
121
0
            auto dash_color = increase_contrast(input_colors.dark_gray, background_color);
122
0
            auto dash_rect = checkbox_rect.inflated(-0.4 * checkbox_rect.width(), -0.8 * checkbox_rect.height());
123
0
            context.display_list_recorder().fill_rect_with_rounded_corners(dash_rect, dash_color, radius, radius, radius, radius);
124
0
        }
125
0
    }
126
0
}
127
128
}