summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGUI
diff options
context:
space:
mode:
authorthankyouverycool <66646555+thankyouverycool@users.noreply.github.com>2022-11-28 17:58:17 -0500
committerSam Atkins <atkinssj@gmail.com>2022-11-29 15:39:13 +0000
commit8231bd9bc3d725f8ba16508522c45e6d349a6a88 (patch)
tree8470fb96b1064b433fb0714110157a7a29b7c692 /Userland/Libraries/LibGUI
parent3c4a563415f053e1b51e52938c782137775f182b (diff)
downloadserenity-8231bd9bc3d725f8ba16508522c45e6d349a6a88.zip
LibGUI: Add IncrementalSearchBanner
Compared to traditional modal search, incremental search begins matching as soon as the user starts typing, highlighting results immediately. This refactors Itamar's work for HackStudio into a common LibGUI widget to be used in all multi-line TextEditors.
Diffstat (limited to 'Userland/Libraries/LibGUI')
-rw-r--r--Userland/Libraries/LibGUI/AbstractScrollableWidget.h2
-rw-r--r--Userland/Libraries/LibGUI/CMakeLists.txt3
-rw-r--r--Userland/Libraries/LibGUI/Forward.h1
-rw-r--r--Userland/Libraries/LibGUI/IncrementalSearchBanner.cpp136
-rw-r--r--Userland/Libraries/LibGUI/IncrementalSearchBanner.gml81
-rw-r--r--Userland/Libraries/LibGUI/IncrementalSearchBanner.h45
6 files changed, 267 insertions, 1 deletions
diff --git a/Userland/Libraries/LibGUI/AbstractScrollableWidget.h b/Userland/Libraries/LibGUI/AbstractScrollableWidget.h
index 7f16066093..6e33e2ceb4 100644
--- a/Userland/Libraries/LibGUI/AbstractScrollableWidget.h
+++ b/Userland/Libraries/LibGUI/AbstractScrollableWidget.h
@@ -56,6 +56,7 @@ public:
void scroll_to_top();
void scroll_to_bottom();
+ void update_scrollbar_ranges();
void set_automatic_scrolling_timer(bool active);
virtual Gfx::IntPoint automatic_scroll_delta_from_position(Gfx::IntPoint const&) const;
@@ -89,7 +90,6 @@ protected:
virtual void on_automatic_scrolling_timer_fired() {};
int autoscroll_threshold() const { return m_autoscroll_threshold; }
void update_scrollbar_visibility();
- void update_scrollbar_ranges();
private:
class AbstractScrollableWidgetScrollbar final : public Scrollbar {
diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt
index 65fd641497..ae8fa20e23 100644
--- a/Userland/Libraries/LibGUI/CMakeLists.txt
+++ b/Userland/Libraries/LibGUI/CMakeLists.txt
@@ -1,6 +1,7 @@
compile_gml(EmojiInputDialog.gml EmojiInputDialogGML.h emoji_input_dialog_gml)
compile_gml(FontPickerDialog.gml FontPickerDialogGML.h font_picker_dialog_gml)
compile_gml(FilePickerDialog.gml FilePickerDialogGML.h file_picker_dialog_gml)
+compile_gml(IncrementalSearchBanner.gml IncrementalSearchBannerGML.h incremental_search_banner_gml)
compile_gml(PasswordInputDialog.gml PasswordInputDialogGML.h password_input_dialog_gml)
set(SOURCES
@@ -57,6 +58,7 @@ set(SOURCES
Icon.cpp
IconView.cpp
ImageWidget.cpp
+ IncrementalSearchBanner.cpp
INILexer.cpp
INISyntaxHighlighter.cpp
InputBox.cpp
@@ -133,6 +135,7 @@ set(GENERATED_SOURCES
EmojiInputDialogGML.h
FilePickerDialogGML.h
FontPickerDialogGML.h
+ IncrementalSearchBannerGML.h
PasswordInputDialogGML.h
)
diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h
index 2a11e53283..0ad380d344 100644
--- a/Userland/Libraries/LibGUI/Forward.h
+++ b/Userland/Libraries/LibGUI/Forward.h
@@ -37,6 +37,7 @@ class HorizontalSlider;
class Icon;
class IconView;
class ImageWidget;
+class IncrementalSearchBanner;
class JsonArrayModel;
class KeyEvent;
class Label;
diff --git a/Userland/Libraries/LibGUI/IncrementalSearchBanner.cpp b/Userland/Libraries/LibGUI/IncrementalSearchBanner.cpp
new file mode 100644
index 0000000000..eeab9a573c
--- /dev/null
+++ b/Userland/Libraries/LibGUI/IncrementalSearchBanner.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2022, Itamar S. <itamar8910@gmail.com>
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGUI/Button.h>
+#include <LibGUI/IncrementalSearchBanner.h>
+#include <LibGUI/IncrementalSearchBannerGML.h>
+#include <LibGUI/Label.h>
+#include <LibGUI/Layout.h>
+#include <LibGUI/Painter.h>
+#include <LibGUI/TextBox.h>
+#include <LibGfx/Palette.h>
+
+namespace GUI {
+
+IncrementalSearchBanner::IncrementalSearchBanner(TextEditor& editor)
+ : m_editor(editor)
+{
+ load_from_gml(incremental_search_banner_gml);
+ m_index_label = find_descendant_of_type_named<Label>("index_label");
+
+ m_wrap_search_button = find_descendant_of_type_named<Button>("wrap_search_button");
+ m_wrap_search_button->on_checked = [this](auto is_checked) {
+ m_wrap_search = is_checked
+ ? TextDocument::SearchShouldWrap::Yes
+ : TextDocument::SearchShouldWrap::No;
+ };
+
+ m_match_case_button = find_descendant_of_type_named<Button>("match_case_button");
+ m_match_case_button->on_checked = [this](auto is_checked) {
+ m_match_case = is_checked;
+ m_editor->reset_search_results();
+ search(TextEditor::SearchDirection::Forward);
+ };
+
+ m_close_button = find_descendant_of_type_named<Button>("close_button");
+ m_close_button->set_text("\xE2\x9D\x8C");
+ m_close_button->on_click = [this](auto) {
+ hide();
+ };
+
+ m_next_button = find_descendant_of_type_named<Button>("next_button");
+ m_next_button->on_click = [this](auto) {
+ search(TextEditor::SearchDirection::Forward);
+ };
+
+ m_previous_button = find_descendant_of_type_named<Button>("previous_button");
+ m_previous_button->on_click = [this](auto) {
+ search(TextEditor::SearchDirection::Backward);
+ };
+
+ m_search_textbox = find_descendant_of_type_named<TextBox>("search_textbox");
+ m_search_textbox->on_change = [this]() {
+ m_editor->reset_search_results();
+ search(TextEditor::SearchDirection::Forward);
+ };
+
+ m_search_textbox->on_return_pressed = [this]() {
+ search(TextEditor::SearchDirection::Forward);
+ };
+
+ m_search_textbox->on_shift_return_pressed = [this]() {
+ search(TextEditor::SearchDirection::Backward);
+ };
+
+ m_search_textbox->on_escape_pressed = [this]() {
+ hide();
+ };
+}
+
+void IncrementalSearchBanner::show()
+{
+ set_visible(true);
+ m_editor->do_layout();
+ m_editor->update_scrollbar_ranges();
+ m_search_textbox->set_focus(true);
+}
+
+void IncrementalSearchBanner::hide()
+{
+ set_visible(false);
+ m_editor->do_layout();
+ m_editor->update_scrollbar_ranges();
+ m_editor->reset_search_results();
+ m_editor->set_focus(true);
+}
+
+void IncrementalSearchBanner::search(TextEditor::SearchDirection direction)
+{
+ auto needle = m_search_textbox->text();
+ if (needle.is_empty()) {
+ m_editor->reset_search_results();
+ m_index_label->set_text(String::empty());
+ return;
+ }
+
+ auto index = m_editor->search_result_index().value_or(0) + 1;
+ if (m_wrap_search == TextDocument::SearchShouldWrap::No) {
+ auto forward = direction == TextEditor::SearchDirection::Forward;
+ if ((index == m_editor->search_results().size() && forward) || (index == 1 && !forward))
+ return;
+ }
+
+ auto result = m_editor->find_text(needle, direction, m_wrap_search, false, m_match_case);
+ index = m_editor->search_result_index().value_or(0) + 1;
+ if (result.is_valid())
+ m_index_label->set_text(String::formatted("{} of {}", index, m_editor->search_results().size()));
+ else
+ m_index_label->set_text(String::empty());
+}
+
+void IncrementalSearchBanner::paint_event(PaintEvent& event)
+{
+ Widget::paint_event(event);
+
+ Painter painter(*this);
+ painter.add_clip_rect(event.rect());
+ painter.draw_line({ 0, rect().bottom() - 1 }, { width(), rect().bottom() - 1 }, palette().threed_shadow1());
+ painter.draw_line({ 0, rect().bottom() }, { width(), rect().bottom() }, palette().threed_shadow2());
+}
+
+Optional<UISize> IncrementalSearchBanner::calculated_min_size() const
+{
+ auto textbox_width = m_search_textbox->effective_min_size().width().as_int();
+ auto textbox_height = m_search_textbox->effective_min_size().height().as_int();
+ auto button_width = m_next_button->effective_min_size().width().as_int();
+ VERIFY(layout());
+ auto margins = layout()->margins();
+ auto spacing = layout()->spacing();
+ return { { margins.left() + textbox_width + spacing + button_width * 2 + margins.right(), textbox_height + margins.top() + margins.bottom() } };
+}
+
+}
diff --git a/Userland/Libraries/LibGUI/IncrementalSearchBanner.gml b/Userland/Libraries/LibGUI/IncrementalSearchBanner.gml
new file mode 100644
index 0000000000..7019d44edd
--- /dev/null
+++ b/Userland/Libraries/LibGUI/IncrementalSearchBanner.gml
@@ -0,0 +1,81 @@
+@GUI::Widget {
+ fill_with_background_color: true
+ visible: false
+ layout: @GUI::HorizontalBoxLayout {
+ margins: [4]
+ }
+
+ @GUI::TextBox {
+ name: "search_textbox"
+ max_width: 250
+ preferred_width: "grow"
+ placeholder: "Find"
+ }
+
+ @GUI::Widget {
+ preferred_width: "shrink"
+ layout: @GUI::HorizontalBoxLayout {
+ spacing: 0
+ }
+
+ @GUI::Button {
+ name: "next_button"
+ icon: "/res/icons/16x16/go-down.png"
+ fixed_width: 18
+ button_style: "Coolbar"
+ focus_policy: "NoFocus"
+ }
+
+ @GUI::Button {
+ name: "previous_button"
+ icon: "/res/icons/16x16/go-up.png"
+ fixed_width: 18
+ button_style: "Coolbar"
+ focus_policy: "NoFocus"
+ }
+ }
+
+ @GUI::Label {
+ name: "index_label"
+ text_alignment: "CenterLeft"
+ }
+
+ @GUI::Layout::Spacer {}
+
+ @GUI::Widget {
+ preferred_width: "shrink"
+ layout: @GUI::HorizontalBoxLayout {
+ spacing: 0
+ }
+
+ @GUI::Button {
+ name: "wrap_search_button"
+ fixed_width: 24
+ icon: "/res/icons/16x16/reload.png"
+ tooltip: "Wrap Search"
+ checkable: true
+ checked: true
+ button_style: "Coolbar"
+ focus_policy: "NoFocus"
+ }
+
+ @GUI::Button {
+ name: "match_case_button"
+ fixed_width: 24
+ icon: "/res/icons/16x16/app-font-editor.png"
+ tooltip: "Match Case"
+ checkable: true
+ button_style: "Coolbar"
+ focus_policy: "NoFocus"
+ }
+ }
+
+ @GUI::VerticalSeparator {}
+
+ @GUI::Button {
+ name: "close_button"
+ fixed_size: [15, 16]
+ button_style: "Coolbar"
+ focus_policy: "NoFocus"
+ }
+}
diff --git a/Userland/Libraries/LibGUI/IncrementalSearchBanner.h b/Userland/Libraries/LibGUI/IncrementalSearchBanner.h
new file mode 100644
index 0000000000..5b6e80a269
--- /dev/null
+++ b/Userland/Libraries/LibGUI/IncrementalSearchBanner.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022, the SerenityOS developers.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibGUI/TextEditor.h>
+#include <LibGUI/Widget.h>
+
+namespace GUI {
+
+class IncrementalSearchBanner final : public Widget {
+ C_OBJECT(IncrementalSearchBanner);
+
+public:
+ virtual ~IncrementalSearchBanner() override = default;
+
+ void show();
+ void hide();
+
+protected:
+ explicit IncrementalSearchBanner(TextEditor&);
+
+ virtual void paint_event(PaintEvent&) override;
+ virtual Optional<UISize> calculated_min_size() const override;
+
+private:
+ void search(TextEditor::SearchDirection);
+
+ NonnullRefPtr<TextEditor> m_editor;
+ RefPtr<Button> m_close_button;
+ RefPtr<Button> m_next_button;
+ RefPtr<Button> m_previous_button;
+ RefPtr<Button> m_wrap_search_button;
+ RefPtr<Button> m_match_case_button;
+ RefPtr<Label> m_index_label;
+ RefPtr<TextBox> m_search_textbox;
+
+ TextDocument::SearchShouldWrap m_wrap_search { true };
+ bool m_match_case { false };
+};
+
+}