Browse Source

ColorPicker: Add ability to select a color on the screen

This commit adds a `ColorSelectOverlay` class, and uses it to
allow the user to pick a color from the screen. The API for
`ColorSelectOverlay` is inspired from the `SelectableOverlay`
in `Utilities/shot.cpp`. In particular, it opens up it's own
window, so that we can have control over the cursor over the
whole screen.

There's one thing notably different: In addition to returning the
final selected color from the `exec()` function, it also provides
an `on_color_changed()` hook, which can be used to (optionally)
get live updated as the mouse is moving around.

This is a bit odd, but allows us to use the preview widget of the
color picker to see the current color under the mouse (which will
be selected upon clicking). When trying to select the color from
text / other small elements, this is very useful.
Mustafa Quraish 3 năm trước cách đây
mục cha
commit
253eb5c6d7

+ 74 - 1
Userland/Libraries/LibGUI/ColorPicker.cpp

@@ -13,6 +13,7 @@
 #include <LibGUI/SpinBox.h>
 #include <LibGUI/SpinBox.h>
 #include <LibGUI/TabWidget.h>
 #include <LibGUI/TabWidget.h>
 #include <LibGUI/TextBox.h>
 #include <LibGUI/TextBox.h>
+#include <LibGUI/WindowServerConnection.h>
 #include <LibGfx/Palette.h>
 #include <LibGfx/Palette.h>
 
 
 namespace GUI {
 namespace GUI {
@@ -125,6 +126,60 @@ private:
     RefPtr<ColorSlider> m_color_slider;
     RefPtr<ColorSlider> m_color_slider;
 };
 };
 
 
+class ColorSelectOverlay final : public Widget {
+    C_OBJECT(ColorSelectOverlay)
+public:
+    ColorSelectOverlay()
+    {
+        set_override_cursor(Gfx::StandardCursor::Eyedropper);
+    }
+
+    Optional<Color> exec()
+    {
+        m_event_loop = make<Core::EventLoop>();
+
+        // FIXME: Allow creation of fully transparent windows without a backing store.
+        auto window = Window::construct();
+        window->set_main_widget(this);
+        window->set_has_alpha_channel(true);
+        window->set_background_color(Color::Transparent);
+        window->set_fullscreen(true);
+        window->set_frameless(true);
+        window->show();
+
+        if (!m_event_loop->exec())
+            return {};
+        return m_col;
+    }
+
+    virtual ~ColorSelectOverlay() override { }
+    Function<void(Color)> on_color_changed;
+
+private:
+    virtual void mousedown_event(GUI::MouseEvent&) { m_event_loop->quit(1); }
+    virtual void mousemove_event(GUI::MouseEvent&)
+    {
+        auto new_col = WindowServerConnection::the().get_color_under_cursor();
+        if (new_col == m_col)
+            return;
+        m_col = new_col;
+        if (on_color_changed)
+            on_color_changed(m_col);
+    }
+
+    virtual void keydown_event(GUI::KeyEvent& event)
+    {
+        if (event.key() == KeyCode::Key_Escape) {
+            event.accept();
+            m_event_loop->quit(0);
+            return;
+        }
+    }
+
+    OwnPtr<Core::EventLoop> m_event_loop;
+    Color m_col;
+};
+
 ColorPicker::ColorPicker(Color color, Window* parent_window, String title)
 ColorPicker::ColorPicker(Color color, Window* parent_window, String title)
     : Dialog(parent_window)
     : Dialog(parent_window)
     , m_color(color)
     , m_color(color)
@@ -247,7 +302,7 @@ void ColorPicker::build_ui_custom(Widget& root_container)
     preview_container.set_layout<VerticalBoxLayout>();
     preview_container.set_layout<VerticalBoxLayout>();
     preview_container.layout()->set_margins(2);
     preview_container.layout()->set_margins(2);
     preview_container.layout()->set_spacing(0);
     preview_container.layout()->set_spacing(0);
-    preview_container.set_fixed_height(128);
+    preview_container.set_fixed_height(100);
 
 
     // Current color
     // Current color
     preview_container.add<ColorPreview>(m_color);
     preview_container.add<ColorPreview>(m_color);
@@ -340,6 +395,24 @@ void ColorPicker::build_ui_custom(Widget& root_container)
     make_spinbox(Green, m_color.green());
     make_spinbox(Green, m_color.green());
     make_spinbox(Blue, m_color.blue());
     make_spinbox(Blue, m_color.blue());
     make_spinbox(Alpha, m_color.alpha());
     make_spinbox(Alpha, m_color.alpha());
+
+    m_selector_button = vertical_container.add<GUI::Button>("Select on screen");
+    m_selector_button->on_click = [this](auto) {
+        auto selector = ColorSelectOverlay::construct();
+        auto original_color = m_color;
+        // This allows us to use the color preview widget as a live-preview for
+        // the color currently under the cursor, which is helpful.
+        selector->on_color_changed = [this](auto color) {
+            m_color = color;
+            update_color_widgets();
+        };
+
+        // Set the final color
+        auto maybe_color = selector->exec();
+        m_color = maybe_color.value_or(original_color);
+        m_custom_color->set_color(m_color);
+        update_color_widgets();
+    };
 }
 }
 
 
 void ColorPicker::update_color_widgets()
 void ColorPicker::update_color_widgets()

+ 2 - 0
Userland/Libraries/LibGUI/ColorPicker.h

@@ -14,6 +14,7 @@ namespace GUI {
 class ColorButton;
 class ColorButton;
 class ColorPreview;
 class ColorPreview;
 class CustomColorWidget;
 class CustomColorWidget;
+class ColorSelectOverlay;
 
 
 class ColorPicker final : public Dialog {
 class ColorPicker final : public Dialog {
     C_OBJECT(ColorPicker)
     C_OBJECT(ColorPicker)
@@ -40,6 +41,7 @@ private:
     Vector<ColorButton&> m_color_widgets;
     Vector<ColorButton&> m_color_widgets;
     RefPtr<CustomColorWidget> m_custom_color;
     RefPtr<CustomColorWidget> m_custom_color;
     RefPtr<ColorPreview> m_preview_widget;
     RefPtr<ColorPreview> m_preview_widget;
+    RefPtr<Button> m_selector_button;
     RefPtr<TextBox> m_html_text;
     RefPtr<TextBox> m_html_text;
     RefPtr<SpinBox> m_red_spinbox;
     RefPtr<SpinBox> m_red_spinbox;
     RefPtr<SpinBox> m_green_spinbox;
     RefPtr<SpinBox> m_green_spinbox;