Newer
Older
minerva / Userland / Libraries / LibGfx / GlassWindowTheme.cpp
@minerva minerva on 13 Jul 7 KB Initial commit
/*
 * Copyright (c) 2023, MacDue <macdue@dueutil.tech>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/Array.h>
#include <LibGfx/CharacterBitmap.h>
#include <LibGfx/GlassWindowTheme.h>
#include <LibGfx/Gradients.h>
#include <LibGfx/Painter.h>
#include <LibGfx/Palette.h>
#include <LibGfx/StylePainter.h>
namespace Gfx {

// TODO: Somehow allow colors to be configured in the theme .ini file.
static Array const s_title_gradient {
    ColorStop { Color(25, 40, 55, 191), 0.35f },
    ColorStop { Color(65, 85, 100, 191), 0.40f },
    ColorStop { Color(65, 85, 100, 191), 0.42f },
    ColorStop { Color(25, 40, 55, 191), 0.50f },
    ColorStop { Color(25, 40, 55, 191), 0.55f },
    ColorStop { Color(70, 85, 100, 191), 0.60f },
    ColorStop { Color(70, 85, 100, 191), 0.75f },
    ColorStop { Color(25, 40, 55, 191), 0.90f }
};

static constexpr struct {
    Color base { 235, 235, 236 };
    Color border { 2, 3, 4, 219 };
} s_frame_colors;

static constexpr Gfx::CharacterBitmap s_window_border_radius_mask {
    "#####"
    "###  "
    "##   "
    "#    "
    "#    "sv,
    5, 5
};

static constexpr Gfx::CharacterBitmap s_window_border_radius_accent {
    "     "
    "   ##"
    "  #  "
    " #   "
    " #   "sv,
    5, 5
};

static constexpr Gfx::CharacterBitmap s_window_border_radius_accent2 {
    "     "
    "     "
    "   ##"
    "  #  "
    "  #  "sv,
    5, 5
};

IntRect GlassWindowTheme::titlebar_rect(WindowType window_type, WindowMode window_mode, IntRect const& window_rect, Palette const& palette) const
{
    auto window_titlebar_height = titlebar_height(window_type, window_mode, palette);
    // FIXME: Theme notifications.
    if (window_type == WindowType::Notification)
        return ClassicWindowTheme::titlebar_rect(window_type, window_mode, window_rect, palette);
    return { 0, 0, window_rect.width() + palette.window_border_thickness() * 2, window_titlebar_height };
}

void GlassWindowTheme::paint_normal_frame(Painter& painter, WindowState window_state, WindowMode window_mode, IntRect const& window_rect, StringView window_title, Bitmap const& icon, Palette const& palette, IntRect const& leftmost_button_rect, int menu_row_count, bool window_modified) const
{
    // FIXME: Handle these cases.
    (void)window_state;
    (void)icon;
    (void)window_modified;

    auto frame_rect = frame_rect_for_window(WindowType::Normal, window_mode, window_rect, palette, menu_row_count);
    auto relative_window_location = window_rect.location() - frame_rect.location();
    frame_rect.set_location({ 0, 0 });
    frame_rect.shrink(0, 1, 1, 1);

    auto frame_rects = frame_rect.shatter({ relative_window_location, window_rect.size() });
    for (auto const& clip_rect : frame_rects) {
        // Paint Aero-style gradient.
        PainterStateSaver saver { painter };
        painter.add_clip_rect(clip_rect);
        painter.fill_rect(frame_rect, s_frame_colors.base.with_alpha(150));
        painter.fill_rect_with_linear_gradient(frame_rect, s_title_gradient, 45, 0.9f);
    }

    // Draw frame title.
    auto titlebar_rect = this->titlebar_rect(WindowType::Normal, window_mode, window_rect, palette);
    titlebar_rect.set_height(titlebar_rect.height() + palette.window_border_thickness() + 1);
    auto& title_font = FontDatabase::window_title_font();
    auto clipped_title_rect = titlebar_rect.translated(7, 0);
    clipped_title_rect.set_width(leftmost_button_rect.left() - clipped_title_rect.x());
    if (!clipped_title_rect.is_empty()) {
        auto title_alignment = palette.title_alignment();
        painter.draw_text(clipped_title_rect.translated(1, 2), window_title, title_font, title_alignment, Color(15, 16, 137), Gfx::TextElision::Right);
        // FIXME: The translated(0, 1) wouldn't be necessary if we could center text based on its baseline.
        painter.draw_text(clipped_title_rect.translated(0, 1), window_title, title_font, title_alignment, Color::White, Gfx::TextElision::Right);
    }

    // Draw frame border.
    auto inner = frame_rect.shrunken(palette.window_title_height() + palette.window_border_thickness(), palette.window_border_thickness(), palette.window_border_thickness(), palette.window_border_thickness());
    painter.draw_rect_with_thickness(frame_rect, s_frame_colors.border, 1);
    painter.draw_rect_with_thickness(frame_rect.shrunken(1, 1, 1, 1), s_frame_colors.base.with_alpha(170), 1);
    painter.draw_rect_with_thickness(inner.inflated(1, 1, 1, 1), s_frame_colors.base.with_alpha(110), 1);
    painter.draw_rect_with_thickness(inner, s_frame_colors.border.with_alpha(110), 1);

    // Paint/clip border radii.
    Gfx::IntRect point(0, 0, 1, 1);
    auto border_radius = s_window_border_radius_mask.width();
    auto left_border_radius_pos = frame_rect.location();
    auto right_border_radius_pos = frame_rect.location().translated(frame_rect.width() - border_radius, 0);
    for (unsigned y = 0; y < s_window_border_radius_mask.height(); y++) {
        for (unsigned x = 0; x < s_window_border_radius_mask.width(); x++) {
            if (s_window_border_radius_mask.bit_at(x, y)) {
                painter.clear_rect(point.translated(left_border_radius_pos).translated(x, y), Color::Transparent);
                painter.clear_rect(point.translated(right_border_radius_pos).translated(border_radius - x, y), Color::Transparent);
            }
            if (s_window_border_radius_accent.bit_at(x, y)) {
                painter.fill_rect(point.translated(left_border_radius_pos).translated(x, y), s_frame_colors.border);
                painter.fill_rect(point.translated(right_border_radius_pos).translated(border_radius - x, y), s_frame_colors.border);
            }
            if (s_window_border_radius_accent2.bit_at(x, y)) {
                painter.fill_rect(point.translated(left_border_radius_pos).translated(x, y), s_frame_colors.base.with_alpha(170));
                painter.fill_rect(point.translated(right_border_radius_pos).translated(border_radius - x, y), s_frame_colors.base.with_alpha(170));
            }
        }
    }
}

void GlassWindowTheme::paint_notification_frame(Painter& painter, WindowMode window_mode, IntRect const& window_rect, Palette const& palette, IntRect const& close_button_rect) const
{
    (void)close_button_rect;

    auto frame_rect = frame_rect_for_window(WindowType::Notification, window_mode, window_rect, palette, 0);
    frame_rect.set_location({ 0, 0 });
    frame_rect.shrink(0, 1, 1, 0);

    painter.fill_rect(frame_rect, s_frame_colors.base.with_alpha(150));
    painter.fill_rect_with_linear_gradient(frame_rect, s_title_gradient, 45, 0.9f);

    painter.draw_rect_with_thickness(frame_rect, s_frame_colors.border, 1);
    painter.draw_rect_with_thickness(frame_rect.shrunken(1, 1, 1, 1), s_frame_colors.base.with_alpha(170), 1);
}

Vector<IntRect> GlassWindowTheme::layout_buttons(WindowType window_type, WindowMode window_mode, IntRect const& window_rect, Palette const& palette, size_t buttons, bool is_maximized) const
{
    auto button_rects = ClassicWindowTheme::layout_buttons(window_type, window_mode, window_rect, palette, buttons, is_maximized);
    auto button_offset = [&](IntRect button_rect) -> IntPoint {
        if (window_type == WindowType::Notification)
            return { 1, -1 };
        return { -max(palette.window_border_thickness(), s_window_border_radius_mask.width()) - 1,
            -button_rect.y() + (is_maximized ? palette.window_border_thickness() : 1) + 3 };
    };

    for (auto& button_rect : button_rects)
        button_rect.translate_by(button_offset(button_rect));
    return button_rects;
}

void GlassWindowTheme::paint_taskbar(Painter& painter, IntRect const& taskbar_rect, Palette const&) const
{
    painter.clear_rect(taskbar_rect, Color::Transparent);
    painter.fill_rect_with_linear_gradient(taskbar_rect, s_title_gradient, 45, 0.9f);
    painter.draw_line(taskbar_rect.top_left(), taskbar_rect.top_right(), s_frame_colors.border, 1);
    painter.draw_line(taskbar_rect.top_left().translated(0, 1), taskbar_rect.top_right().translated(0, 1), s_frame_colors.base.with_alpha(170), 1);
}

}