mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +00:00
Ladybird: Use custom QIconEngine to render scalable/vector icons
Rather than render the icons to a 16x16 bitmap, keep them as vector graphics and render them on request. This keeps the icons crisp on high DPI displays.
This commit is contained in:
parent
3e6ca1085c
commit
1837e94ba4
Notes:
sideshowbarker
2024-07-17 05:09:48 +09:00
Author: https://github.com/MacDue Commit: https://github.com/SerenityOS/serenity/commit/1837e94ba4 Pull-request: https://github.com/SerenityOS/serenity/pull/20254
5 changed files with 170 additions and 31 deletions
|
@ -91,6 +91,7 @@ set(SOURCES
|
||||||
Settings.cpp
|
Settings.cpp
|
||||||
SettingsDialog.cpp
|
SettingsDialog.cpp
|
||||||
Tab.cpp
|
Tab.cpp
|
||||||
|
TVGIconEngine.cpp
|
||||||
Utilities.cpp
|
Utilities.cpp
|
||||||
WebContentView.cpp
|
WebContentView.cpp
|
||||||
ladybird.qrc
|
ladybird.qrc
|
||||||
|
|
79
Ladybird/TVGIconEngine.cpp
Normal file
79
Ladybird/TVGIconEngine.cpp
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/MemoryStream.h>
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <LibGfx/Painter.h>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPixmapCache>
|
||||||
|
|
||||||
|
#include "TVGIconEngine.h"
|
||||||
|
#include "Utilities.h"
|
||||||
|
|
||||||
|
void TVGIconEngine::paint(QPainter* qpainter, QRect const& rect, QIcon::Mode mode, QIcon::State state)
|
||||||
|
{
|
||||||
|
qpainter->drawPixmap(rect, pixmap(rect.size(), mode, state));
|
||||||
|
}
|
||||||
|
|
||||||
|
QIconEngine* TVGIconEngine::clone() const
|
||||||
|
{
|
||||||
|
return new TVGIconEngine(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap TVGIconEngine::pixmap(QSize const& size, QIcon::Mode mode, QIcon::State state)
|
||||||
|
{
|
||||||
|
QPixmap pixmap;
|
||||||
|
auto key = pixmap_cache_key(size, mode, state);
|
||||||
|
if (QPixmapCache::find(key, &pixmap))
|
||||||
|
return pixmap;
|
||||||
|
auto bitmap = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, { size.width(), size.height() }));
|
||||||
|
Gfx::Painter painter { *bitmap };
|
||||||
|
auto icon_rect = m_image_data->rect().to_type<float>();
|
||||||
|
auto scale = min(size.width() / icon_rect.width(), size.height() / icon_rect.height()) * m_scale;
|
||||||
|
auto centered = Gfx::FloatRect { {}, icon_rect.size().scaled_by(scale) }
|
||||||
|
.centered_within(Gfx::FloatRect { {}, { size.width(), size.height() } });
|
||||||
|
auto transform = Gfx::AffineTransform {}
|
||||||
|
.translate(centered.location())
|
||||||
|
.multiply(Gfx::AffineTransform {}.scale(scale, scale));
|
||||||
|
m_image_data->draw_transformed(painter, transform);
|
||||||
|
for (auto const& filter : m_filters) {
|
||||||
|
if (filter->mode() == mode) {
|
||||||
|
painter.blit_filtered({}, *bitmap, bitmap->rect(), filter->function(), false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QImage qimage { bitmap->scanline_u8(0), bitmap->width(), bitmap->height(), QImage::Format::Format_ARGB32 };
|
||||||
|
pixmap = QPixmap::fromImage(qimage);
|
||||||
|
if (!pixmap.isNull())
|
||||||
|
QPixmapCache::insert(key, pixmap);
|
||||||
|
return pixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TVGIconEngine::pixmap_cache_key(QSize const& size, QIcon::Mode mode, QIcon::State state)
|
||||||
|
{
|
||||||
|
return qstring_from_ak_string(
|
||||||
|
MUST(String::formatted("$sernity_tvgicon_{}_{}x{}_{}_{}", m_cache_id, size.width(), size.height(), to_underlying(mode), to_underlying(state))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TVGIconEngine::add_filter(QIcon::Mode mode, Function<Color(Color)> filter)
|
||||||
|
{
|
||||||
|
m_filters.empend(adopt_ref(*new Filter(mode, move(filter))));
|
||||||
|
invalidate_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
TVGIconEngine* TVGIconEngine::from_file(QString const& path)
|
||||||
|
{
|
||||||
|
QFile icon_resource(path);
|
||||||
|
if (!icon_resource.open(QIODeviceBase::ReadOnly))
|
||||||
|
return nullptr;
|
||||||
|
auto icon_data = icon_resource.readAll();
|
||||||
|
FixedMemoryStream icon_bytes { ReadonlyBytes { icon_data.data(), static_cast<size_t>(icon_data.size()) } };
|
||||||
|
if (auto tvg = Gfx::TinyVGDecodedImageData::decode(icon_bytes); !tvg.is_error())
|
||||||
|
return new TVGIconEngine(tvg.release_value());
|
||||||
|
return nullptr;
|
||||||
|
}
|
71
Ladybird/TVGIconEngine.h
Normal file
71
Ladybird/TVGIconEngine.h
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/RefCounted.h>
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
#include <LibGfx/ImageFormats/TinyVGLoader.h>
|
||||||
|
#include <QIconEngine>
|
||||||
|
|
||||||
|
class TVGIconEngine : public QIconEngine {
|
||||||
|
public:
|
||||||
|
TVGIconEngine(Gfx::TinyVGDecodedImageData const& image_data)
|
||||||
|
: m_image_data(image_data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static TVGIconEngine* from_file(QString const& path);
|
||||||
|
|
||||||
|
void paint(QPainter* painter, QRect const& rect, QIcon::Mode mode,
|
||||||
|
QIcon::State state) override;
|
||||||
|
QIconEngine* clone() const override;
|
||||||
|
QPixmap pixmap(QSize const& size, QIcon::Mode mode,
|
||||||
|
QIcon::State state) override;
|
||||||
|
|
||||||
|
void add_filter(QIcon::Mode mode, Function<Color(Color)> filter);
|
||||||
|
|
||||||
|
void set_scale(float scale)
|
||||||
|
{
|
||||||
|
m_scale = scale;
|
||||||
|
invalidate_cache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static unsigned next_cache_id()
|
||||||
|
{
|
||||||
|
static unsigned cache_id = 0;
|
||||||
|
return cache_id++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void invalidate_cache()
|
||||||
|
{
|
||||||
|
m_cache_id = next_cache_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Filter : public RefCounted<Filter> {
|
||||||
|
public:
|
||||||
|
Filter(QIcon::Mode mode, Function<Color(Color)> function)
|
||||||
|
: m_mode(mode)
|
||||||
|
, m_function(move(function))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QIcon::Mode mode() const { return m_mode; }
|
||||||
|
Function<Color(Color)> const& function() const { return m_function; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
QIcon::Mode m_mode;
|
||||||
|
Function<Color(Color)> m_function;
|
||||||
|
};
|
||||||
|
|
||||||
|
QString pixmap_cache_key(QSize const& size, QIcon::Mode mode, QIcon::State state);
|
||||||
|
|
||||||
|
float m_scale { 1 };
|
||||||
|
Vector<NonnullRefPtr<Filter>> m_filters;
|
||||||
|
NonnullRefPtr<Gfx::TinyVGDecodedImageData> m_image_data;
|
||||||
|
unsigned m_cache_id { next_cache_id() };
|
||||||
|
};
|
|
@ -9,6 +9,7 @@
|
||||||
#include "ConsoleWidget.h"
|
#include "ConsoleWidget.h"
|
||||||
#include "InspectorWidget.h"
|
#include "InspectorWidget.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
#include "TVGIconEngine.h"
|
||||||
#include "Utilities.h"
|
#include "Utilities.h"
|
||||||
#include <Browser/History.h>
|
#include <Browser/History.h>
|
||||||
#include <LibGfx/ImageFormats/BMPWriter.h>
|
#include <LibGfx/ImageFormats/BMPWriter.h>
|
||||||
|
@ -31,33 +32,20 @@
|
||||||
extern DeprecatedString s_serenity_resource_root;
|
extern DeprecatedString s_serenity_resource_root;
|
||||||
extern Browser::Settings* s_settings;
|
extern Browser::Settings* s_settings;
|
||||||
|
|
||||||
static QIcon render_tvg_icon_with_theme_colors(QString name, QPalette const& palette)
|
static QIcon create_tvg_icon_with_theme_colors(QString name, QPalette const& palette)
|
||||||
{
|
{
|
||||||
auto path = QString(":/Icons/%1.tvg").arg(name);
|
auto path = QString(":/Icons/%1.tvg").arg(name);
|
||||||
Gfx::IntSize icon_size(16, 16);
|
auto icon_engine = TVGIconEngine::from_file(path);
|
||||||
|
VERIFY(icon_engine);
|
||||||
QFile icon_resource(path);
|
auto icon_filter = [](QColor color) {
|
||||||
VERIFY(icon_resource.open(QIODeviceBase::ReadOnly));
|
return [color = Color::from_argb(color.rgba64().toArgb32())](Gfx::Color icon_color) {
|
||||||
auto icon_data = icon_resource.readAll();
|
return color.with_alpha((icon_color.alpha() * color.alpha()) / 255);
|
||||||
ReadonlyBytes icon_bytes { icon_data.data(), static_cast<size_t>(icon_data.size()) };
|
};
|
||||||
auto icon_raster = MUST(Gfx::Bitmap::load_from_bytes(icon_bytes, icon_size));
|
|
||||||
|
|
||||||
QIcon icon;
|
|
||||||
auto render = [&](QColor color) -> QPixmap {
|
|
||||||
auto image = MUST(Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, icon_size));
|
|
||||||
Gfx::Painter painter { image };
|
|
||||||
auto icon_color = Color::from_argb(color.rgba64().toArgb32());
|
|
||||||
painter.blit_filtered({ 0, 0 }, *icon_raster, icon_raster->rect(), [&](auto color) {
|
|
||||||
return icon_color.with_alpha((icon_color.alpha() * color.alpha()) / 255);
|
|
||||||
});
|
|
||||||
QImage qimage { image->scanline_u8(0), image->width(), image->height(), QImage::Format::Format_ARGB32 };
|
|
||||||
return QPixmap::fromImage(qimage);
|
|
||||||
};
|
};
|
||||||
|
icon_engine->add_filter(QIcon::Mode::Normal, icon_filter(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)));
|
||||||
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Normal, QPalette::ColorRole::ButtonText)), QIcon::Mode::Normal);
|
icon_engine->add_filter(QIcon::Mode::Disabled, icon_filter(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)));
|
||||||
icon.addPixmap(render(palette.color(QPalette::ColorGroup::Disabled, QPalette::ColorRole::ButtonText)), QIcon::Mode::Disabled);
|
icon_engine->set_scale(0.66f);
|
||||||
|
return QIcon(icon_engine);
|
||||||
return icon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling, WebView::UseJavaScriptBytecode use_javascript_bytecode)
|
Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::EnableCallgrindProfiling enable_callgrind_profiling, WebView::UseJavaScriptBytecode use_javascript_bytecode)
|
||||||
|
@ -85,7 +73,7 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView::
|
||||||
m_layout->addWidget(m_toolbar);
|
m_layout->addWidget(m_toolbar);
|
||||||
m_layout->addWidget(m_view);
|
m_layout->addWidget(m_view);
|
||||||
|
|
||||||
rerender_toolbar_icons();
|
recreate_toolbar_icons();
|
||||||
|
|
||||||
m_toolbar->addAction(&m_window->go_back_action());
|
m_toolbar->addAction(&m_window->go_back_action());
|
||||||
m_toolbar->addAction(&m_window->go_forward_action());
|
m_toolbar->addAction(&m_window->go_forward_action());
|
||||||
|
@ -614,18 +602,18 @@ void Tab::update_hover_label()
|
||||||
bool Tab::event(QEvent* event)
|
bool Tab::event(QEvent* event)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::PaletteChange) {
|
if (event->type() == QEvent::PaletteChange) {
|
||||||
rerender_toolbar_icons();
|
recreate_toolbar_icons();
|
||||||
return QWidget::event(event);
|
return QWidget::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return QWidget::event(event);
|
return QWidget::event(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tab::rerender_toolbar_icons()
|
void Tab::recreate_toolbar_icons()
|
||||||
{
|
{
|
||||||
m_window->go_back_action().setIcon(render_tvg_icon_with_theme_colors("back", palette()));
|
m_window->go_back_action().setIcon(create_tvg_icon_with_theme_colors("back", palette()));
|
||||||
m_window->go_forward_action().setIcon(render_tvg_icon_with_theme_colors("forward", palette()));
|
m_window->go_forward_action().setIcon(create_tvg_icon_with_theme_colors("forward", palette()));
|
||||||
m_window->reload_action().setIcon(render_tvg_icon_with_theme_colors("reload", palette()));
|
m_window->reload_action().setIcon(create_tvg_icon_with_theme_colors("reload", palette()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Tab::show_inspector_window(InspectorTarget inspector_target)
|
void Tab::show_inspector_window(InspectorTarget inspector_target)
|
||||||
|
|
|
@ -68,7 +68,7 @@ private:
|
||||||
virtual void resizeEvent(QResizeEvent*) override;
|
virtual void resizeEvent(QResizeEvent*) override;
|
||||||
virtual bool event(QEvent*) override;
|
virtual bool event(QEvent*) override;
|
||||||
|
|
||||||
void rerender_toolbar_icons();
|
void recreate_toolbar_icons();
|
||||||
void update_hover_label();
|
void update_hover_label();
|
||||||
|
|
||||||
void open_link(URL const&);
|
void open_link(URL const&);
|
||||||
|
|
Loading…
Reference in a new issue