diff options
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 }; }; |