From 1b2aeae66566f8192e0922c1fa22a6579a60911d Mon Sep 17 00:00:00 2001 From: yggverse Date: Sun, 15 Dec 2024 13:56:30 +0200 Subject: [PATCH] implement search match highlight --- .../item/page/content/text/gemini/reader.rs | 52 ++++----------- .../page/content/text/gemini/reader/tag.rs | 6 -- .../content/text/gemini/reader/tag/found.rs | 19 ------ .../page/content/text/gemini/reader/widget.rs | 50 ++++++++------- .../content/text/gemini/reader/widget/find.rs | 63 +++++++++++++++---- 5 files changed, 91 insertions(+), 99 deletions(-) delete mode 100644 src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/found.rs diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs index ce8e480a..26ccf57f 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader.rs @@ -9,9 +9,8 @@ use syntax::Syntax; use tag::Tag; use widget::Widget; -use crate::app::browser::window::{ - action::Position, tab::item::Action as TabAction, Action as WindowAction, -}; +use super::{TabAction, WindowAction}; +use crate::app::browser::window::action::Position; use gemtext::line::{ code::{Inline, Multiline}, header::{Header, Level}, @@ -24,8 +23,8 @@ use gtk::{ gio::Cancellable, glib::{TimeZone, Uri}, prelude::{TextBufferExt, TextBufferExtManual, TextTagExt, TextViewExt, WidgetExt}, - EventControllerMotion, GestureClick, TextBuffer, TextSearchFlags, TextTag, TextWindowType, - UriLauncher, Window, WrapMode, + EventControllerMotion, GestureClick, TextBuffer, TextTag, TextWindowType, UriLauncher, Window, + WrapMode, }; use std::{cell::Cell, collections::HashMap, rc::Rc}; @@ -39,8 +38,6 @@ const LINK_COLOR_DEFAULT: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 255.0); const LINK_COLOR_ONHOVER: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 228.0); pub struct Reader { - buffer: TextBuffer, - tag: Tag, pub title: Option, pub widget: Rc, } @@ -50,7 +47,7 @@ impl Reader { pub fn new( gemtext: &str, base: &Uri, - actions: (Rc, Rc), + (window_action, tab_action): (Rc, Rc), ) -> Result { // Init default values let mut title = None; @@ -319,10 +316,11 @@ impl Reader { // Init widget let widget = Rc::new(Widget::new( + &window_action, &buffer, - primary_button_controller.clone(), - middle_button_controller.clone(), - motion_controller.clone(), + &primary_button_controller, + &middle_button_controller, + &motion_controller, )); // Init shared reference container for HashTable collected @@ -348,7 +346,7 @@ impl Reader { return match uri.scheme().as_str() { "gemini" => { // Open new page in browser - actions.1.load.activate(Some(&uri.to_str()), true); + tab_action.load.activate(Some(&uri.to_str()), true); } // Scheme not supported, delegate _ => UriLauncher::new(&uri.to_str()).launch( @@ -385,7 +383,7 @@ impl Reader { return match uri.scheme().as_str() { "gemini" => { // Open new page in browser - actions.0.append.activate_stateful_once( + window_action.append.activate_stateful_once( Position::After, Some(uri.to_string()), false, @@ -461,33 +459,7 @@ impl Reader { }); // @TODO may be expensive for CPU, add timeout? // Result - Ok(Self { - buffer, - tag, - title, - widget, - }) - } - - // Actions - - pub fn find(&self) { - self.buffer.remove_tag( - &self.tag.found.text_tag, - &self.buffer.start_iter(), - &self.buffer.end_iter(), - ); - - let mut next = self.buffer.start_iter(); - while let Some((start, end)) = next.forward_search( - "Gemini", - TextSearchFlags::CASE_INSENSITIVE, // @TODO - None, // unlimited - ) { - self.buffer - .apply_tag(&self.tag.found.text_tag, &start, &end); - next = end; - } + Ok(Self { title, widget }) } } diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs index 72b25287..72b99c29 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag.rs @@ -1,4 +1,3 @@ -mod found; mod h1; mod h2; mod h3; @@ -6,7 +5,6 @@ mod list; mod quote; mod title; -use found::Found; use h1::H1; use h2::H2; use h3::H3; @@ -25,7 +23,6 @@ pub struct Tag { pub list: List, pub quote: Quote, pub title: Title, - pub found: Found, } impl Tag { @@ -38,7 +35,6 @@ impl Tag { let list = List::new(); let quote = Quote::new(); let title = Title::new(); - let found = Found::new(); // Init tag table let text_tag_table = TextTagTable::new(); @@ -49,7 +45,6 @@ impl Tag { text_tag_table.add(&title.text_tag); text_tag_table.add(&list.text_tag); text_tag_table.add("e.text_tag); - text_tag_table.add(&found.text_tag); Self { text_tag_table, @@ -60,7 +55,6 @@ impl Tag { list, quote, title, - found, } } } diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/found.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/found.rs deleted file mode 100644 index fb578464..00000000 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/tag/found.rs +++ /dev/null @@ -1,19 +0,0 @@ -use gtk::{gdk::RGBA, TextTag, WrapMode}; - -pub struct Found { - pub text_tag: TextTag, -} - -impl Found { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - text_tag: TextTag::builder() - .background_rgba(&RGBA::new(0.502, 0.502, 0.502, 0.5)) // @TODO - .wrap_mode(WrapMode::Word) - .build(), - } - } -} diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget.rs index 460fc5cb..b9a1f041 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget.rs @@ -1,16 +1,17 @@ mod find; +use std::rc::Rc; use find::Find; +use super::WindowAction; use gtk::{ - prelude::{TextViewExt, WidgetExt}, + prelude::{ButtonExt, TextViewExt, WidgetExt}, EventControllerMotion, GestureClick, TextBuffer, TextView, TextWindowType, WrapMode, }; const MARGIN: i32 = 8; pub struct Widget { - find: Find, pub text_view: TextView, } @@ -19,13 +20,14 @@ impl Widget { /// Create new `Self` pub fn new( + action: &WindowAction, buffer: &TextBuffer, - primary_button_controller: GestureClick, - middle_button_controller: GestureClick, - motion_controller: EventControllerMotion, + primary_button_controller: &GestureClick, + middle_button_controller: &GestureClick, + motion_controller: &EventControllerMotion, ) -> Self { // Init components - let find = Find::new(); + let find = Rc::new(Find::new(buffer)); // Init main widget let text_view = TextView::builder() @@ -40,24 +42,26 @@ impl Widget { .wrap_mode(WrapMode::Word) .build(); - text_view.add_controller(primary_button_controller); - text_view.add_controller(middle_button_controller); - text_view.add_controller(motion_controller); + text_view.add_controller(primary_button_controller.clone()); + text_view.add_controller(middle_button_controller.clone()); + text_view.add_controller(motion_controller.clone()); + + // Connect events + action.find.connect_activate({ + let find = find.clone(); + let text_view = text_view.clone(); + move |_| { + text_view.set_gutter(TextWindowType::Bottom, Some(&find.g_box)); + find.entry.grab_focus(); + } + }); + + find.close.connect_clicked({ + let text_view = text_view.clone(); + move |_| text_view.set_gutter(TextWindowType::Bottom, gtk::Widget::NONE) + }); // Done - Self { find, text_view } - } - - // Actions - - pub fn find(&self, is_visible: bool) { - if is_visible { - self.text_view - .set_gutter(TextWindowType::Bottom, Some(&self.find.g_box)); - self.find.g_box.grab_focus(); - } else { - self.text_view - .set_gutter(TextWindowType::Bottom, gtk::Widget::NONE); - } + Self { text_view } } } diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget/find.rs b/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget/find.rs index 1a20a066..9eae050e 100644 --- a/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget/find.rs +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/widget/find.rs @@ -1,18 +1,20 @@ use gtk::{ - gdk::Cursor, - prelude::{BoxExt, EditableExt, EntryExt}, - Box, Button, Entry, EntryIconPosition, Orientation, + gdk::{Cursor, RGBA}, + prelude::{BoxExt, ButtonExt, EditableExt, EntryExt, TextBufferExt}, + Box, Button, Entry, EntryIconPosition, Orientation, TextBuffer, TextSearchFlags, TextTag, }; const MARGIN: i32 = 6; pub struct Find { + pub close: Button, + pub entry: Entry, pub g_box: Box, } impl Find { // Construct - pub fn new() -> Self { + pub fn new(text_buffer: &TextBuffer) -> Self { // Init components let close = Button::builder() .cursor(&Cursor::from_name("default", None).unwrap()) @@ -34,6 +36,11 @@ impl Find { .primary_icon_name("system-search-symbolic") .build(); + let text_tag = TextTag::builder() + .background_rgba(&RGBA::new(0.502, 0.502, 0.502, 0.5)) // @TODO + .build(); + text_buffer.tag_table().add(&text_tag); + // Init main container let g_box = Box::builder().orientation(Orientation::Horizontal).build(); @@ -41,13 +48,43 @@ impl Find { g_box.append(&close); // Connect events - entry.connect_activate(|_| {}); // @TODO + close.connect_clicked({ + let entry = entry.clone(); + move |_| entry.delete_text(0, -1) + }); - entry.connect_changed(move |this| { - if this.text().is_empty() { - this.set_secondary_icon_name(None); - } else { - this.set_secondary_icon_name(Some("edit-clear-symbolic")); + entry.connect_changed({ + let entry = entry.clone(); + let text_buffer = text_buffer.clone(); + let text_tag = text_tag.clone(); + move |this| { + // Toggle clear action + if this.text().is_empty() { + this.set_secondary_icon_name(None); + } else { + this.set_secondary_icon_name(Some("edit-clear-symbolic")); + } + + // Cleanup previous search results + text_buffer.remove_tag( + &text_tag, + &text_buffer.start_iter(), + &text_buffer.end_iter(), + ); + + // Get subject once + let query = entry.text(); + + // Begin search + let mut next = text_buffer.start_iter(); + while let Some((start, end)) = next.forward_search( + &query, + TextSearchFlags::CASE_INSENSITIVE, // @TODO + None, // unlimited + ) { + text_buffer.apply_tag(&text_tag, &start, &end); + next = end; + } } }); @@ -57,6 +94,10 @@ impl Find { }); // Done - Self { g_box } + Self { + close, + entry, + g_box, + } } }