diff options
author | MacDue <macdue@dueutil.tech> | 2023-03-19 23:55:03 +0000 |
---|---|---|
committer | Sam Atkins <atkinssj@gmail.com> | 2023-03-23 08:27:51 +0000 |
commit | cfc99666546a82c40286ad732474f675e60817a5 (patch) | |
tree | ab1e37eb7b53d015ac305ceeb891303b071c04fd | |
parent | 77456d1d0b8d8c90c03edb46fd184acef51cd7a1 (diff) | |
download | serenity-cfc99666546a82c40286ad732474f675e60817a5.zip |
LibWeb: Add scaleable checkboxes (with theme/accent-color support)
This reworks checkboxes to use a tiny 16x16 SDF for the tick icon along
with an antialiased background/border.
The checkbox now works well at any scale, shows the various checkbox
state (enabled, disabled, being pressed), and supports using the colors
from the active system theme and/or the accent-color property.
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Default.css | 27 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp | 128 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h | 2 |
3 files changed, 129 insertions, 28 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Default.css b/Userland/Libraries/LibWeb/CSS/Default.css index 8224e05ad8..c42ad12640 100644 --- a/Userland/Libraries/LibWeb/CSS/Default.css +++ b/Userland/Libraries/LibWeb/CSS/Default.css @@ -64,33 +64,6 @@ button:hover, input[type=submit]:hover, input[type=button]:hover, input[type=res background-color: -libweb-palette-hover-highlight; } -input[type=checkbox] { - display: inline-block; - width: 12px; - height: 12px; - margin: 2px; - border: 1px solid -libweb-palette-threed-shadow1; - background-repeat: no-repeat; - background-size: cover; -} - -input[type=checkbox]:checked { - /* - This roughly resembles ClassicStylePainter's paint_check_box() while uncoupling the styling from LibGfx, similar to - <button> above. This is a simple checkmark that still looks ok at 2x scale. - Eventually we should respect the `accent-color` property here, which may require going back to an implementation via - CheckBoxPaintable::paint(). - */ - image-rendering: pixelated; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABHNCSVQICAgIfAhkiAAAAEhJREFUKFNjYBgs4D/MIYxEuAiuGKiWkZAGFMUgw5mQbECWBAljKAYJwmxAl0Tnw81FdhK6DcgGYtWA0xlw1TgY2GzCoZQWwgCU1woFwvnCFQAAAABJRU5ErkJggg==); -} - -input[type=checkbox]:indeterminate { - image-rendering: pixelated; - /* FIXME: Eventually respect the `accent-color` property here. */ - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAACZJREFUKFNjZCARMJKonmGQa/hPwD9g5yP7gWQNRAXYIA8lovwAAHYYAg3vRihbAAAAAElFTkSuQmCC); -} - option { display: none; } diff --git a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp index 22c95bdf8d..9ab7e6d5b2 100644 --- a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.cpp @@ -1,10 +1,14 @@ /* * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2023, MacDue <macdue@dueutil.tech> * * SPDX-License-Identifier: BSD-2-Clause */ #include <LibGUI/Event.h> +#include <LibGfx/AntiAliasingPainter.h> +#include <LibGfx/Bitmap.h> +#include <LibGfx/GrayscaleBitmap.h> #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/HTMLImageElement.h> #include <LibWeb/Layout/CheckBox.h> @@ -13,7 +17,60 @@ namespace Web::Painting { -JS::NonnullGCPtr<CheckBoxPaintable> CheckBoxPaintable::create(Layout::CheckBox const& layout_box) +// A 16x16 signed distance field for the checkbox's tick (slightly rounded): +static Array<u8, 16 * 16> s_check_mark_sdf { + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 251, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 231, 194, 189, 218, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 245, 193, 142, 131, 165, 205, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 209, 156, 105, 78, 116, 174, 237, + 254, 254, 254, 254, 254, 254, 254, 254, 226, 173, 120, 69, 79, 132, 185, 243, + 254, 254, 254, 254, 254, 254, 254, 243, 190, 138, 85, 62, 115, 167, 219, 254, + 254, 254, 227, 203, 212, 249, 254, 207, 154, 102, 50, 98, 149, 202, 254, 254, + 254, 225, 180, 141, 159, 204, 224, 171, 119, 67, 81, 134, 186, 238, 254, 254, + 243, 184, 135, 90, 113, 157, 188, 136, 84, 64, 116, 169, 221, 254, 254, 254, + 237, 174, 118, 71, 68, 113, 153, 100, 48, 100, 152, 204, 254, 254, 254, 254, + 254, 208, 162, 116, 71, 67, 107, 65, 83, 135, 187, 240, 254, 254, 254, 254, + 254, 251, 206, 162, 116, 71, 43, 66, 119, 171, 223, 254, 254, 254, 254, 254, + 254, 254, 251, 206, 162, 116, 73, 102, 154, 207, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 251, 206, 162, 124, 139, 190, 242, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 251, 210, 187, 194, 229, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 251, 254, 254, 254, 254, 254, 254, 254, 254, 254 +}; + +// A 16x16 signed distance field for an indeterminate checkbox (rounded line) +// Note: We could use the AA fill_rect_with_rounded_corners() for this in future, +// though right now it can't draw at subpixel accuracy (so is misaligned and jitters when scaling). +static Array<u8, 16 * 16> s_check_indeterminate { + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 239, 211, 209, 209, 209, 209, 209, 209, 211, 237, 254, 254, 254, + 254, 254, 252, 195, 151, 145, 145, 145, 145, 145, 145, 150, 193, 250, 254, 254, + 254, 254, 243, 179, 115, 81, 81, 81, 81, 81, 81, 113, 177, 241, 254, 254, + 254, 254, 243, 179, 115, 79, 79, 79, 79, 79, 79, 113, 177, 241, 254, 254, + 254, 254, 251, 194, 149, 143, 143, 143, 143, 143, 143, 148, 192, 250, 254, 254, + 254, 254, 254, 237, 210, 207, 207, 207, 207, 207, 207, 209, 236, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254 +}; + +static constexpr Gfx::GrayscaleBitmap check_mark_sdf() +{ + return Gfx::GrayscaleBitmap(s_check_mark_sdf, 16, 16); +} + +static constexpr Gfx::GrayscaleBitmap check_indeterminate_sdf() +{ + return Gfx::GrayscaleBitmap(s_check_indeterminate, 16, 16); +} + +JS::NonnullGCPtr<CheckBoxPaintable> +CheckBoxPaintable::create(Layout::CheckBox const& layout_box) { return layout_box.heap().allocate_without_realm<CheckBoxPaintable>(layout_box); } @@ -33,4 +90,73 @@ Layout::CheckBox& CheckBoxPaintable::layout_box() return static_cast<Layout::CheckBox&>(layout_node()); } +void CheckBoxPaintable::paint(PaintContext& context, PaintPhase phase) const +{ + if (!is_visible()) + return; + + PaintableBox::paint(context, phase); + + if (phase != PaintPhase::Foreground) + return; + + auto const& checkbox = static_cast<HTML::HTMLInputElement const&>(layout_box().dom_node()); + bool enabled = layout_box().dom_node().enabled(); + Gfx::AntiAliasingPainter painter { context.painter() }; + auto checkbox_rect = context.enclosing_device_rect(absolute_rect()).to_type<int>(); + auto checkbox_radius = checkbox_rect.width() / 5; + + auto& palette = context.palette(); + + auto shade = [&](Color color, float amount) { + return color.mixed_with(palette.is_dark() ? Color::Black : Color::White, amount); + }; + + auto modify_color = [&](Color color) { + if (being_pressed() && enabled) + return shade(color, 0.3f); + return color; + }; + + auto base_text_color = palette.color(ColorRole::BaseText); + auto accent = computed_values().accent_color().value_or(palette.color(ColorRole::Accent)); + auto base = shade(base_text_color.inverted(), 0.8f); + auto dark_gray = shade(base_text_color, 0.3f); + auto gray = shade(dark_gray, 0.4f); + auto mid_gray = shade(gray, 0.3f); + auto light_gray = shade(mid_gray, 0.3f); + + 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. + float smoothness = 1.0f / (max(checkbox_rect.width(), checkbox_rect.height()) / 2); + if (checkbox.checked() && !checkbox.indeterminate()) { + auto background_color = enabled ? accent : mid_gray; + painter.fill_rect_with_rounded_corners(checkbox_rect, modify_color(background_color), checkbox_radius); + auto tick_color = increase_contrast(base, background_color); + if (!enabled) + tick_color = shade(tick_color, 0.5f); + context.painter().draw_signed_distance_field(checkbox_rect, tick_color, check_mark_sdf(), smoothness); + } else { + auto background_color = enabled ? base : light_gray; + auto border_thickness = max(1, checkbox_rect.width() / 10); + painter.fill_rect_with_rounded_corners(checkbox_rect, modify_color(enabled ? gray : mid_gray), checkbox_radius); + painter.fill_rect_with_rounded_corners(checkbox_rect.shrunken(border_thickness, border_thickness, border_thickness, border_thickness), + enabled ? base : light_gray, max(0, checkbox_radius - border_thickness)); + if (checkbox.indeterminate()) { + auto dash_color = increase_contrast(dark_gray, background_color); + context.painter().draw_signed_distance_field(checkbox_rect, + modify_color(enabled ? dash_color : shade(dash_color, 0.3f)), check_indeterminate_sdf(), smoothness); + } + } +} + } diff --git a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h index 4c1a91e34d..0b9d85cd08 100644 --- a/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h +++ b/Userland/Libraries/LibWeb/Painting/CheckBoxPaintable.h @@ -20,6 +20,8 @@ public: Layout::CheckBox const& layout_box() const; Layout::CheckBox& layout_box(); + virtual void paint(PaintContext&, PaintPhase) const override; + private: CheckBoxPaintable(Layout::CheckBox const&); }; |