diff options
author | thankyouverycool <66646555+thankyouverycool@users.noreply.github.com> | 2021-03-29 20:43:40 -0400 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-03-30 10:34:34 +0200 |
commit | 4465b37897a24e910c2c76e4da090c1c424ee8a2 (patch) | |
tree | 5392b57151b495c759c84c96cc7aae10f296f19a /Userland/Libraries | |
parent | effb42675713c3800c4643fd88f9ba5c2f7585b7 (diff) | |
download | serenity-4465b37897a24e910c2c76e4da090c1c424ee8a2.zip |
LibGUI+Calendar: Add new month and year views to Calendar
And overhaul resize and paint events to fix layout edge cases in
which Calendar wasn't filling its parent widget completely. Ensures
month views always display prior month days for click navigation.
Converts Calendar app layout to GML.
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibGUI/Calendar.cpp | 911 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/Calendar.h | 163 |
2 files changed, 750 insertions, 324 deletions
diff --git a/Userland/Libraries/LibGUI/Calendar.cpp b/Userland/Libraries/LibGUI/Calendar.cpp index 6ca093e397..1201848610 100644 --- a/Userland/Libraries/LibGUI/Calendar.cpp +++ b/Userland/Libraries/LibGUI/Calendar.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2019-2020, Ryan Grieb <ryan.m.grieb@gmail.com> - * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2020-2021, the SerenityOS developers * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,15 +26,14 @@ */ #include <LibCore/DateTime.h> -#include <LibGUI/BoxLayout.h> -#include <LibGUI/Button.h> #include <LibGUI/Calendar.h> #include <LibGUI/Painter.h> #include <LibGUI/Window.h> -#include <LibGfx/Font.h> #include <LibGfx/FontDatabase.h> #include <LibGfx/Palette.h> +REGISTER_WIDGET(GUI, Calendar); + namespace GUI { static const char* long_day_names[] = { @@ -56,316 +55,734 @@ static const char* short_month_names[] = { "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; -Calendar::Calendar(Core::DateTime date_time) +static const auto extra_large_font = Gfx::BitmapFont::load_from_file("/res/fonts/LizaRegular36.font"); +static const auto large_font = Gfx::BitmapFont::load_from_file("/res/fonts/LizaRegular24.font"); +static const auto medium_font = Gfx::BitmapFont::load_from_file("/res/fonts/PebbletonRegular14.font"); +static const auto small_font = Gfx::BitmapFont::load_from_file("/res/fonts/KaticaRegular10.font"); + +Calendar::Calendar(Core::DateTime date_time, Mode mode) : m_selected_date(date_time) - , m_selected_year(date_time.year()) - , m_selected_month(date_time.month()) + , m_mode(mode) { set_fill_with_background_color(true); - set_layout<GUI::VerticalBoxLayout>(); - layout()->set_spacing(0); - - m_day_name_container = add<GUI::Widget>(); - m_day_name_container->set_layout<GUI::HorizontalBoxLayout>(); - m_day_name_container->set_fixed_height(16); - m_day_name_container->layout()->set_spacing(0); - m_day_name_container->set_fill_with_background_color(true); - m_day_name_container->set_background_role(Gfx::ColorRole::HoverHighlight); - for (auto& day : m_day_names) { - day = m_day_name_container->add<GUI::Label>(); - day->set_font(Gfx::FontDatabase::default_bold_font()); - } + set_background_role(Gfx::ColorRole::ThreedShadow2); - m_calendar_tile_container = add<GUI::Widget>(); - m_calendar_tile_container->set_layout<GUI::VerticalBoxLayout>(); - m_calendar_tile_container->layout()->set_spacing(0); - - for (auto& row : m_week_rows) { - row = m_calendar_tile_container->add<GUI::Widget>(); - row->set_layout<GUI::HorizontalBoxLayout>(); - row->layout()->set_spacing(0); + for (int i = 0; i < 7; i++) { + Day day; + m_days.append(move(day)); } - - int i = 0; - for (int j = 0; j < 6; j++) - for (int k = 0; k < 7; k++) { - m_calendar_tiles[i] = m_week_rows[j]->add<CalendarTile>(i, date_time); - m_calendar_tiles[i]->on_click = [this](int index) { - m_previous_selected_date = m_selected_date; - m_selected_date = m_calendar_tiles[index]->get_date_time(); - update_tiles(m_selected_date.year(), m_selected_date.month()); - if (on_calendar_tile_click) - on_calendar_tile_click(); - }; - m_calendar_tiles[i]->on_doubleclick = [this](int index) { - if (m_calendar_tiles[index]->get_date_time().day() != m_previous_selected_date.day()) - return; - if (on_calendar_tile_doubleclick) - on_calendar_tile_doubleclick(); - }; - i++; + for (int i = 0; i < 12; i++) { + MonthTile month; + m_months.append(move(month)); + for (int j = 0; j < 42; j++) { + Tile tile; + m_tiles[i].append(move(tile)); } - - m_month_tile_container = add<GUI::Widget>(); - m_month_tile_container->set_visible(false); - m_month_tile_container->set_layout<GUI::VerticalBoxLayout>(); - m_month_tile_container->set_fill_with_background_color(true); - m_month_tile_container->set_background_role(Gfx::ColorRole::HoverHighlight); - m_month_tile_container->layout()->set_spacing(0); - - for (auto& row : m_month_rows) { - row = m_month_tile_container->add<GUI::Widget>(); - row->set_layout<GUI::HorizontalBoxLayout>(); - row->layout()->set_spacing(0); } - i = 0; - for (int j = 0; j < 3; j++) - for (int k = 0; k < 4; k++) { - m_month_tiles[i] = m_month_rows[j]->add<MonthTile>(i, date_time); - m_month_tiles[i]->set_button_style(Gfx::ButtonStyle::CoolBar); - m_month_tiles[i]->on_indexed_click = [this](int index) { - toggle_mode(); - update_tiles(m_month_tiles[index]->get_date_time().year(), m_month_tiles[index]->get_date_time().month()); - if (on_month_tile_click) - on_month_tile_click(); - }; - i++; - } - - update_tiles(selected_year(), selected_month()); + update_tiles(m_selected_date.year(), m_selected_date.month()); } Calendar::~Calendar() { } +void Calendar::set_grid(bool show) +{ + m_grid = show; + set_background_role(has_grid() + ? Gfx::ColorRole::ThreedShadow2 + : Gfx::ColorRole::Base); +} + void Calendar::toggle_mode() { m_mode == Month ? m_mode = Year : m_mode = Month; - - if (mode() == Month) { - m_day_name_container->set_visible(true); - m_calendar_tile_container->set_visible(true); - m_month_tile_container->set_visible(false); - } else { - m_day_name_container->set_visible(false); - m_calendar_tile_container->set_visible(false); - m_month_tile_container->set_visible(true); - } - + set_show_days_of_the_week(!m_show_days); + set_show_year(!m_show_year); + set_show_month_and_year(!m_show_month_year); + update_tiles(this->view_year(), this->view_month()); this->resize(this->height(), this->width()); - update_tiles(selected_year(), selected_month()); + invalidate_layout(); } -void Calendar::set_grid(bool grid) +void Calendar::resize_event(GUI::ResizeEvent& event) { - if (m_grid == grid) - return; + m_event_size.set_width(event.size().width()); + m_event_size.set_height(event.size().height()); - m_grid = grid; + if (mode() == Month) { + if (m_event_size.width() < 160 || m_event_size.height() < 130) + set_show_month_and_year(false); + else if (m_event_size.width() >= 160 && m_event_size.height() >= 130) + set_show_month_and_year(true); + + set_show_year(false); + + const int GRID_LINES = 6; + int tile_width = (m_event_size.width() - GRID_LINES) / 7; + int width_remainder = (m_event_size.width() - GRID_LINES) % 7; + int y_offset = is_showing_days_of_the_week() ? 16 : 0; + y_offset += is_showing_month_and_year() ? 24 : 0; + int tile_height = (m_event_size.height() - y_offset - GRID_LINES) / 6; + int height_remainder = (m_event_size.height() - y_offset - GRID_LINES) % 6; + + set_unadjusted_tile_size(tile_width, tile_height); + tile_width < 30 || tile_height < 30 ? set_grid(false) : set_grid(true); + + for (int i = 0; i < 42; i++) { + m_tiles[0][i].width = tile_width; + m_tiles[0][i].height = tile_height; + } - for (int i = 0; i < 42; i++) { - m_calendar_tiles[i]->set_grid(grid); - m_calendar_tiles[i]->update(); - } -} + for (auto& day : m_days) + day.width = tile_width; -void Calendar::resize_event(GUI::ResizeEvent& event) -{ - if (m_day_name_container->is_visible()) { - for (int i = 0; i < 7; i++) { - if (event.size().width() < 120) - m_day_names[i]->set_text(micro_day_names[i]); - else if (event.size().width() < 200) - m_day_names[i]->set_text(mini_day_names[i]); - else if (event.size().width() < 480) - m_day_names[i]->set_text(short_day_names[i]); - else - m_day_names[i]->set_text(long_day_names[i]); + for (int i = 0; i < width_remainder; i++) { + m_days[i].width = (tile_width + 1); + for (int j = i; j < i + 36; j += 7) { + m_tiles[0][j].width = tile_width + 1; + } } - } - if (m_month_tile_container->is_visible()) { - for (int i = 0; i < 12; i++) { - if (event.size().width() < 250) - m_month_tiles[i]->set_text(short_month_names[i]); - else - m_month_tiles[i]->set_text(long_month_names[i]); + for (int j = 0; j < height_remainder * 7; j++) + m_tiles[0][j].height = tile_height + 1; + + if (is_showing_days_of_the_week()) { + for (int i = 0; i < 7; i++) { + if (m_event_size.width() < 138) + m_days[i].name = micro_day_names[i]; + else if (m_event_size.width() < 200) + m_days[i].name = mini_day_names[i]; + else if (m_event_size.width() < 480) + m_days[i].name = short_day_names[i]; + else + m_days[i].name = long_day_names[i]; + } + } + } else { + if (m_event_size.width() < 140 && m_event_size.height() < 120) + set_show_year(false); + else if (m_event_size.width() >= 140 && m_event_size.height() >= 120) + set_show_year(true); + + set_show_month_and_year(false); + + const int VERT_GRID_LINES = 27; + const int HORI_GRID_LINES = 15; + const int THREADING = 3; + const int MONTH_TITLE = 19; + int tile_width = (m_event_size.width() - VERT_GRID_LINES) / 28; + int width_remainder = (m_event_size.width() - VERT_GRID_LINES) % 28; + int y_offset = is_showing_year() ? 22 : 0; + y_offset += (MONTH_TITLE * 3) + (THREADING * 3); + int tile_height = (m_event_size.height() - y_offset - HORI_GRID_LINES) / 18; + int height_remainder = (m_event_size.height() - y_offset - HORI_GRID_LINES) % 18; + + set_grid(false); + set_unadjusted_tile_size(tile_width, tile_height); + if (unadjusted_tile_size().width() < 17 || unadjusted_tile_size().height() < 13) { + m_show_month_tiles = true; + set_background_role(Gfx::ColorRole::Window); + } else { + m_show_month_tiles = false; + set_background_role(Gfx::ColorRole::Base); } - } - (event.size().width() < 200) ? set_grid(false) : set_grid(true); -} + if (m_show_month_tiles) { + int month_tile_width = m_event_size.width() / 4; + int width_remainder = m_event_size.width() % 4; + int y_offset = is_showing_year() ? 23 : 0; + int month_tile_height = (m_event_size.height() - y_offset) / 3; + int height_remainder = (m_event_size.height() - y_offset) % 3; + + for (int i = 0; i < 12; i++) { + m_months[i].width = month_tile_width; + m_months[i].height = month_tile_height; + if (m_event_size.width() < 250) + m_months[i].name = short_month_names[i]; + else + m_months[i].name = long_month_names[i]; + } -void Calendar::update_tiles(unsigned int target_year, unsigned int target_month) -{ - set_selected_calendar(target_year, target_month); - if (mode() == Month) { - unsigned int i = 0; - for (int y = 0; y < 6; y++) - for (int x = 0; x < 7; x++) { - auto date_time = Core::DateTime::create(target_year, target_month, 1); - unsigned int start_of_month = date_time.weekday(); - unsigned int year; - unsigned int month; - unsigned int day; - - if (start_of_month > i) { - month = (target_month - 1 == 0) ? 12 : target_month - 1; - year = (month == 12) ? target_year - 1 : target_year; - date_time.set_time(year, month, 1); - day = (date_time.days_in_month() - (start_of_month) + i) + 1; - date_time.set_time(year, month, day); - } else if ((i - start_of_month) + 1 > date_time.days_in_month()) { - month = (target_month + 1) > 12 ? 1 : target_month + 1; - year = (month == 1) ? target_year + 1 : target_year; - day = ((i - start_of_month) + 1) - date_time.days_in_month(); - date_time.set_time(year, month, day); - } else { - month = target_month; - year = target_year; - day = (i - start_of_month) + 1; - date_time.set_time(year, month, day); + if (width_remainder) { + for (int i = 0; i < width_remainder; i++) { + for (int j = i; j < 12; j += 4) { + m_months[j].width = month_tile_width + 1; + } } + } - m_calendar_tiles[i]->update_values(i, date_time); - m_calendar_tiles[i]->set_selected(date_time.year() == m_selected_date.year() && date_time.month() == m_selected_date.month() && date_time.day() == m_selected_date.day()); - m_calendar_tiles[i]->set_outside_selection(date_time.month() != selected_month() || date_time.year() != selected_year()); - m_calendar_tiles[i]->update(); - i++; + if (height_remainder) { + for (int i = 0; i < height_remainder * 4; i++) { + m_months[i].height = month_tile_height + 1; + } } - } else { + return; + } + for (int i = 0; i < 12; i++) { - auto date_time = Core::DateTime::create(target_year, i + 1, 1); - m_month_tiles[i]->update_values(date_time); + int remainder = 0; + if (i == 0 || i == 4 || i == 8) + remainder = min(width_remainder, 7); + if (i == 1 || i == 5 || i == 9) + width_remainder > 7 ? remainder = min(width_remainder - 7, 7) : remainder = 0; + if (i == 2 || i == 6 || i == 10) + width_remainder > 14 ? remainder = min(width_remainder - 14, 7) : remainder = 0; + if (i == 3 || i == 7 || i == 11) + width_remainder > 21 ? remainder = width_remainder - 21 : remainder = 0; + m_month_size[i].set_width(remainder + 6 + tile_width * 7); + + if (i >= 0 && i <= 3) + remainder = min(height_remainder, 6); + if (i >= 4 && i <= 7) + height_remainder > 6 ? remainder = min(height_remainder - 6, 6) : remainder = 0; + if (i >= 8 && i <= 12) + height_remainder > 12 ? remainder = height_remainder - 12 : remainder = 0; + m_month_size[i].set_height(remainder + 5 + tile_height * 6); + + for (int j = 0; j < 42; j++) { + m_tiles[i][j].width = tile_width; + m_tiles[i][j].height = tile_height; + } + } + + if (width_remainder) { + for (int i = 0; i < 12; i += 4) { + for (int j = 0; j < min(width_remainder, 7); j++) { + for (int k = j; k < j + 36; k += 7) { + m_tiles[i][k].width = tile_width + 1; + } + } + } + } + if (width_remainder > 7) { + for (int i = 1; i < 12; i += 4) { + for (int j = 0; j < min(width_remainder - 7, 7); j++) { + for (int k = j; k < j + 36; k += 7) { + m_tiles[i][k].width = tile_width + 1; + } + } + } + } + if (width_remainder > 14) { + for (int i = 2; i < 12; i += 4) { + for (int j = 0; j < min(width_remainder - 14, 7); j++) { + for (int k = j; k < j + 36; k += 7) { + m_tiles[i][k].width = tile_width + 1; + } + } + } + } + if (width_remainder > 21) { + for (int i = 3; i < 12; i += 4) { + for (int j = 0; j < width_remainder - 21; j++) { + for (int k = j; k < j + 36; k += 7) { + m_tiles[i][k].width = tile_width + 1; + } + } + } + } + if (height_remainder) { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < min(height_remainder, 6) * 7; j++) { + m_tiles[i][j].height = tile_height + 1; + } + } + } + if (height_remainder > 6) { + for (int i = 4; i < 8; i++) { + for (int j = 0; j < min(height_remainder - 6, 6) * 7; j++) { + m_tiles[i][j].height = tile_height + 1; + } + } + } + if (height_remainder > 12) { + for (int i = 8; i < 12; i++) { + for (int j = 0; j < (height_remainder - 12) * 7; j++) { + m_tiles[i][j].height = tile_height + 1; + } + } } } } -const String Calendar::selected_calendar_text(bool long_names) +void Calendar::update_tiles(unsigned view_year, unsigned view_month) { - if (mode() == Month) - return String::formatted("{} {}", long_names ? long_month_names[m_selected_month - 1] : short_month_names[m_selected_month - 1], m_selected_year); - else - return String::number(m_selected_year); + set_view_date(view_year, view_month); + + unsigned months; + mode() == Month ? months = 1 : months = 12; + for (unsigned i = 0; i < months; i++) { + if (mode() == Year) + view_month = i + 1; + for (unsigned j = 0; j < 42; j++) { + auto date_time = Core::DateTime::create(view_year, view_month, 1); + unsigned start_of_month = date_time.weekday(); + unsigned year; + unsigned month; + unsigned day; + + if (start_of_month == 0 && mode() != Year) { + month = (view_month - 1 == 0) ? 12 : view_month - 1; + year = (month == 12) ? view_year - 1 : view_year; + date_time.set_time(year, month, 1); + day = (date_time.days_in_month() - 6 + j); + } else if (start_of_month > j) { + month = (view_month - 1 == 0) ? 12 : view_month - 1; + year = (month == 12) ? view_year - 1 : view_year; + date_time.set_time(year, month, 1); + day = (date_time.days_in_month() - (start_of_month) + j) + 1; + } else if ((j - start_of_month) + 1 > date_time.days_in_month()) { + month = (view_month + 1) > 12 ? 1 : view_month + 1; + year = (month == 1) ? view_year + 1 : view_year; + day = ((j - start_of_month) + 1) - date_time.days_in_month(); + } else { + month = view_month; + year = view_year; + day = (j - start_of_month) + 1; + } + date_time.set_time(year, month, day); + + m_tiles[i][j].date_time = date_time; + m_tiles[i][j].is_outside_selected_month = (date_time.month() != view_month + || date_time.year() != view_year); + m_tiles[i][j].is_selected = (date_time.year() == m_selected_date.year() + && date_time.month() == m_selected_date.month() + && date_time.day() == m_selected_date.day() + && (mode() == Year ? !m_tiles[i][j].is_outside_selected_month : true)); + m_tiles[i][j].is_today = (date_time.day() == Core::DateTime::now().day() + && date_time.month() == Core::DateTime::now().month() + && date_time.year() == Core::DateTime::now().year()); + } + } + update(); } -void Calendar::set_selected_calendar(unsigned int year, unsigned int month) +String Calendar::formatted_date(Format format) { - m_selected_year = year; - m_selected_month = month; + switch (format) { + case ShortMonthYear: + return String::formatted("{} {}", short_month_names[view_month() - 1], view_year()); + case LongMonthYear: + return String::formatted("{} {}", long_month_names[view_month() - 1], view_year()); + case MonthOnly: + return String::formatted("{}", long_month_names[view_month() - 1]); + case YearOnly: + return String::number(view_year()); + default: + VERIFY_NOT_REACHED(); + } } -Calendar::MonthTile::MonthTile(int index, Core::DateTime date_time) - : m_index(index) - , m_date_time(date_time) +void Calendar::paint_event(GUI::PaintEvent& event) { -} + GUI::Painter painter(*this); + painter.add_clip_rect(event.rect()); + + int width = unadjusted_tile_size().width(); + int height = unadjusted_tile_size().height(); + int x_offset = 0; + int y_offset = 0; + + if (is_showing_year()) { + auto year_only_rect = Gfx::IntRect( + 0, + 0, + event.rect().width(), + 22); + y_offset += year_only_rect.height(); + painter.fill_rect(year_only_rect, palette().hover_highlight()); + painter.draw_text(year_only_rect, formatted_date(YearOnly), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, (!m_show_month_tiles ? palette().threed_shadow1() : palette().threed_shadow2()), 1); + y_offset += 1; + if (!m_show_month_tiles) { + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, palette().threed_highlight(), 1); + y_offset += 1; + } + } else if (is_showing_month_and_year()) { + auto month_year_rect = Gfx::IntRect( + 0, + 0, + event.rect().width(), + 22); + painter.fill_rect(month_year_rect, palette().hover_highlight()); + auto month_rect = Gfx::IntRect( + 0, + 0, + event.rect().width() / 2, + 22); + auto year_rect = Gfx::IntRect( + event.rect().width() / 2, + 0, + event.rect().width() / 2 + ((event.rect().width() % 2) ? 1 : 0), + 22); + painter.fill_rect(month_rect, palette().hover_highlight()); + painter.fill_rect(year_rect, palette().hover_highlight()); + painter.draw_text(month_rect, formatted_date(MonthOnly), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + painter.draw_text(year_rect, formatted_date(YearOnly), medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + y_offset += year_rect.height(); + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, palette().threed_shadow1(), 1); + y_offset += 1; + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, palette().threed_highlight(), 1); + y_offset += 1; + } -Calendar::MonthTile::~MonthTile() -{ -} + if (mode() == Year && m_show_month_tiles) { + int i = 0; + for (int j = 0; j < 3; j++) { + x_offset = 0; + for (int k = 0; k < 4; k++) { + if (k > 0) + x_offset += m_months[i - 1].width; + auto month_tile_rect = Gfx::IntRect( + x_offset, + y_offset, + m_months[i].width, + m_months[i].height); + m_months[i].rect = month_tile_rect; + Gfx::StylePainter::paint_button( + painter, month_tile_rect, palette(), + Gfx::ButtonStyle::Normal, + m_months[i].is_being_pressed, + m_months[i].is_hovered, + false, true, false); + set_font(small_font); + painter.draw_text(month_tile_rect, m_months[i].name, font(), Gfx::TextAlignment::Center, palette().base_text()); + i++; + } + y_offset += m_months[i - 1].height; + } + return; + } -void Calendar::MonthTile::mouseup_event(GUI::MouseEvent& event) -{ - if (on_indexed_click) - on_indexed_click(m_index); + if (is_showing_days_of_the_week()) { + auto days_of_the_week_rect = Gfx::IntRect( + 0, + y_offset, + event.rect().width(), + 16); + painter.fill_rect(days_of_the_week_rect, palette().hover_highlight()); + for (int i = 0; i < 7; i++) { + if (i > 0) + x_offset += m_days[i - 1].width + 1; + Gfx::IntRect day_rect = Gfx::IntRect( + event.rect().x() + x_offset, + y_offset, + m_days[i].width, + 16); + painter.draw_text(day_rect, m_days[i].name, small_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + } + y_offset += days_of_the_week_rect.height(); + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, palette().threed_shadow2(), 1); + y_offset += 1; + } - GUI::Button::mouseup_event(event); -} + if (mode() == Month) { + int i = 0; + for (int j = 0; j < 6; j++) { + x_offset = 0; + if (j > 0) + y_offset += m_tiles[0][(j - 1) * 7].height + 1; + for (int k = 0; k < 7; k++) { + if (k > 0) + x_offset += m_tiles[0][k - 1].width + 1; + auto tile_rect = Gfx::IntRect( + x_offset, + y_offset, + m_tiles[0][i].width, + m_tiles[0][i].height); + m_tiles[0][i].rect = tile_rect; + if (m_tiles[0][i].is_hovered || m_tiles[0][i].is_selected) + painter.fill_rect(tile_rect, palette().hover_highlight()); + else + painter.fill_rect(tile_rect, palette().base()); + + auto text_alignment = Gfx::TextAlignment::TopRight; + auto text_rect = Gfx::IntRect( + x_offset, + y_offset + 4, + m_tiles[0][i].width, + font().glyph_height() + 4); + + if (width > 150 && height > 150) { + set_font(extra_large_font); + } else if (width > 100 && height > 100) { + set_font(large_font); + } else if (width > 50 && height > 50) { + set_font(medium_font); + text_rect.set_width(m_tiles[0][i].width - 4); + } else if (width >= 30 && height >= 30) { + set_font(small_font); + text_rect.set_width(m_tiles[0][i].width - 4); + } else { + set_font(small_font); + text_alignment = Gfx::TextAlignment::Center; + text_rect = Gfx::IntRect(tile_rect); + } -Calendar::CalendarTile::CalendarTile(int index, Core::DateTime date_time) -{ - set_frame_thickness(0); - update_values(index, date_time); -} + auto display_date = String::number(m_tiles[0][i].date_time.day()); + if (m_tiles[0][i].is_today) { + if (m_tiles[0][i].is_selected && width < 30) + painter.draw_rect(tile_rect, palette().base_text()); + painter.draw_text(text_rect, display_date, font().bold_variant(), text_alignment, palette().base_text()); + } else if (m_tiles[0][i].is_outside_selected_month) { + painter.draw_text(text_rect, display_date, font(), text_alignment, Color::LightGray); + } else { + if ((width < 30 || height < 30) && m_tiles[0][i].is_selected) + painter.draw_rect(tile_rect, palette().base_text()); + painter.draw_text(text_rect, display_date, font(), text_alignment, palette().base_text()); + } + i++; + } + } + } else { + for (int i = 0; i < 4; i++) { + static int x_month_offset; + x_month_offset += (i > 0 ? m_month_size[i - 1].width() + 1 : 0); + auto month_rect = Gfx::IntRect( + event.rect().x() + x_month_offset, + y_offset, + m_month_size[i].width(), + 19); + painter.fill_rect(month_rect, palette().hover_highlight()); + painter.draw_text(month_rect, long_month_names[i], medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + if (i > 0 && i < 4) { + painter.draw_line({ x_month_offset - 1, y_offset - 1 }, { x_month_offset - 1, y_offset + 18 }, palette().threed_shadow2(), 1); + painter.draw_line({ x_month_offset, y_offset - 1 }, { x_month_offset, y_offset + 18 }, palette().threed_highlight(), 1); + } + if (i == 3) + x_month_offset = 0; + } + y_offset += 19; + painter.draw_line({ 0, y_offset }, { event.rect().width(), y_offset }, palette().threed_shadow2(), 1); + y_offset += 1; + + int x_translation = 0; + int y_translation = y_offset; + for (int l = 0; l < 12; l++) { + if ((l > 0 && l < 4) || (l > 4 && l < 8) || (l > 8)) { + x_translation += m_month_size[l - 1].width() + 1; + } else if (l % 4 == 0) { + x_translation = 0; + } + if (l < 4 || (l > 4 && l < 8) || l > 8) { + y_offset = y_translation; + } else if (l == 4 || l == 8) { + y_translation += m_month_size[l - 1].height(); + painter.draw_line({ 0, y_translation }, { event.rect().width(), y_translation }, palette().threed_shadow1(), 1); + y_translation += 1; + painter.draw_line({ 0, y_translation }, { event.rect().width(), y_translation }, palette().threed_highlight(), 1); + y_translation += 1; + y_offset = y_translation; + for (int i = l; i < (l == 4 ? 8 : 12); i++) { + static int x_month_offset; + x_month_offset += (i > (l == 4 ? 4 : 8) ? m_month_size[i - 1].width() + 1 : 0); + auto month_rect = Gfx::IntRect( + event.rect().x() + x_month_offset, + y_offset, + m_month_size[i].width(), + 19); + painter.fill_rect(month_rect, palette().hover_highlight()); + painter.draw_text(month_rect, long_month_names[i], medium_font->bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + if (i > (l == 4 ? 4 : 8) && i < (l == 4 ? 8 : 12)) { + painter.draw_line({ x_month_offset - 1, y_offset - 1 }, { x_month_offset - 1, y_offset + 18 }, palette().threed_shadow2(), 1); + painter.draw_line({ x_month_offset, y_offset - 1 }, { x_month_offset, y_offset + 18 }, palette().threed_highlight(), 1); + } + if (i == 7 || i == 11) + x_month_offset = 0; + } + y_translation += 19; + painter.draw_line({ 0, y_translation }, { event.rect().width(), y_translation }, palette().threed_shadow2(), 1); + y_translation += 1; + y_offset = y_translation; + } -void Calendar::CalendarTile::update_values(int index, Core::DateTime date_time) -{ - m_index = index; - m_date_time = date_time; - m_display_date = (m_date_time.day() == 1) ? String::formatted("{} {}", short_month_names[m_date_time.month() - 1], m_date_time.day()) : String::number(m_date_time.day()); + int i = 0; + for (int j = 0; j < 6; j++) { + x_offset = 0; + if (j > 0) + y_offset += m_tiles[l][(j - 1) * 7].height + (j < 6 ? 1 : 0); + if (j == 0 && l != 3 && l != 7 && l != 11) { + painter.draw_line( + { m_month_size[l].width() + x_translation, y_offset }, + { m_month_size[l].width() + x_translation, y_offset + m_month_size[l].height() }, + palette().threed_shadow2(), + 1); + } + for (int k = 0; k < 7; k++) { + if (k > 0) + x_offset += m_tiles[l][k - 1].width + 1; + auto tile_rect = Gfx::IntRect( + x_offset + x_translation, + y_offset, + m_tiles[l][i].width, + m_tiles[l][i].height); + m_tiles[l][i].rect = tile_rect; + + if (m_tiles[l][i].is_hovered || m_tiles[l][i].is_selected) + painter.fill_rect(tile_rect, palette().hover_highlight()); + else + painter.fill_rect(tile_rect, palette().base()); + + if (width > 50 && height > 50) { + set_font(medium_font); + } else { + set_font(small_font); + } + + auto display_date = String::number(m_tiles[l][i].date_time.day()); + if (m_tiles[l][i].is_today && !m_tiles[l][i].is_outside_selected_month) { + if (m_tiles[l][i].is_selected) + painter.draw_rect(tile_rect, palette().base_text()); + painter.draw_text(tile_rect, display_date, font().bold_variant(), Gfx::TextAlignment::Center, palette().base_text()); + } else if (!m_tiles[l][i].is_outside_selected_month) { + if (m_tiles[l][i].is_selected) + painter.draw_rect(tile_rect, palette().base_text()); + painter.draw_text(tile_rect, display_date, font(), Gfx::TextAlignment::Center, palette().base_text()); + } + i++; + } + } + } + } } -Calendar::CalendarTile::~CalendarTile() +void Calendar::leave_event(Core::Event&) { + int months; + mode() == Month ? months = 1 : months = 12; + for (int i = 0; i < months; i++) { + if (mode() == Year && m_show_month_tiles) { + m_months[i].is_hovered = false; + continue; + } else { + for (int j = 0; j < 42; j++) { + m_tiles[i][j].is_hovered = false; + } + } + } + update(); } -void Calendar::CalendarTile::doubleclick_event(GUI::MouseEvent&) +void Calendar::mousemove_event(GUI::MouseEvent& event) { - if (on_doubleclick) - on_doubleclick(m_index); -} + static int last_index_i; + static int last_index_j; + + if (mode() == Year && m_show_month_tiles) { + if (m_months[last_index_i].rect.contains(event.position()) && (m_months[last_index_i].is_hovered || m_months[last_index_i].is_being_pressed)) { + return; + } else { + m_months[last_index_i].is_hovered = false; + m_months[last_index_i].is_being_pressed = false; + update(m_months[last_index_i].rect); + } + } else { + if (m_tiles[last_index_i][last_index_j].rect.contains(event.position()) && m_tiles[last_index_i][last_index_j].is_hovered) { + return; + } else { + m_tiles[last_index_i][last_index_j].is_hovered = false; + update(m_tiles[last_index_i][last_index_j].rect); + } + } -void Calendar::CalendarTile::mousedown_event(GUI::MouseEvent&) -{ - if (on_click) - on_click(m_index); -} -void Calendar::CalendarTile::enter_event(Core::Event&) -{ - m_hovered = true; - update(); + int months; + mode() == Month ? months = 1 : months = 12; + for (int i = 0; i < months; i++) { + if (mode() == Year && m_show_month_tiles) { + if (m_months[i].rect.contains(event.position())) { + if (m_currently_pressed_index == -1 || m_currently_pressed_index == i) + m_months[i].is_hovered = true; + if (m_currently_pressed_index == i) + m_months[i].is_being_pressed = true; + update(m_months[last_index_i].rect); + if (m_months[i].is_being_pressed == true) + m_currently_pressed_index = i; + last_index_i = i; + update(m_months[i].rect); + break; + } + } else { + for (int j = 0; j < 42; j++) { + if (mode() == Year && m_tiles[i][j].is_outside_selected_month) + continue; + if (m_tiles[i][j].rect.contains(event.position())) { + m_tiles[i][j].is_hovered = true; + update(m_tiles[last_index_i][last_index_j].rect); + last_index_i = i; + last_index_j = j; + update(m_tiles[i][j].rect); + break; + } + } + } + } } -void Calendar::CalendarTile::leave_event(Core::Event&) +void Calendar::mouseup_event(GUI::MouseEvent& event) { - m_hovered = false; + int months; + mode() == Month ? months = 1 : months = 12; + for (int i = 0; i < months; i++) { + if (mode() == Year && m_show_month_tiles) { + if (m_months[i].rect.contains(event.position()) && m_months[i].is_being_pressed) { + set_view_date(view_year(), (unsigned)i + 1); + toggle_mode(); + if (on_month_click) + on_month_click(); + } + } else { + for (int j = 0; j < 42; j++) { + if (mode() == Year && m_tiles[i][j].is_outside_selected_month) + continue; + if (m_tiles[i][j].rect.contains(event.position())) { + m_previous_selected_date = m_selected_date; + m_selected_date = m_tiles[i][j].date_time; + update_tiles(m_selected_date.year(), m_selected_date.month()); + if (on_tile_click) + on_tile_click(); + } + } + } + if (months == 12) { + m_months[i].is_being_pressed = false; + m_months[i].is_hovered = false; + } + } + m_currently_pressed_index = -1; update(); } -bool Calendar::CalendarTile::is_today() const +void Calendar::mousedown_event(GUI::MouseEvent& event) { - auto current_date_time = Core::DateTime::now(); - return m_date_time.day() == current_date_time.day() && m_date_time.month() == current_date_time.month() && m_date_time.year() == current_date_time.year(); + if (mode() == Year && m_show_month_tiles) { + for (int i = 0; i < 12; i++) { + if (m_months[i].rect.contains(event.position())) { + m_months[i].is_being_pressed = true; + m_currently_pressed_index = i; + update(m_months[i].rect); + break; + } + } + } } -void Calendar::CalendarTile::paint_event(GUI::PaintEvent& event) +void Calendar::doubleclick_event(GUI::MouseEvent& event) { - GUI::Frame::paint_event(event); - - GUI::Painter painter(*this); - painter.add_clip_rect(frame_inner_rect()); - - if (is_hovered() || is_selected()) - painter.fill_rect(frame_inner_rect(), palette().hover_highlight()); - else - painter.fill_rect(frame_inner_rect(), palette().base()); - - if (m_index < 7) - painter.draw_line(frame_inner_rect().top_left(), frame_inner_rect().top_right(), Color::NamedColor::Black); - if (!((m_index + 1) % 7 == 0) && has_grid()) - painter.draw_line(frame_inner_rect().top_right(), frame_inner_rect().bottom_right(), Color::NamedColor::Black); - if (m_index < 35 && has_grid()) - painter.draw_line(frame_inner_rect().bottom_left(), frame_inner_rect().bottom_right(), Color::NamedColor::Black); - - Gfx::IntRect day_rect; - if (has_grid()) { - day_rect = Gfx::IntRect(frame_inner_rect().x(), frame_inner_rect().y(), frame_inner_rect().width(), font().glyph_height() + 4); - day_rect.set_y(frame_inner_rect().y() + 4); - } else { - day_rect = Gfx::IntRect(frame_inner_rect()); - } - - int highlight_rect_width = (font().glyph_width('0') * (m_display_date.length() + 1)) + 2; - auto display_date = (m_date_time.day() == 1 && frame_inner_rect().width() > highlight_rect_width) ? m_display_date : String::number(m_date_time.day()); - - if (is_today()) { - if (has_grid()) { - auto highlight_rect = Gfx::IntRect(day_rect.width() / 2 - (highlight_rect_width / 2), day_rect.y(), highlight_rect_width, font().glyph_height() + 4); - painter.draw_rect(highlight_rect, palette().base_text()); - } else if (is_selected()) { - painter.draw_rect(frame_inner_rect(), palette().base_text()); + int months; + mode() == Month ? months = 1 : months = 12; + for (int i = 0; i < months; i++) { + for (int j = 0; j < 42; j++) { + if (m_tiles[i][j].date_time.day() != m_previous_selected_date.day()) + continue; + if (mode() == Year && m_tiles[i][j].is_outside_selected_month) + continue; + if (m_tiles[i][j].rect.contains(event.position())) { + if (on_tile_doubleclick) + on_tile_doubleclick(); + } } - painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_bold_font(), Gfx::TextAlignment::Center, palette().base_text()); - } else if (is_outside_selection()) { - painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_font(), Gfx::TextAlignment::Center, Color::LightGray); - } else { - if (!has_grid() && is_selected()) - painter.draw_rect(frame_inner_rect(), palette().base_text()); - painter.draw_text(day_rect, display_date, Gfx::FontDatabase::default_font(), Gfx::TextAlignment::Center, palette().base_text()); } } - } diff --git a/Userland/Libraries/LibGUI/Calendar.h b/Userland/Libraries/LibGUI/Calendar.h index a160665591..0cc84bf3df 100644 --- a/Userland/Libraries/LibGUI/Calendar.h +++ b/Userland/Libraries/LibGUI/Calendar.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2019-2020, Ryan Grieb <ryan.m.grieb@gmail.com> - * Copyright (c) 2020, the SerenityOS developers. + * Copyright (c) 2020-2021, the SerenityOS developers * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,9 +29,7 @@ #include <AK/String.h> #include <LibCore/DateTime.h> -#include <LibGUI/Button.h> #include <LibGUI/Frame.h> -#include <LibGUI/Label.h> #include <LibGUI/Widget.h> namespace GUI { @@ -45,97 +43,108 @@ public: Year }; - enum { - ShortNames, - LongNames + enum Format { + ShortMonthYear, + LongMonthYear, + MonthOnly, + YearOnly }; - Calendar(Core::DateTime); + Calendar(Core::DateTime date_time = Core::DateTime::now(), Mode mode = Month); virtual ~Calendar() override; - unsigned int selected_year() const { return m_selected_year; } - unsigned int selected_month() const { return m_selected_month; } - const String selected_calendar_text(bool long_names = ShortNames); - void update_tiles(unsigned int target_year, unsigned int target_month); - void set_selected_calendar(unsigned int year, unsigned int month); void set_selected_date(Core::DateTime date_time) { m_selected_date = date_time; } Core::DateTime selected_date() const { return m_selected_date; } - void toggle_mode(); - void set_grid(bool grid); - bool has_grid() { return m_grid; } + + void set_view_date(unsigned year, unsigned month) + { + m_view_year = year; + m_view_month = month; + } + unsigned view_year() const { return m_view_year; } + unsigned view_month() const { return m_view_month; } + + String formatted_date(Format format = LongMonthYear); + Mode mode() const { return m_mode; } + void toggle_mode(); - Function<void()> on_calendar_tile_click; - Function<void()> on_calendar_tile_doubleclick; - Function<void()> on_month_tile_click; + void update_tiles(unsigned year, unsigned month); -private: - virtual void resize_event(GUI::ResizeEvent&) override; + void set_grid(bool); + bool has_grid() const { return m_grid; } - class CalendarTile final : public GUI::Frame { - C_OBJECT(CalendarTile) - public: - CalendarTile(int index, Core::DateTime m_date_time); - void update_values(int index, Core::DateTime date_time); - virtual ~CalendarTile() override; - bool is_today() const; - bool is_hovered() const { return m_hovered; } - bool is_selected() const { return m_selected; } - void set_selected(bool b) { m_selected = b; } - bool is_outside_selection() const { return m_outside_selection; } - void set_outside_selection(bool b) { m_outside_selection = b; } - bool has_grid() const { return m_grid; } - void set_grid(bool b) { m_grid = b; } - Core::DateTime get_date_time() { return m_date_time; } - Function<void(int index)> on_doubleclick; - Function<void(int index)> on_click; - - private: - virtual void doubleclick_event(GUI::MouseEvent&) override; - virtual void mousedown_event(GUI::MouseEvent&) override; - virtual void enter_event(Core::Event&) override; - virtual void leave_event(Core::Event&) override; - virtual void paint_event(GUI::PaintEvent&) override; - - int m_index { 0 }; - bool m_outside_selection { false }; - bool m_hovered { false }; - bool m_selected { false }; - bool m_grid { true }; - String m_display_date; - Core::DateTime m_date_time; - }; + void set_show_year(bool b) { m_show_year = b; } + bool is_showing_year() const { return m_show_year; } - class MonthTile final : public GUI::Button { - C_OBJECT(MonthTile) - public: - MonthTile(int index, Core::DateTime m_date_time); - virtual ~MonthTile() override; - void update_values(Core::DateTime date_time) { m_date_time = date_time; } - Core::DateTime get_date_time() { return m_date_time; } - Function<void(int index)> on_indexed_click; + void set_show_month_and_year(bool b) { m_show_month_year = b; } + bool is_showing_month_and_year() const { return m_show_month_year; } - private: - virtual void mouseup_event(GUI::MouseEvent&) override; + void set_show_days_of_the_week(bool b) { m_show_days = b; } + bool is_showing_days_of_the_week() const { return m_show_days; } - int m_index { 0 }; - Core::DateTime m_date_time; - }; + Gfx::IntSize unadjusted_tile_size() const { return m_unadjusted_tile_size; } + void set_unadjusted_tile_size(int width, int height) + { + m_unadjusted_tile_size.set_width(width); + m_unadjusted_tile_size.set_height(height); + } - RefPtr<MonthTile> m_month_tiles[12]; - RefPtr<CalendarTile> m_calendar_tiles[42]; - RefPtr<GUI::Label> m_day_names[7]; - RefPtr<GUI::Widget> m_week_rows[6]; - RefPtr<GUI::Widget> m_month_rows[3]; - RefPtr<GUI::Widget> m_month_tile_container; - RefPtr<GUI::Widget> m_calendar_tile_container; - RefPtr<GUI::Widget> m_day_name_container; + Function<void()> on_tile_click; + Function<void()> on_tile_doubleclick; + Function<void()> on_month_click; +private: + virtual void resize_event(GUI::ResizeEvent&) override; + virtual void paint_event(GUI::PaintEvent&) override; + virtual void mousemove_event(GUI::MouseEvent&) override; + virtual void mousedown_event(MouseEvent&) override; + virtual void mouseup_event(MouseEvent&) override; + virtual void doubleclick_event(MouseEvent&); + virtual void leave_event(Core::Event&) override; + + struct Day { + String name; + int width { 0 }; + int height { 16 }; + }; + Vector<Day> m_days; + + struct MonthTile { + String name; + Gfx::IntRect rect; + int width { 0 }; + int height { 0 }; + bool is_hovered { false }; + bool is_being_pressed { false }; + }; + Vector<MonthTile> m_months; + + struct Tile { + Core::DateTime date_time; + Gfx::IntRect rect; + int width { 0 }; + int height { 0 }; + bool is_today { false }; + bool is_selected { false }; + bool is_hovered { false }; + bool is_outside_selected_month { false }; + }; + Vector<Tile> m_tiles[12]; + + bool m_grid { true }; + bool m_show_month_year { true }; + bool m_show_days { true }; + bool m_show_year { false }; + bool m_show_month_tiles { false }; + int m_currently_pressed_index { -1 }; + unsigned m_view_year; + unsigned m_view_month; Core::DateTime m_selected_date; Core::DateTime m_previous_selected_date; - unsigned int m_selected_year { 0 }; - unsigned int m_selected_month { 0 }; - bool m_grid { true }; + Gfx::IntSize m_unadjusted_tile_size; + Gfx::IntSize m_event_size; + Gfx::IntSize m_month_size[12]; Mode m_mode { Month }; }; |