ladybird/Libraries/LibWeb/Painting/RadioButtonPaintable.cpp
Aliaksandr Kalenik 568b7ce7ea LibWeb: Make Paintable tree ref-counted
The Paintable tree and its supplemental painting data structures were
GC allocated because that was the easiest way to manage it and avoid
leaks introduced by ref cycles. This included the Paintable subclasses
themselves plus StackingContext, ChromeWidget, Scrollbar, ResizeHandle,
and scroll-frame state.

We are now trying to reduce GC allocation churn on layout and painting
updates, so keeping this short-lived rendering tree outside the JS heap
is a better fit. Move Paintable to RefCountedTreeNode, make painting
helpers ref-counted or weakly reference Paintables, and update the
layout and event-handler call sites to use RefPtr/WeakPtr ownership.
2026-05-07 15:03:44 +02:00

87 lines
3.2 KiB
C++

/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/HTMLInputElement.h>
#include <LibWeb/Layout/RadioButton.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/InputColors.h>
#include <LibWeb/Painting/RadioButtonPaintable.h>
namespace Web::Painting {
NonnullRefPtr<RadioButtonPaintable> RadioButtonPaintable::create(Layout::RadioButton const& layout_box)
{
return adopt_ref(*new RadioButtonPaintable(layout_box));
}
RadioButtonPaintable::RadioButtonPaintable(Layout::RadioButton const& layout_box)
: PaintableBox(layout_box)
{
}
void RadioButtonPaintable::paint(DisplayListRecordingContext& context, PaintPhase phase) const
{
if (!is_visible())
return;
PaintableBox::paint(context, phase);
if (phase != PaintPhase::Foreground)
return;
auto draw_circle = [&](auto const& rect, Color color) {
// Note: Doing this is a bit more forgiving than draw_circle() which will round to the nearest even radius.
// This will fudge it (which works better here).
context.display_list_recorder().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&>(*dom_node());
bool enabled = radio_button.enabled();
auto input_colors = compute_input_colors(computed_values().color_scheme(), 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();
// FIXME: Make this only take effect while this element or its labels are hovered.
if (radio_button.is_being_activated())
color = InputColors::get_shade(color, 0.3f, computed_values().color_scheme());
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);
}
}