/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 | | } |