mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 20:42:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			252 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2020, the SerenityOS developers.
 | |
|  * Copyright (c) 2022, the SerenityOS developers.
 | |
|  *
 | |
|  * SPDX-License-Identifier: BSD-2-Clause
 | |
|  */
 | |
| 
 | |
| #include "BoardView.h"
 | |
| #include <LibGUI/Painter.h>
 | |
| #include <LibGfx/Font/Font.h>
 | |
| #include <LibGfx/Font/FontDatabase.h>
 | |
| #include <LibGfx/Palette.h>
 | |
| 
 | |
| BoardView::BoardView(Game::Board const* board)
 | |
|     : m_board(board)
 | |
| {
 | |
| }
 | |
| 
 | |
| void BoardView::set_board(Game::Board const* board)
 | |
| {
 | |
|     if (has_timer())
 | |
|         stop_timer();
 | |
| 
 | |
|     slide_animation_frame = 0;
 | |
|     pop_in_animation_frame = 0;
 | |
|     start_timer(frame_duration_ms);
 | |
| 
 | |
|     if (m_board == board)
 | |
|         return;
 | |
| 
 | |
|     if (!board) {
 | |
|         m_board = nullptr;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     bool must_resize = !m_board || m_board->tiles().size() != board->tiles().size();
 | |
| 
 | |
|     m_board = board;
 | |
| 
 | |
|     if (must_resize)
 | |
|         resize();
 | |
| 
 | |
|     update();
 | |
| }
 | |
| 
 | |
| void BoardView::pick_font()
 | |
| {
 | |
|     String best_font_name;
 | |
|     int best_font_size = -1;
 | |
|     auto& font_database = Gfx::FontDatabase::the();
 | |
|     font_database.for_each_font([&](Gfx::Font const& font) {
 | |
|         if (font.family() != "Liza" || font.weight() != 700)
 | |
|             return;
 | |
|         auto size = font.glyph_height();
 | |
|         if (size * 2 <= m_cell_size && size > best_font_size) {
 | |
|             best_font_name = font.qualified_name();
 | |
|             best_font_size = size;
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     auto font = font_database.get_by_name(best_font_name);
 | |
|     set_font(font);
 | |
| 
 | |
|     m_min_cell_size = best_font_size;
 | |
| }
 | |
| 
 | |
| size_t BoardView::rows() const
 | |
| {
 | |
|     if (!m_board)
 | |
|         return 0;
 | |
|     return m_board->tiles().size();
 | |
| }
 | |
| 
 | |
| size_t BoardView::columns() const
 | |
| {
 | |
|     if (!m_board)
 | |
|         return 0;
 | |
|     if (m_board->tiles().is_empty())
 | |
|         return 0;
 | |
|     return m_board->tiles()[0].size();
 | |
| }
 | |
| 
 | |
| void BoardView::resize_event(GUI::ResizeEvent&)
 | |
| {
 | |
|     resize();
 | |
| }
 | |
| 
 | |
| void BoardView::resize()
 | |
| {
 | |
|     constexpr float padding_ratio = 7;
 | |
|     m_padding = min(
 | |
|         width() / (columns() * (padding_ratio + 1) + 1),
 | |
|         height() / (rows() * (padding_ratio + 1) + 1));
 | |
|     m_cell_size = m_padding * padding_ratio;
 | |
| 
 | |
|     pick_font();
 | |
| }
 | |
| 
 | |
| void BoardView::keydown_event(GUI::KeyEvent& event)
 | |
| {
 | |
|     if (!on_move)
 | |
|         return;
 | |
| 
 | |
|     switch (event.key()) {
 | |
|     case KeyCode::Key_A:
 | |
|     case KeyCode::Key_Left:
 | |
|         on_move(Game::Direction::Left);
 | |
|         break;
 | |
|     case KeyCode::Key_D:
 | |
|     case KeyCode::Key_Right:
 | |
|         on_move(Game::Direction::Right);
 | |
|         break;
 | |
|     case KeyCode::Key_W:
 | |
|     case KeyCode::Key_Up:
 | |
|         on_move(Game::Direction::Up);
 | |
|         break;
 | |
|     case KeyCode::Key_S:
 | |
|     case KeyCode::Key_Down:
 | |
|         on_move(Game::Direction::Down);
 | |
|         break;
 | |
|     default:
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| Gfx::Color BoardView::background_color_for_cell(u32 value)
 | |
| {
 | |
|     switch (value) {
 | |
|     case 0:
 | |
|         return Color::from_rgb(0xcdc1b4);
 | |
|     case 2:
 | |
|         return Color::from_rgb(0xeee4da);
 | |
|     case 4:
 | |
|         return Color::from_rgb(0xede0c8);
 | |
|     case 8:
 | |
|         return Color::from_rgb(0xf2b179);
 | |
|     case 16:
 | |
|         return Color::from_rgb(0xf59563);
 | |
|     case 32:
 | |
|         return Color::from_rgb(0xf67c5f);
 | |
|     case 64:
 | |
|         return Color::from_rgb(0xf65e3b);
 | |
|     case 128:
 | |
|         return Color::from_rgb(0xedcf72);
 | |
|     case 256:
 | |
|         return Color::from_rgb(0xedcc61);
 | |
|     case 512:
 | |
|         return Color::from_rgb(0xedc850);
 | |
|     case 1024:
 | |
|         return Color::from_rgb(0xedc53f);
 | |
|     case 2048:
 | |
|         return Color::from_rgb(0xedc22e);
 | |
|     default:
 | |
|         VERIFY(value > 2048);
 | |
|         return Color::from_rgb(0x3c3a32);
 | |
|     }
 | |
| }
 | |
| 
 | |
| Gfx::Color BoardView::text_color_for_cell(u32 value)
 | |
| {
 | |
|     if (value <= 4)
 | |
|         return Color::from_rgb(0x776e65);
 | |
|     return Color::from_rgb(0xf9f6f2);
 | |
| }
 | |
| 
 | |
| void BoardView::timer_event(Core::TimerEvent&)
 | |
| {
 | |
|     if (slide_animation_frame < animation_duration) {
 | |
|         slide_animation_frame++;
 | |
|         update();
 | |
|     } else if (pop_in_animation_frame < animation_duration) {
 | |
|         pop_in_animation_frame++;
 | |
|         update();
 | |
|         if (pop_in_animation_frame == animation_duration)
 | |
|             stop_timer();
 | |
|     }
 | |
| }
 | |
| 
 | |
| void BoardView::paint_event(GUI::PaintEvent& event)
 | |
| {
 | |
|     Frame::paint_event(event);
 | |
| 
 | |
|     Color background_color = Color::from_rgb(0xbbada0);
 | |
| 
 | |
|     GUI::Painter painter(*this);
 | |
|     painter.add_clip_rect(event.rect());
 | |
|     painter.add_clip_rect(frame_inner_rect());
 | |
|     painter.translate(frame_thickness(), frame_thickness());
 | |
| 
 | |
|     if (!m_board) {
 | |
|         painter.fill_rect(rect(), background_color);
 | |
|         return;
 | |
|     }
 | |
|     auto& tiles = m_board->tiles();
 | |
| 
 | |
|     Gfx::IntRect field_rect {
 | |
|         0,
 | |
|         0,
 | |
|         static_cast<int>(m_padding + (m_cell_size + m_padding) * columns()),
 | |
|         static_cast<int>(m_padding + (m_cell_size + m_padding) * rows())
 | |
|     };
 | |
|     field_rect.center_within(rect());
 | |
|     painter.fill_rect(field_rect, background_color);
 | |
| 
 | |
|     auto tile_center = [&](size_t row, size_t column) {
 | |
|         return Gfx::IntPoint {
 | |
|             field_rect.x() + m_padding + (m_cell_size + m_padding) * column + m_cell_size / 2,
 | |
|             field_rect.y() + m_padding + (m_cell_size + m_padding) * row + m_cell_size / 2,
 | |
|         };
 | |
|     };
 | |
| 
 | |
|     if (slide_animation_frame < animation_duration) {
 | |
|         // background
 | |
|         for (size_t column = 0; column < columns(); ++column) {
 | |
|             for (size_t row = 0; row < rows(); ++row) {
 | |
|                 auto center = tile_center(row, column);
 | |
|                 auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
 | |
|                 auto rect = Gfx::IntRect::centered_on(center, tile_size);
 | |
|                 painter.fill_rect(rect, background_color_for_cell(0));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         for (auto& sliding_tile : m_board->sliding_tiles()) {
 | |
|             auto center_from = tile_center(sliding_tile.row_from, sliding_tile.column_from);
 | |
|             auto center_to = tile_center(sliding_tile.row_to, sliding_tile.column_to);
 | |
|             auto offset = Gfx::FloatPoint(center_to - center_from);
 | |
|             auto center = center_from + Gfx::IntPoint(offset * (slide_animation_frame / (float)animation_duration));
 | |
| 
 | |
|             auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
 | |
|             auto rect = Gfx::IntRect::centered_on(center, tile_size);
 | |
| 
 | |
|             painter.fill_rect(rect, background_color_for_cell(sliding_tile.value_from));
 | |
|             painter.draw_text(rect, String::number(sliding_tile.value_from), font(), Gfx::TextAlignment::Center, text_color_for_cell(sliding_tile.value_from));
 | |
|         }
 | |
|     } else {
 | |
|         for (size_t column = 0; column < columns(); ++column) {
 | |
|             for (size_t row = 0; row < rows(); ++row) {
 | |
|                 auto center = tile_center(row, column);
 | |
|                 auto tile_size = Gfx::IntSize { m_cell_size, m_cell_size };
 | |
|                 if (pop_in_animation_frame < animation_duration && Game::Board::Position { row, column } == m_board->last_added_position()) {
 | |
|                     float pop_in_size = m_min_cell_size + (m_cell_size - m_min_cell_size) * (pop_in_animation_frame / (float)animation_duration);
 | |
|                     tile_size = Gfx::IntSize { pop_in_size, pop_in_size };
 | |
|                 }
 | |
|                 auto rect = Gfx::IntRect::centered_on(center, tile_size);
 | |
|                 auto entry = tiles[row][column];
 | |
|                 painter.fill_rect(rect, background_color_for_cell(entry));
 | |
|                 if (entry > 0)
 | |
|                     painter.draw_text(rect, String::number(entry), font(), Gfx::TextAlignment::Center, text_color_for_cell(entry));
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | 
