2022-03-10 14:02:25 +01:00
|
|
|
/*
|
2024-10-04 13:19:50 +02:00
|
|
|
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
|
2023-03-19 23:55:03 +00:00
|
|
|
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
2022-03-10 14:02:25 +01:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <LibWeb/HTML/HTMLImageElement.h>
|
2024-09-23 11:13:14 -04:00
|
|
|
#include <LibWeb/HTML/HTMLInputElement.h>
|
2022-03-10 14:02:25 +01:00
|
|
|
#include <LibWeb/Layout/CheckBox.h>
|
|
|
|
#include <LibWeb/Painting/CheckBoxPaintable.h>
|
2025-02-02 15:55:26 +01:00
|
|
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
2023-03-23 22:51:22 +00:00
|
|
|
#include <LibWeb/Painting/InputColors.h>
|
2025-08-02 21:16:51 +02:00
|
|
|
#include <LibWeb/Painting/PaintStyle.h>
|
2022-03-10 14:02:25 +01:00
|
|
|
|
|
|
|
namespace Web::Painting {
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
GC_DEFINE_ALLOCATOR(CheckBoxPaintable);
|
2024-04-06 10:16:04 -07:00
|
|
|
|
2024-08-09 14:00:10 +02:00
|
|
|
static Gfx::Path check_mark_path(Gfx::IntRect checkbox_rect)
|
2023-03-19 23:55:03 +00:00
|
|
|
{
|
2024-08-09 14:00:10 +02:00
|
|
|
Gfx::Path path;
|
2024-06-13 17:31:58 +03:00
|
|
|
path.move_to({ 72, 14 });
|
|
|
|
path.line_to({ 37, 64 });
|
|
|
|
path.line_to({ 19, 47 });
|
|
|
|
path.line_to({ 8, 58 });
|
|
|
|
path.line_to({ 40, 89 });
|
|
|
|
path.line_to({ 85, 24 });
|
|
|
|
path.close();
|
|
|
|
|
|
|
|
float const checkmark_width = 100;
|
|
|
|
float const checkmark_height = 100;
|
|
|
|
Gfx::AffineTransform scale_checkmark_to_fit;
|
|
|
|
scale_checkmark_to_fit.scale(checkbox_rect.width() / checkmark_width, checkbox_rect.height() / checkmark_height);
|
|
|
|
return path.copy_transformed(scale_checkmark_to_fit);
|
2023-03-19 23:55:03 +00:00
|
|
|
}
|
|
|
|
|
2024-11-15 04:01:23 +13:00
|
|
|
GC::Ref<CheckBoxPaintable>
|
2023-03-19 23:55:03 +00:00
|
|
|
CheckBoxPaintable::create(Layout::CheckBox const& layout_box)
|
2022-03-10 14:02:25 +01:00
|
|
|
{
|
2024-11-14 06:13:46 +13:00
|
|
|
return layout_box.heap().allocate<CheckBoxPaintable>(layout_box);
|
2022-03-10 14:02:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
CheckBoxPaintable::CheckBoxPaintable(Layout::CheckBox const& layout_box)
|
2022-03-10 22:46:35 +01:00
|
|
|
: LabelablePaintable(layout_box)
|
2022-03-10 14:02:25 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2025-07-31 23:07:26 +02:00
|
|
|
void CheckBoxPaintable::paint(DisplayListRecordingContext& context, PaintPhase phase) const
|
2023-03-19 23:55:03 +00:00
|
|
|
{
|
|
|
|
if (!is_visible())
|
|
|
|
return;
|
|
|
|
|
|
|
|
PaintableBox::paint(context, phase);
|
|
|
|
|
|
|
|
if (phase != PaintPhase::Foreground)
|
|
|
|
return;
|
|
|
|
|
2025-10-10 03:47:30 +02:00
|
|
|
auto const& checkbox = as<HTML::HTMLInputElement const>(*dom_node());
|
2023-03-19 23:55:03 +00:00
|
|
|
bool enabled = layout_box().dom_node().enabled();
|
|
|
|
auto checkbox_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>();
|
|
|
|
auto checkbox_radius = checkbox_rect.width() / 5;
|
|
|
|
|
|
|
|
auto shade = [&](Color color, float amount) {
|
2025-01-02 12:59:09 +11:00
|
|
|
return InputColors::get_shade(color, amount, computed_values().color_scheme());
|
2023-03-19 23:55:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
auto modify_color = [&](Color color) {
|
|
|
|
if (being_pressed() && enabled)
|
|
|
|
return shade(color, 0.3f);
|
|
|
|
return color;
|
|
|
|
};
|
|
|
|
|
2025-01-02 12:59:09 +11:00
|
|
|
auto input_colors = compute_input_colors(computed_values().color_scheme(), computed_values().accent_color());
|
2023-03-19 23:55:03 +00:00
|
|
|
|
|
|
|
auto increase_contrast = [&](Color color, Color background) {
|
|
|
|
auto constexpr min_contrast = 2;
|
|
|
|
if (color.contrast_ratio(background) < min_contrast) {
|
|
|
|
color = color.inverted();
|
|
|
|
if (color.contrast_ratio(background) > min_contrast)
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
return color;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Little heuristic that smaller things look better with more smoothness.
|
|
|
|
if (checkbox.checked() && !checkbox.indeterminate()) {
|
2023-03-23 22:51:22 +00:00
|
|
|
auto background_color = enabled ? input_colors.accent : input_colors.mid_gray;
|
2024-06-23 18:40:10 +02:00
|
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect, modify_color(background_color), checkbox_radius);
|
2023-03-23 22:51:22 +00:00
|
|
|
auto tick_color = increase_contrast(input_colors.base, background_color);
|
2023-03-19 23:55:03 +00:00
|
|
|
if (!enabled)
|
|
|
|
tick_color = shade(tick_color, 0.5f);
|
2025-08-02 22:39:39 +02:00
|
|
|
auto path = check_mark_path(checkbox_rect);
|
|
|
|
path.offset(checkbox_rect.location().to_type<float>());
|
|
|
|
context.display_list_recorder().fill_path({ .path = move(path), .paint_style_or_color = tick_color });
|
2023-03-19 23:55:03 +00:00
|
|
|
} else {
|
2023-03-23 22:51:22 +00:00
|
|
|
auto background_color = input_colors.background_color(enabled);
|
2023-03-19 23:55:03 +00:00
|
|
|
auto border_thickness = max(1, checkbox_rect.width() / 10);
|
2024-06-23 18:40:10 +02:00
|
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect, modify_color(input_colors.border_color(enabled)), checkbox_radius);
|
|
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(checkbox_rect.shrunken(border_thickness, border_thickness, border_thickness, border_thickness),
|
2023-03-23 22:51:22 +00:00
|
|
|
background_color, max(0, checkbox_radius - border_thickness));
|
2023-03-19 23:55:03 +00:00
|
|
|
if (checkbox.indeterminate()) {
|
2024-06-13 17:31:58 +03:00
|
|
|
int radius = 0.05 * checkbox_rect.width();
|
2023-03-23 22:51:22 +00:00
|
|
|
auto dash_color = increase_contrast(input_colors.dark_gray, background_color);
|
2024-06-13 17:31:58 +03:00
|
|
|
auto dash_rect = checkbox_rect.inflated(-0.4 * checkbox_rect.width(), -0.8 * checkbox_rect.height());
|
2025-08-19 12:56:23 +02:00
|
|
|
context.display_list_recorder().fill_rect_with_rounded_corners(dash_rect, dash_color, radius);
|
2023-03-19 23:55:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-10 14:02:25 +01:00
|
|
|
}
|