diff options
-rw-r--r-- | Userland/Demos/CatDog/CatDog.cpp | 257 | ||||
-rw-r--r-- | Userland/Demos/CatDog/CatDog.h | 137 | ||||
-rw-r--r-- | Userland/Demos/CatDog/SpeechBubble.cpp | 5 | ||||
-rw-r--r-- | Userland/Demos/CatDog/main.cpp | 3 |
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(); |