Browse Source

CatDog: Help the user debug their programs

This adds helpful speech bubbles to CatDog. CatDog just wants to
help, that's all.
Gunnar Beutner 4 years ago
parent
commit
e0fe38ea25

+ 1 - 0
Userland/Demos/CatDog/CMakeLists.txt

@@ -1,5 +1,6 @@
 set(SOURCES
     CatDog.cpp
+    SpeechBubble.cpp
     main.cpp
 )
 

+ 13 - 0
Userland/Demos/CatDog/CatDog.cpp

@@ -11,6 +11,8 @@
 
 void CatDog::timer_event(Core::TimerEvent&)
 {
+    if (!m_roaming)
+        return;
     if (m_temp_pos.x() > 48) {
         m_left = false;
         m_right = true;
@@ -105,6 +107,8 @@ void CatDog::paint_event(GUI::PaintEvent& event)
 
 void CatDog::mousemove_event(GUI::MouseEvent& event)
 {
+    if (!m_roaming)
+        return;
     if (m_temp_pos == event.position())
         return;
     m_temp_pos = event.position();
@@ -115,6 +119,15 @@ void CatDog::mousemove_event(GUI::MouseEvent& event)
     }
     m_sleeping = false;
 }
+
+void CatDog::mousedown_event(GUI::MouseEvent& event)
+{
+    if (event.button() != GUI::MouseButton::Left)
+        return;
+    if (on_click)
+        on_click();
+}
+
 void CatDog::track_cursor_globally()
 {
     VERIFY(window());

+ 16 - 0
Userland/Demos/CatDog/CatDog.h

@@ -18,16 +18,32 @@ public:
     virtual void timer_event(Core::TimerEvent&) override;
     virtual void paint_event(GUI::PaintEvent& event) override;
     virtual void mousemove_event(GUI::MouseEvent& event) override;
+    virtual void mousedown_event(GUI::MouseEvent& event) override;
 
     void track_cursor_globally();
     void start_the_timer() { m_timer.start(); }
 
+    Function<void()> on_click;
+
+    bool roaming() const { return m_roaming; }
+    void set_roaming(bool roaming)
+    {
+        m_roaming = roaming;
+        if (!roaming) {
+            m_sleeping = false;
+            m_curr_frame = 0;
+            m_curr_bmp = m_alert;
+            update();
+        }
+    }
+
 private:
     Gfx::IntPoint m_temp_pos;
     Core::ElapsedTimer m_timer;
     int m_curr_frame = 1;
     int m_moveX, m_moveY = 0;
     bool m_up, m_down, m_left, m_right, m_sleeping = false;
+    bool m_roaming { true };
 
     AK::NonnullRefPtr<Gfx::Bitmap> m_alert = *Gfx::Bitmap::load_from_file("/res/icons/catdog/alert.png");
     AK::NonnullRefPtr<Gfx::Bitmap> m_erun1 = *Gfx::Bitmap::load_from_file("/res/icons/catdog/erun1.png");

+ 40 - 0
Userland/Demos/CatDog/SpeechBubble.cpp

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, Gunnar Beutner <gunnar@beutner.name>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "SpeechBubble.h"
+#include <LibGUI/Painter.h>
+#include <LibGfx/Palette.h>
+
+void SpeechBubble::paint_event(GUI::PaintEvent&)
+{
+    GUI::Painter painter(*this);
+    painter.clear_rect(rect(), Gfx::Color());
+
+    constexpr auto background_color = Gfx::Color::from_rgb(0xeaf688);
+
+    auto text_area = rect();
+    text_area.set_height(text_area.height() - 10);
+    painter.draw_rect(text_area, palette().active_window_border1());
+    text_area.shrink(2, 2);
+    painter.fill_rect(text_area, background_color);
+
+    auto connector_top_left = Gfx::IntPoint { rect().width() / 2 - 5, text_area.height() + 1 };
+    auto connector_top_right = Gfx::IntPoint { rect().width() / 2 + 5, text_area.height() + 1 };
+    auto connector_bottom = Gfx::IntPoint { rect().width() / 2 + 10, rect().height() };
+    painter.draw_triangle(connector_top_left, connector_top_right, connector_bottom, background_color);
+    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());
+
+    painter.draw_text(text_area, "It looks like you're trying to debug\na program. Would you like some help?", Gfx::TextAlignment::Center);
+}
+
+void SpeechBubble::mousedown_event(GUI::MouseEvent& event)
+{
+    if (event.button() != GUI::MouseButton::Left)
+        return;
+    if (on_dismiss)
+        on_dismiss();
+}

+ 22 - 0
Userland/Demos/CatDog/SpeechBubble.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021, Gunnar Beutner <gunnar@beutner.name>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/Widget.h>
+
+class SpeechBubble final : public GUI::Widget {
+    C_OBJECT(SpeechBubble);
+
+public:
+    virtual void paint_event(GUI::PaintEvent&) override;
+    virtual void mousedown_event(GUI::MouseEvent&) override;
+
+    Function<void()> on_dismiss;
+
+private:
+    SpeechBubble() = default;
+};

+ 40 - 0
Userland/Demos/CatDog/main.cpp

@@ -5,6 +5,8 @@
  */
 
 #include "CatDog.h"
+#include "SpeechBubble.h"
+#include <LibCore/Timer.h>
 #include <LibGUI/Action.h>
 #include <LibGUI/Application.h>
 #include <LibGUI/BoxLayout.h>
@@ -63,5 +65,43 @@ int main(int argc, char** argv)
     catdog_widget.start_timer(250, Core::TimerShouldFireWhenNotVisible::Yes);
     catdog_widget.start_the_timer(); // timer for "mouse sleep detection"
 
+    auto advice_window = GUI::Window::construct();
+    advice_window->set_title("CatDog Advice");
+    advice_window->resize(225, 50);
+    advice_window->set_frameless(true);
+    advice_window->set_resizable(false);
+    advice_window->set_has_alpha_channel(true);
+    advice_window->set_alpha_hit_threshold(1.0f);
+
+    auto& advice_widget = advice_window->set_main_widget<SpeechBubble>();
+    advice_widget.set_layout<GUI::VerticalBoxLayout>();
+    advice_widget.layout()->set_spacing(0);
+
+    auto advice_timer = Core::Timer::construct();
+    advice_timer->set_interval(15000);
+    advice_timer->set_single_shot(true);
+    advice_timer->on_timeout = [&] {
+        window->move_to_front();
+        advice_window->move_to_front();
+        catdog_widget.set_roaming(false);
+        advice_window->move_to(window->x() - advice_window->width() / 2, window->y() - advice_window->height());
+        advice_window->show();
+    };
+    advice_timer->start();
+
+    advice_widget.on_dismiss = [&] {
+        catdog_widget.set_roaming(true);
+        advice_window->hide();
+        advice_timer->start();
+    };
+
+    // Let users toggle the advice functionality by clicking on catdog.
+    catdog_widget.on_click = [&] {
+        if (advice_timer->is_active())
+            advice_timer->stop();
+        else
+            advice_timer->start();
+    };
+
     return app->exec();
 }