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.
87 lines
3.2 KiB
C++
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);
|
|
}
|
|
|
|
}
|