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