summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Userland/Demos/CatDog/CatDog.cpp257
-rw-r--r--Userland/Demos/CatDog/CatDog.h137
-rw-r--r--Userland/Demos/CatDog/SpeechBubble.cpp5
-rw-r--r--Userland/Demos/CatDog/main.cpp3
4 files changed, 173 insertions, 229 deletions
diff --git a/Userland/Demos/CatDog/CatDog.cpp b/Userland/Demos/CatDog/CatDog.cpp
index fd25d2486a..ec6a14a817 100644
--- a/Userland/Demos/CatDog/CatDog.cpp
+++ b/Userland/Demos/CatDog/CatDog.cpp
@@ -12,172 +12,165 @@
ErrorOr<NonnullRefPtr<CatDog>> CatDog::create()
{
+ struct ImageSource {
+ State state;
+ StringView path;
+ };
+
+ // NOTE: The order of the elements is important. Matching is done in best-match order.
+ // So items with the more bits should be placed before items with less bits to
+ // ensure correct matching order. This also means that "Frame2" has to be first.
+ static constexpr Array<ImageSource, 24> const image_sources = {
+ ImageSource { State::Up | State::Right | State::Frame2, "/res/icons/catdog/nerun2.png"sv },
+ { State::Up | State::Right, "/res/icons/catdog/nerun1.png"sv },
+ { State::Up | State::Left | State::Frame2, "/res/icons/catdog/nwrun2.png"sv },
+ { State::Up | State::Left, "/res/icons/catdog/nwrun1.png"sv },
+ { State::Down | State::Right | State::Frame2, "/res/icons/catdog/serun2.png"sv },
+ { State::Down | State::Right, "/res/icons/catdog/serun1.png"sv },
+ { State::Down | State::Left | State::Frame2, "/res/icons/catdog/swrun2.png"sv },
+ { State::Down | State::Left, "/res/icons/catdog/swrun1.png"sv },
+ { State::Up | State::Frame2, "/res/icons/catdog/nrun2.png"sv },
+ { State::Up, "/res/icons/catdog/nrun1.png"sv },
+ { State::Down | State::Frame2, "/res/icons/catdog/srun2.png"sv },
+ { State::Down, "/res/icons/catdog/srun1.png"sv },
+ { State::Left | State::Frame2, "/res/icons/catdog/wrun2.png"sv },
+ { State::Left, "/res/icons/catdog/wrun1.png"sv },
+ { State::Right | State::Frame2, "/res/icons/catdog/erun2.png"sv },
+ { State::Right, "/res/icons/catdog/erun1.png"sv },
+ { State::Sleeping | State::Frame2, "/res/icons/catdog/sleep2.png"sv },
+ { State::Sleeping, "/res/icons/catdog/sleep1.png"sv },
+ { State::Idle | State::Artist, "/res/icons/catdog/artist.png"sv },
+ { State::Idle | State::Inspector, "/res/icons/catdog/inspector.png"sv },
+ { State::Idle, "/res/icons/catdog/still.png"sv },
+ { State::Alert | State::Artist, "/res/icons/catdog/artist.png"sv },
+ { State::Alert | State::Inspector, "/res/icons/catdog/inspector.png"sv },
+ { State::Alert, "/res/icons/catdog/alert.png"sv }
+ };
+
auto catdog = TRY(adopt_nonnull_ref_or_enomem(new (nothrow) CatDog));
- catdog->m_alert = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/alert.png"sv));
- catdog->m_artist = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/artist.png"sv));
- catdog->m_erun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/erun1.png"sv));
- catdog->m_erun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/erun2.png"sv));
- catdog->m_inspector = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/inspector.png"sv));
- catdog->m_nerun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nerun1.png"sv));
- catdog->m_nerun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nerun2.png"sv));
- catdog->m_nrun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nrun1.png"sv));
- catdog->m_nrun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nrun2.png"sv));
- catdog->m_nwrun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nwrun1.png"sv));
- catdog->m_nwrun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/nwrun2.png"sv));
- catdog->m_serun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/serun1.png"sv));
- catdog->m_serun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/serun2.png"sv));
- catdog->m_sleep1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/sleep1.png"sv));
- catdog->m_sleep2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/sleep2.png"sv));
- catdog->m_srun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/srun1.png"sv));
- catdog->m_srun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/srun2.png"sv));
- catdog->m_still = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/still.png"sv));
- catdog->m_swrun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/swrun1.png"sv));
- catdog->m_swrun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/swrun2.png"sv));
- catdog->m_wrun1 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/wrun1.png"sv));
- catdog->m_wrun2 = *TRY(Gfx::Bitmap::try_load_from_file("/res/icons/catdog/wrun2.png"sv));
- catdog->m_curr_bmp = catdog->m_alert;
+ for (auto const& image_source : image_sources)
+ TRY(catdog->m_images.try_append({ image_source.state, *TRY(Gfx::Bitmap::try_load_from_file(image_source.path)) }));
+
return catdog;
}
-void CatDog::timer_event(Core::TimerEvent&)
+CatDog::CatDog()
+ : m_proc_all(MUST(Core::Stream::File::open("/sys/kernel/processes"sv, Core::Stream::OpenMode::Read)))
+{
+ m_idle_sleep_timer.start();
+}
+
+void CatDog::set_roaming(bool roaming)
+{
+ m_state = (roaming ? State::Idle : State::Alert) | special_application_states();
+ update();
+}
+
+CatDog::State CatDog::special_application_states() const
{
auto maybe_proc_info = Core::ProcessStatisticsReader::get_all(*m_proc_all);
- if (!maybe_proc_info.is_error()) {
- auto proc_info = maybe_proc_info.release_value();
-
- auto maybe_paint_program = proc_info.processes.first_matching([](auto& process) {
- return process.name.equals_ignoring_case("pixelpaint"sv) || process.name.equals_ignoring_case("fonteditor"sv);
- });
- if (maybe_paint_program.has_value()) {
- m_main_state = MainState::Artist;
- } else {
- auto maybe_inspector_program = proc_info.processes.first_matching([](auto& process) {
- return process.name.equals_ignoring_case("inspector"sv) || process.name.equals_ignoring_case("systemmonitor"sv) || process.name.equals_ignoring_case("profiler"sv);
- });
-
- if (maybe_inspector_program.has_value())
- m_main_state = MainState::Inspector;
- // If we currently have an application state but that app isn't open anymore, go back to idle.
- else if (!is_non_application_state(m_main_state))
- m_main_state = MainState::Idle;
- }
- }
+ if (maybe_proc_info.is_error())
+ return State::GenericCatDog;
+
+ auto proc_info = maybe_proc_info.release_value();
+ auto maybe_paint_program = proc_info.processes.first_matching([](auto& process) {
+ return process.name.equals_ignoring_case("pixelpaint"sv) || process.name.equals_ignoring_case("fonteditor"sv);
+ });
+ if (maybe_paint_program.has_value())
+ return State::Artist;
+
+ auto maybe_inspector_program = proc_info.processes.first_matching([](auto& process) {
+ return process.name.equals_ignoring_case("inspector"sv) || process.name.equals_ignoring_case("systemmonitor"sv) || process.name.equals_ignoring_case("profiler"sv);
+ });
+ if (maybe_inspector_program.has_value())
+ return State::Inspector;
+
+ return State::GenericCatDog;
+}
+
+bool CatDog::is_artist() const
+{
+ return has_flag(special_application_states(), State::Artist);
+}
+
+bool CatDog::is_inspector() const
+{
+ return has_flag(special_application_states(), State::Inspector);
+}
- if (!m_roaming)
+void CatDog::timer_event(Core::TimerEvent&)
+{
+ if (has_flag(m_state, State::Alert))
return;
- if (m_temp_pos.x() > 48) {
- m_left = false;
- m_right = true;
- m_moveX = 16;
-
- m_curr_bmp = m_erun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_erun2;
- } else if (m_temp_pos.x() < -16) {
- m_left = true;
- m_right = false;
- m_moveX = -16;
-
- m_curr_bmp = m_wrun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_wrun2;
- } else {
- m_left = false;
- m_right = false;
- m_moveX = 0;
- }
- if (m_temp_pos.y() > 48) {
- m_up = false;
- m_down = true;
- m_moveY = 10;
-
- m_curr_bmp = m_srun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_srun2;
- } else if (m_temp_pos.y() < -16) {
- m_up = true;
- m_down = false;
- m_moveY = -10;
-
- m_curr_bmp = m_nrun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_nrun2;
- } else {
- m_up = false;
- m_down = false;
- m_moveY = 0;
- }
+ m_state = special_application_states();
- if (m_up && m_left) {
- m_curr_bmp = m_nwrun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_nwrun2;
- } else if (m_up && m_right) {
- m_curr_bmp = m_nerun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_nerun2;
- } else if (m_down && m_left) {
- m_curr_bmp = m_swrun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_swrun2;
- } else if (m_down && m_right) {
- m_curr_bmp = m_serun1;
- if (m_curr_frame == 2)
- m_curr_bmp = m_serun2;
+ auto const size = window()->size();
+ Gfx::IntPoint move;
+
+ if (m_mouse_offset.x() < 0) {
+ m_state |= State::Left;
+ move.set_x(max(m_mouse_offset.x(), -size.width() / 2));
+ } else if (m_mouse_offset.x() > size.width()) {
+ m_state |= State::Right;
+ move.set_x(min(m_mouse_offset.x(), size.width() / 2));
}
- window()->move_to(window()->position().x() + m_moveX, window()->position().y() + m_moveY);
- m_temp_pos.set_x(m_temp_pos.x() + (-m_moveX));
- m_temp_pos.set_y(m_temp_pos.y() + (-m_moveY));
+ if (m_mouse_offset.y() < 0) {
+ m_state |= State::Up;
+ move.set_y(max(m_mouse_offset.y(), -size.height() / 2));
+ } else if (m_mouse_offset.y() > size.height()) {
+ m_state |= State::Down;
+ move.set_y(min(m_mouse_offset.y(), size.height() / 2));
+ }
- if (m_curr_frame == 1) {
- m_curr_frame = 2;
+ if (has_any_flag(m_state, State::Directions)) {
+ m_idle_sleep_timer.start();
} else {
- m_curr_frame = 1;
+ if (m_idle_sleep_timer.elapsed() > 5'000)
+ m_state |= State::Sleeping;
+ else
+ m_state |= State::Idle;
}
- if (!m_up && !m_down && !m_left && !m_right) {
- // Select the movement-free image based on the main state.
- if (m_timer.elapsed() > 5000)
- m_main_state = MainState::Sleeping;
- set_image_by_main_state();
- } else if (is_non_application_state(m_main_state)) {
- // If CatDog currently moves, it should be idle the next time it stops.
- m_main_state = MainState::Idle;
- }
+ window()->move_to(window()->position() + move);
+ m_mouse_offset -= move;
+
+ m_frame = m_frame == State::Frame1 ? State::Frame2 : State::Frame1;
+ m_state |= m_frame;
update();
}
+Gfx::Bitmap& CatDog::bitmap_for_state() const
+{
+ auto const iter = m_images.find_if([&](auto const& image) { return (m_state & image.state) == image.state; });
+ return iter != m_images.end() ? *iter->bitmap : *m_images[m_images.size() - 1].bitmap;
+}
+
void CatDog::paint_event(GUI::PaintEvent& event)
{
+ auto const& bmp = bitmap_for_state();
GUI::Painter painter(*this);
painter.clear_rect(event.rect(), Gfx::Color());
- painter.blit(Gfx::IntPoint(0, 0), *m_curr_bmp, m_curr_bmp->rect());
+ painter.blit(Gfx::IntPoint(0, 0), bmp, bmp.rect());
}
void CatDog::track_mouse_move(Gfx::IntPoint point)
{
- if (!m_roaming)
- return;
- Gfx::IntPoint relative_point = point - window()->position();
- if (m_temp_pos == relative_point)
+ if (has_flag(m_state, State::Alert))
return;
- m_temp_pos = relative_point;
- m_timer.start();
- if (m_main_state == MainState::Sleeping) {
- m_main_state = MainState::Alerted;
- set_image_by_main_state();
- update();
+
+ Gfx::IntPoint relative_offset = point - window()->position();
+ if (m_mouse_offset != relative_offset) {
+ m_mouse_offset = relative_offset;
+ m_idle_sleep_timer.start();
}
}
void CatDog::mousedown_event(GUI::MouseEvent& event)
{
- if (event.button() != GUI::MouseButton::Primary)
- return;
- if (on_click)
+ if (event.button() == GUI::MouseButton::Primary && on_click)
on_click();
}
diff --git a/Userland/Demos/CatDog/CatDog.h b/Userland/Demos/CatDog/CatDog.h
index b007f4f60a..ddabc8f39f 100644
--- a/Userland/Demos/CatDog/CatDog.h
+++ b/Userland/Demos/CatDog/CatDog.h
@@ -7,8 +7,10 @@
#pragma once
+#include <AK/EnumBits.h>
#include <AK/NonnullRefPtr.h>
#include <AK/RefPtr.h>
+#include <AK/Types.h>
#include <LibCore/ElapsedTimer.h>
#include <LibCore/File.h>
#include <LibCore/Stream.h>
@@ -25,112 +27,61 @@ class CatDog final : public GUI::Widget
public:
static ErrorOr<NonnullRefPtr<CatDog>> create();
- // The general state, does not contain movement direction or whether CatDog is roaming.
- enum class MainState {
- Idle, // default state
- Alerted, // woken by mouse cursor or speaking after being idle
- Sleeping, // mouse hasn't moved in some time
- Artist, // PixelPaint or FontEditor are open
- Inspector, // SystemServer, Profiler or Inspector are open
- };
- static bool is_non_application_state(MainState state)
- {
- return state == MainState::Idle || state == MainState::Alerted || state == MainState::Sleeping;
- }
-
virtual void timer_event(Core::TimerEvent&) override;
virtual void paint_event(GUI::PaintEvent& event) override;
virtual void track_mouse_move(Gfx::IntPoint point) override;
virtual void mousedown_event(GUI::MouseEvent& event) override;
virtual void context_menu_event(GUI::ContextMenuEvent& event) override;
- void start_the_timer() { m_timer.start(); }
-
Function<void()> on_click;
Function<void(GUI::ContextMenuEvent&)> on_context_menu_request;
- bool roaming() const { return m_roaming; }
- void set_roaming(bool roaming)
- {
- m_roaming = roaming;
- if (!roaming) {
- // If we stop CatDog while it's in a program-specific state, we don't want it to be alerted.
- if (m_main_state == MainState::Idle || m_main_state == MainState::Sleeping)
- m_main_state = MainState::Alerted;
- m_curr_frame = 0;
- set_image_by_main_state();
- update();
- }
- }
-
- MainState main_state() const { return m_main_state; }
+ void set_roaming(bool roaming);
+
+ [[nodiscard]] bool is_artist() const;
+ [[nodiscard]] bool is_inspector() const;
private:
- Gfx::IntPoint m_temp_pos;
- Core::ElapsedTimer m_timer;
+ enum class State : u16 {
+ Frame1 = 0x0,
+ Frame2 = 0x1,
- int m_curr_frame = 1;
- int m_moveX, m_moveY = 0;
- MainState m_main_state { MainState::Alerted };
- bool m_up, m_down, m_left, m_right;
- bool m_roaming { true };
+ Up = 0x10,
+ Down = 0x20,
+ Left = 0x40,
+ Right = 0x80,
+
+ Directions = Up | Down | Left | Right,
+
+ Roaming = 0x0100,
+ Idle = 0x0200,
+ Sleeping = 0x0400,
+ Alert = 0x0800,
+
+ GenericCatDog = 0x0000,
+ Inspector = 0x1000,
+ Artist = 0x2000
+ };
+
+ AK_ENUM_BITWISE_FRIEND_OPERATORS(State);
+
+ struct ImageForState {
+ State state;
+ NonnullRefPtr<Gfx::Bitmap> bitmap;
+ };
+
+ Vector<ImageForState> m_images;
+
+ Gfx::IntPoint m_mouse_offset {};
+ Core::ElapsedTimer m_idle_sleep_timer;
NonnullOwnPtr<Core::Stream::File> m_proc_all;
- RefPtr<Gfx::Bitmap> m_alert;
- RefPtr<Gfx::Bitmap> m_artist;
- RefPtr<Gfx::Bitmap> m_erun1;
- RefPtr<Gfx::Bitmap> m_erun2;
- RefPtr<Gfx::Bitmap> m_inspector;
- RefPtr<Gfx::Bitmap> m_nerun1;
- RefPtr<Gfx::Bitmap> m_nerun2;
- RefPtr<Gfx::Bitmap> m_nrun1;
- RefPtr<Gfx::Bitmap> m_nrun2;
- RefPtr<Gfx::Bitmap> m_nwrun1;
- RefPtr<Gfx::Bitmap> m_nwrun2;
- RefPtr<Gfx::Bitmap> m_serun1;
- RefPtr<Gfx::Bitmap> m_serun2;
- RefPtr<Gfx::Bitmap> m_sleep1;
- RefPtr<Gfx::Bitmap> m_sleep2;
- RefPtr<Gfx::Bitmap> m_srun1;
- RefPtr<Gfx::Bitmap> m_srun2;
- RefPtr<Gfx::Bitmap> m_still;
- RefPtr<Gfx::Bitmap> m_swrun1;
- RefPtr<Gfx::Bitmap> m_swrun2;
- RefPtr<Gfx::Bitmap> m_wrun1;
- RefPtr<Gfx::Bitmap> m_wrun2;
-
- RefPtr<Gfx::Bitmap> m_curr_bmp;
-
- // Used if CatDog is still; may also account for animation frames.
- void set_image_by_main_state()
- {
- switch (m_main_state) {
- case MainState::Idle:
- m_curr_bmp = m_still;
- break;
- case MainState::Alerted:
- m_curr_bmp = m_alert;
- break;
- case MainState::Sleeping:
- if (m_curr_frame == 1)
- m_curr_bmp = m_sleep1;
- else
- m_curr_bmp = m_sleep2;
- break;
- case MainState::Artist:
- m_curr_bmp = m_artist;
- break;
- case MainState::Inspector:
- m_curr_bmp = m_inspector;
- break;
- }
- }
-
- CatDog()
- : m_temp_pos { 0, 0 }
- , m_proc_all(MUST(Core::Stream::File::open("/sys/kernel/processes"sv, Core::Stream::OpenMode::Read)))
- {
- set_image_by_main_state();
- }
+ State m_state { State::Roaming };
+ State m_frame { State::Frame1 };
+
+ CatDog();
+
+ [[nodiscard]] Gfx::Bitmap& bitmap_for_state() const;
+ [[nodiscard]] State special_application_states() const;
};
diff --git a/Userland/Demos/CatDog/SpeechBubble.cpp b/Userland/Demos/CatDog/SpeechBubble.cpp
index b5309ab789..66c11a9028 100644
--- a/Userland/Demos/CatDog/SpeechBubble.cpp
+++ b/Userland/Demos/CatDog/SpeechBubble.cpp
@@ -17,7 +17,8 @@ static Array<StringView, 3> default_messages = {
"It looks like you're trying to shave\na yak. Would you like some help?"sv,
"Well Hello Friend!"sv,
};
-static Array<StringView, 3> pixel_paint_messages = {
+
+static Array<StringView, 3> artist_messages = {
"It looks like you're creating art.\nWould you like some help?"sv,
"It looks like you're making a meme\nfor Discord. \U0010CD65"sv,
"It looks like you're using the filter\ngallery. Would you like a suggestion?"sv,
@@ -48,7 +49,7 @@ void SpeechBubble::paint_event(GUI::PaintEvent&)
painter.draw_line(connector_top_left, Gfx::IntPoint { connector_bottom.x() - 1, connector_bottom.y() }, palette().active_window_border1());
painter.draw_line(connector_top_right, connector_bottom, palette().active_window_border1());
- auto& message_list = m_cat_dog->main_state() == CatDog::MainState::Artist ? pixel_paint_messages : (m_cat_dog->main_state() == CatDog::MainState::Inspector ? inspector_messages : default_messages);
+ auto& message_list = m_cat_dog->is_artist() ? artist_messages : (m_cat_dog->is_inspector() ? inspector_messages : default_messages);
auto message = message_list[get_random<u8>() % message_list.size()];
painter.draw_text(text_area, message, Gfx::TextAlignment::Center);
}
diff --git a/Userland/Demos/CatDog/main.cpp b/Userland/Demos/CatDog/main.cpp
index 6898320055..2a8ca4ea80 100644
--- a/Userland/Demos/CatDog/main.cpp
+++ b/Userland/Demos/CatDog/main.cpp
@@ -53,7 +53,6 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
window->show();
window->set_always_on_top();
catdog_widget->start_timer(250, Core::TimerShouldFireWhenNotVisible::Yes);
- catdog_widget->start_the_timer(); // timer for "mouse sleep detection"
auto advice_window = TRY(GUI::Window::try_create());
advice_window->set_title("CatDog Advice");
@@ -68,7 +67,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
advice_widget->layout()->set_spacing(0);
auto advice_timer = TRY(Core::Timer::try_create());
- advice_timer->set_interval(15000);
+ advice_timer->set_interval(15'000);
advice_timer->set_single_shot(true);
advice_timer->on_timeout = [&] {
window->move_to_front();