Serenity Operating System
1/*
2 * Copyright (c) 2020, Till Mayer <till.mayer@web.de>
3 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
4 * Copyright (c) 2022, the SerenityOS developers.
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include "CardGame.h"
10#include <LibCards/CardPainter.h>
11#include <LibConfig/Client.h>
12#include <LibGUI/Action.h>
13#include <LibGUI/Process.h>
14#include <LibGUI/Window.h>
15#include <LibGfx/Palette.h>
16
17namespace Cards {
18
19ErrorOr<NonnullRefPtr<GUI::Action>> make_cards_settings_action(GUI::Window* parent)
20{
21 auto action = GUI::Action::create(
22 "&Cards Settings", {}, TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/games.png"sv)), [parent](auto&) {
23 GUI::Process::spawn_or_show_error(parent, "/bin/GamesSettings"sv, Array { "--open-tab", "cards" });
24 },
25 parent);
26 action->set_status_tip("Open the Game Settings for Cards");
27 return action;
28}
29
30CardGame::CardGame()
31{
32 auto background_color = Gfx::Color::from_string(Config::read_string("Games"sv, "Cards"sv, "BackgroundColor"sv));
33 set_background_color(background_color.value_or(Color::from_rgb(0x008000)));
34}
35
36void CardGame::mark_intersecting_stacks_dirty(Cards::Card const& intersecting_card)
37{
38 for (auto& stack : stacks()) {
39 if (intersecting_card.rect().intersects(stack->bounding_box()))
40 update(stack->bounding_box());
41 }
42
43 update(intersecting_card.rect());
44}
45
46Gfx::IntRect CardGame::moving_cards_bounds() const
47{
48 if (!is_moving_cards())
49 return {};
50
51 // Note: This assumes that the cards are arranged in a line.
52 return m_moving_cards.first()->rect().united(m_moving_cards.last()->rect());
53}
54
55ErrorOr<void> CardGame::pick_up_cards_from_stack(Cards::CardStack& stack, Gfx::IntPoint click_location, CardStack::MovementRule movement_rule)
56{
57 TRY(stack.add_all_grabbed_cards(click_location, m_moving_cards, movement_rule));
58 m_moving_cards_source_stack = stack;
59 return {};
60}
61
62RefPtr<CardStack> CardGame::find_stack_to_drop_on(CardStack::MovementRule movement_rule)
63{
64 auto bounds_to_check = moving_cards_bounds();
65
66 RefPtr<CardStack> closest_stack;
67 float closest_distance = FLT_MAX;
68
69 for (auto& stack : stacks()) {
70 if (stack == moving_cards_source_stack())
71 continue;
72
73 if (stack->bounding_box().intersects(bounds_to_check)
74 && stack->is_allowed_to_push(moving_cards().at(0), moving_cards().size(), movement_rule)) {
75
76 auto distance = bounds_to_check.center().distance_from(stack->bounding_box().center());
77 if (distance < closest_distance) {
78 closest_stack = stack;
79 closest_distance = distance;
80 }
81 }
82 }
83
84 return closest_stack;
85}
86
87ErrorOr<void> CardGame::drop_cards_on_stack(Cards::CardStack& stack, CardStack::MovementRule movement_rule)
88{
89 VERIFY(stack.is_allowed_to_push(m_moving_cards.at(0), m_moving_cards.size(), movement_rule));
90 for (auto& to_intersect : moving_cards()) {
91 mark_intersecting_stacks_dirty(to_intersect);
92 TRY(stack.push(to_intersect));
93 (void)moving_cards_source_stack()->pop();
94 }
95
96 update(moving_cards_source_stack()->bounding_box());
97 update(stack.bounding_box());
98
99 return {};
100}
101
102void CardGame::clear_moving_cards()
103{
104 m_moving_cards_source_stack.clear();
105 m_moving_cards.clear();
106}
107
108void CardGame::dump_layout() const
109{
110 dbgln("------------------------------");
111 for (auto const& stack : stacks())
112 dbgln("{}", stack);
113}
114
115void CardGame::config_string_did_change(DeprecatedString const& domain, DeprecatedString const& group, DeprecatedString const& key, DeprecatedString const& value)
116{
117 if (domain == "Games" && group == "Cards") {
118 if (key == "BackgroundColor") {
119 if (auto maybe_color = Gfx::Color::from_string(value); maybe_color.has_value())
120 set_background_color(maybe_color.value());
121 return;
122 }
123 if (key == "CardBackImage") {
124 CardPainter::the().set_background_image_path(value);
125 update();
126 return;
127 }
128 }
129}
130
131Gfx::Color CardGame::background_color() const
132{
133 return palette().color(background_role());
134}
135
136void CardGame::set_background_color(Gfx::Color color)
137{
138 auto new_palette = palette();
139 new_palette.set_color(Gfx::ColorRole::Background, color);
140 set_palette(new_palette);
141
142 CardPainter::the().set_background_color(color);
143}
144
145void CardGame::preview_card(CardStack& stack, Gfx::IntPoint click_location)
146{
147 if (!stack.preview_card(click_location))
148 return;
149
150 m_previewed_card_stack = stack;
151 update(stack.bounding_box());
152}
153
154void CardGame::clear_card_preview()
155{
156 VERIFY(m_previewed_card_stack);
157
158 update(m_previewed_card_stack->bounding_box());
159 m_previewed_card_stack->clear_card_preview();
160 m_previewed_card_stack = nullptr;
161}
162
163}