diff --git a/src/app/browser.rs b/src/app/browser.rs index 65eb2cbd..9614e972 100644 --- a/src/app/browser.rs +++ b/src/app/browser.rs @@ -71,7 +71,9 @@ impl Browser { action.escape.connect_activate({ let widget = widget.clone(); + let window = window.clone(); move || { + window.tab.escape(None); // current tab widget.application_window.set_focus(gtk::Window::NONE); } }); diff --git a/src/app/browser/window.rs b/src/app/browser/window.rs index 7db8fcd7..32ac47ef 100644 --- a/src/app/browser/window.rs +++ b/src/app/browser/window.rs @@ -81,6 +81,11 @@ impl Window { move |_| tab.close_all() }); + action.find.connect_activate({ + let tab = tab.clone(); + move |position| tab.find(position) + }); + action.save_as.connect_activate({ let tab = tab.clone(); move |position| tab.save_as(position) diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index ad33550e..4ffcf480 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -166,9 +166,9 @@ impl Tab { item } - /// Close page at given `position`, `None` to close selected page (if available) - pub fn close(&self, position: Option) { - self.widget.close(position); + /// Close page at given `page_position`, `None` to close selected page (if available) + pub fn close(&self, page_position: Option) { + self.widget.close(page_position); } // Close all pages @@ -176,6 +176,20 @@ impl Tab { self.widget.close_all(); } + // Toggle search widget + pub fn escape(&self, page_position: Option) { + if let Some(item) = self.item(page_position) { + item.page.escape(); + } + } + + // Toggle search widget + pub fn find(&self, page_position: Option) { + if let Some(item) = self.item(page_position) { + item.page.find(); + } + } + // Save page at given `position`, `None` to save selected page (if available) pub fn save_as(&self, page_position: Option) { if let Some(item) = self.item(page_position) { diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 8a5e1496..c7904eea 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -6,6 +6,7 @@ mod input; mod meta; mod navigation; mod request; +mod search; mod widget; use client::Client; @@ -15,6 +16,7 @@ use input::Input; use meta::{Meta, Status}; use navigation::Navigation; use request::Request; +use search::Search; use widget::Widget; use crate::app::browser::{ @@ -42,6 +44,7 @@ pub struct Page { // Components pub client: Rc, pub content: Rc, + pub search: Rc, pub input: Rc, pub meta: Rc, pub navigation: Rc, @@ -67,6 +70,8 @@ impl Page { tab_action.clone(), ))); + let search = Rc::new(Search::new()); + let navigation = Rc::new(Navigation::new( profile.clone(), ( @@ -82,6 +87,7 @@ impl Page { &id, &navigation.widget.g_box, &content.g_box, + &search.g_box, &input.widget.clamp, )); @@ -98,9 +104,10 @@ impl Page { // Components client: Rc::new(Client::new()), content, - navigation, + search, input, meta, + navigation, widget, } } @@ -122,6 +129,16 @@ impl Page { result } + /// Request `Escape` action for child components + pub fn escape(&self) { + self.search.hide() + } + + /// Toggle `Find` widget + pub fn find(&self) { + self.search.toggle() + } + /// Navigate home URL (parsed from current navigation entry) /// * this method create new history record in memory as defined in `action_page_open` action pub fn home(&self) { @@ -169,6 +186,7 @@ impl Page { self.window_action.find.simple_action.set_enabled(false); // Reset widgets + self.search.update(None); self.input.unset(); // Prevent infinitive redirection @@ -372,21 +390,22 @@ impl Page { use gemini::client::connection::response; // Init shared clones + let browser_action = self.browser_action.clone(); let cancellable = self.client.cancellable(); let content = self.content.clone(); - let find = self.window_action.find.clone(); + let search = self.search.clone(); let id = self.id.clone(); let input = self.input.clone(); let meta = self.meta.clone(); let navigation = self.navigation.clone(); let tab_action = self.tab_action.clone(); - let update = self.browser_action.update.clone(); + let window_action = self.window_action.clone(); // Listen for connection status updates self.client.gemini.socket.connect_event({ let id = id.clone(); let meta = meta.clone(); - let update = update.clone(); + let update = browser_action.update.clone(); move |_, event, _, _| { meta.set_status(match event { SocketClientEvent::Resolving => Status::Resolving, @@ -455,7 +474,7 @@ impl Page { .set_title(&title); // Update page - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); }, // https://geminiprotocol.net/docs/protocol-specification.gmi#status-20 response::meta::Status::Success => { @@ -522,7 +541,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); } else { // browse match response.meta.mime.unwrap().value.to_lowercase().as_str() { "text/gemini" => { @@ -532,18 +551,19 @@ impl Page { Priority::DEFAULT, cancellable.clone(), { + let browser_action = browser_action.clone(); let content = content.clone(); - let find = find.clone(); + let search = search.clone(); let id = id.clone(); let meta = meta.clone(); - let update = update.clone(); let uri = uri.clone(); + let window_action = window_action.clone(); move |result|{ match result { Ok(buffer) => { // Set children component, // extract title from meta parsed - let text = if is_source { + let text_widget = if is_source { content.to_text_source( &buffer.data ) @@ -554,16 +574,20 @@ impl Page { ) }; + // Update `find` model with new buffer + search.update(Some(text_widget.buffer)); + // Update page meta meta.set_status(Status::Success) - .set_title(&match text.meta.title { + .set_title(&match text_widget.meta.title { Some(meta_title) => meta_title, None => uri_to_title(&uri) }); // Update window components - find.simple_action.set_enabled(text.has_search); - update.activate(Some(&id)); + window_action.find.simple_action.set_enabled(true); + + browser_action.update.activate(Some(&id)); } Err(e) => { // Update widget @@ -575,7 +599,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); }, } } @@ -603,11 +627,11 @@ impl Page { ); }, { + let browser_action = browser_action.clone(); let cancellable = cancellable.clone(); let content = content.clone(); let id = id.clone(); let meta = meta.clone(); - let update = update.clone(); let uri = uri.clone(); move |result| match result { Ok((memory_input_stream, _)) => { @@ -626,7 +650,7 @@ impl Page { content.to_image(&Texture::for_pixbuf(&buffer)); // Update window components - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); } Err(e) => { // Update widget @@ -666,7 +690,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); }, } } @@ -773,7 +797,7 @@ impl Page { }, } - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); }, // https://geminiprotocol.net/docs/protocol-specification.gmi#status-60 response::meta::Status::CertificateRequest | @@ -803,7 +827,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); } _ => { // Add history record @@ -824,7 +848,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); } } }, @@ -843,7 +867,7 @@ impl Page { .set_title(&status.title()); // Update window - update.activate(Some(&id)); + browser_action.update.activate(Some(&id)); } } ); diff --git a/src/app/browser/window/tab/item/page/content/text.rs b/src/app/browser/window/tab/item/page/content/text.rs index 6fe923b2..7240638d 100644 --- a/src/app/browser/window/tab/item/page/content/text.rs +++ b/src/app/browser/window/tab/item/page/content/text.rs @@ -1,17 +1,14 @@ mod gemini; -mod search; mod source; use gemini::Gemini; -use search::Search; use source::Source; use super::{BrowserAction, TabAction, WindowAction}; -use adw::Clamp; use gtk::{ glib::Uri, - prelude::{BoxExt, ButtonExt, TextViewExt, WidgetExt}, - Box, Orientation, ScrolledWindow, + prelude::{BoxExt, TextViewExt}, + Box, Orientation, ScrolledWindow, TextBuffer, }; use std::rc::Rc; @@ -20,8 +17,8 @@ pub struct Meta { } // @TODO move to separated mod pub struct Text { + pub buffer: TextBuffer, pub g_box: Box, - pub has_search: bool, pub meta: Meta, } @@ -39,7 +36,6 @@ impl Text { ) -> Self { // Init components let gemini = Gemini::new(gemtext, base, (window_action, tab_action)); - let search = Rc::new(Search::new(&gemini.reader.buffer)); // Init main widget let g_box = Box::builder().orientation(Orientation::Vertical).build(); @@ -50,15 +46,8 @@ impl Text { .build(), ); - g_box.append( - &Clamp::builder() - .child(&search.g_box) - .css_classes(["osd"]) - .maximum_size(800) - .build(), - ); - // Connect events + /* @TODO browser_action.escape.connect_activate({ let close = search.close.clone(); move || { @@ -99,29 +88,28 @@ impl Text { move |_| { search.g_box.set_visible(false); } - }); + });*/ Self { + buffer: gemini.reader.widget.text_view.buffer(), meta: Meta { title: gemini.reader.title.clone(), }, - has_search: true, g_box, } } pub fn new_source(data: &str) -> Self { + // Init components + let source = Source::new(data); + let g_box = Box::builder().orientation(Orientation::Vertical).build(); - g_box.append( - &ScrolledWindow::builder() - .child(&Source::new(data).text_view) - .build(), - ); + g_box.append(&ScrolledWindow::builder().child(&source.text_view).build()); Self { + buffer: source.text_view.buffer(), meta: Meta { title: None }, - has_search: false, g_box, } } 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 3900d6a7..ed72172e 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 @@ -38,7 +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 { - pub buffer: TextBuffer, pub title: Option, pub widget: Rc, } @@ -459,11 +458,7 @@ impl Reader { }); // @TODO may be expensive for CPU, add timeout? // Result - Ok(Self { - buffer, - title, - widget, - }) + Ok(Self { title, widget }) } } diff --git a/src/app/browser/window/tab/item/page/content/text/search.rs b/src/app/browser/window/tab/item/page/content/text/search.rs deleted file mode 100644 index 60e8df3b..00000000 --- a/src/app/browser/window/tab/item/page/content/text/search.rs +++ /dev/null @@ -1,130 +0,0 @@ -mod close; -mod input; -mod match_case; -mod navigation; -mod tag; - -use input::Input; -use navigation::Navigation; -use tag::Tag; - -use gtk::{ - prelude::{BoxExt, ButtonExt, CheckButtonExt, EditableExt, TextBufferExt}, - Align, Box, Button, Orientation, TextBuffer, TextIter, TextSearchFlags, -}; -use std::rc::Rc; - -pub struct Search { - pub close: Button, - pub g_box: Box, - pub input: Rc, - pub navigation: Rc, -} - -impl Search { - // Construct - pub fn new(buffer: &TextBuffer) -> Self { - // Init components - let close = close::new(); - let input = Rc::new(Input::new()); - let match_case = match_case::new(); - let tag = Rc::new(Tag::new(buffer.tag_table())); - let navigation = Rc::new(Navigation::new(buffer.clone(), tag.current.clone())); - - // Init main container - let g_box = Box::builder() - .orientation(Orientation::Horizontal) - .valign(Align::Center) - .vexpand(false) - .visible(false) - .build(); - - g_box.append(&input.entry); - g_box.append(&navigation.g_box); - g_box.append(&match_case); - g_box.append(&close); - - // Connect events - close.connect_clicked({ - let input = input.clone(); - move |_| input.clean() - }); - - input.entry.connect_changed({ - let input = input.clone(); - let match_case = match_case.clone(); - let navigation = navigation.clone(); - let tag = tag.clone(); - let buffer = buffer.clone(); - move |_| { - navigation.update(find( - &buffer, - &tag, - input.entry.text().as_str(), - match_case.is_active(), - )); - input.update(navigation.is_match()); - } - }); - - match_case.connect_toggled({ - let input = input.clone(); - let navigation = navigation.clone(); - let tag = tag.clone(); - let buffer = buffer.clone(); - move |this| { - navigation.update(find( - &buffer, - &tag, - input.entry.text().as_str(), - this.is_active(), - )); - input.update(navigation.is_match()); - } - }); - - // Done - Self { - close, - g_box, - input, - navigation, - } - } -} - -// Tools - -fn find( - buffer: &TextBuffer, - tag: &Rc, - subject: &str, - is_match_case: bool, -) -> Vec<(TextIter, TextIter)> { - // Init matches holder - let mut result = Vec::new(); - - // Get iters - let buffer_start = buffer.start_iter(); - let buffer_end = buffer.end_iter(); - - // Cleanup previous search highlights - buffer.remove_tag(&tag.current, &buffer_start, &buffer_end); - buffer.remove_tag(&tag.found, &buffer_start, &buffer_end); - - // Begin new search - let mut next = buffer_start; - while let Some((match_start, match_end)) = next.forward_search( - subject, - match is_match_case { - true => TextSearchFlags::TEXT_ONLY, - false => TextSearchFlags::CASE_INSENSITIVE, - }, - None, // unlimited - ) { - buffer.apply_tag(&tag.found, &match_start, &match_end); - next = match_end; - result.push((match_start, match_end)); - } - result -} diff --git a/src/app/browser/window/tab/item/page/content/text/search/tag.rs b/src/app/browser/window/tab/item/page/content/text/search/tag.rs deleted file mode 100644 index bf1bf700..00000000 --- a/src/app/browser/window/tab/item/page/content/text/search/tag.rs +++ /dev/null @@ -1,26 +0,0 @@ -mod current; -mod found; - -use gtk::{TextTag, TextTagTable}; - -pub struct Tag { - pub current: TextTag, - pub found: TextTag, -} - -impl Tag { - // Constructors - - pub fn new(tag_table: TextTagTable) -> Self { - // Init components - let current = current::new(); - let found = found::new(); - - // Init `Self` - tag_table.add(&found); - tag_table.add(¤t); // keep current priority as `current` should overwrite `found` - // https://docs.gtk.org/gtk4/method.TextTag.set_priority.html - - Self { current, found } - } -} diff --git a/src/app/browser/window/tab/item/page/content/text/search/tag/found.rs b/src/app/browser/window/tab/item/page/content/text/search/tag/found.rs deleted file mode 100644 index 1b2f6f42..00000000 --- a/src/app/browser/window/tab/item/page/content/text/search/tag/found.rs +++ /dev/null @@ -1,7 +0,0 @@ -use gtk::{gdk::RGBA, TextTag}; - -pub fn new() -> TextTag { - TextTag::builder() - .background_rgba(&RGBA::new(0.502, 0.502, 0.502, 0.5)) // @TODO - .build() -} diff --git a/src/app/browser/window/tab/item/page/search.rs b/src/app/browser/window/tab/item/page/search.rs new file mode 100644 index 00000000..ad0393c1 --- /dev/null +++ b/src/app/browser/window/tab/item/page/search.rs @@ -0,0 +1,83 @@ +mod buffer; +mod form; +mod placeholder; + +use buffer::Buffer; +use form::Form; +use placeholder::Placeholder; + +use gtk::{ + prelude::{BoxExt, WidgetExt}, + Align, Box, Orientation, TextBuffer, +}; +use std::{cell::RefCell, rc::Rc}; + +pub struct Search { + buffer: Rc>>, + pub form: Rc
, + pub placeholder: Rc, + pub g_box: Box, +} + +impl Search { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + // Init components + let buffer = Rc::new(RefCell::new(None)); + let form = Rc::new(Form::new(&buffer)); + let placeholder = Rc::new(Placeholder::new()); + + // Init main container + let g_box = Box::builder() + .orientation(Orientation::Vertical) + .valign(Align::Center) + .vexpand(false) + .visible(false) + .build(); + + g_box.append(&form.g_box); + g_box.append(&placeholder.label); + + // Done + Self { + buffer, + form, + g_box, + placeholder, + } + } + + // Actions + + pub fn show(&self) { + if self.buffer.borrow().is_some() { + self.form.show(); + self.placeholder.hide(); + } else { + self.form.hide(); + self.placeholder.show(); + } + self.g_box.set_visible(true) + } + + pub fn hide(&self) { + self.g_box.set_visible(false) + } + + pub fn toggle(&self) { + if self.g_box.is_visible() { + self.hide() + } else { + self.show() + } + } + + pub fn update(&self, text_buffer: Option) { + self.buffer.replace(match text_buffer { + Some(buffer) => Some(Buffer::new(buffer)), + None => None, + }); + } +} diff --git a/src/app/browser/window/tab/item/page/search/buffer.rs b/src/app/browser/window/tab/item/page/search/buffer.rs new file mode 100644 index 00000000..626de604 --- /dev/null +++ b/src/app/browser/window/tab/item/page/search/buffer.rs @@ -0,0 +1,25 @@ +mod tag; + +use tag::Tag; + +use gtk::{prelude::TextBufferExt, TextBuffer}; + +pub struct Buffer { + pub text_buffer: TextBuffer, + pub tag: Tag, +} + +impl Buffer { + // Constructors + + /// Create new `Self` + pub fn new(text_buffer: TextBuffer) -> Self { + // Init components + // * create new tag objects required for new buffer, + // instead of re-use existing refs (maybe the bug) + let tag = Tag::new(text_buffer.tag_table()); + + // Init `Self` + Self { text_buffer, tag } + } +} diff --git a/src/app/browser/window/tab/item/page/search/buffer/tag.rs b/src/app/browser/window/tab/item/page/search/buffer/tag.rs new file mode 100644 index 00000000..184abea7 --- /dev/null +++ b/src/app/browser/window/tab/item/page/search/buffer/tag.rs @@ -0,0 +1,32 @@ +mod current; +mod found; + +use gtk::{TextTag, TextTagTable}; + +pub struct Tag { + pub current: TextTag, + pub found: TextTag, +} + +impl Tag { + // Constructors + + /// Create new `Self` + pub fn new(table: TextTagTable) -> Self { + // Init components + let current = current::new(); + let found = found::new(); + + // Init tag table + // keep order as `current` should overwrite `found` tag style + // https://docs.gtk.org/gtk4/method.TextTag.set_priority.html + for &tag in &[¤t, &found] { + if !table.add(tag) { + todo!() + } + } + + // Init `Self` + Self { current, found } + } +} diff --git a/src/app/browser/window/tab/item/page/content/text/search/tag/current.rs b/src/app/browser/window/tab/item/page/search/buffer/tag/current.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/tag/current.rs rename to src/app/browser/window/tab/item/page/search/buffer/tag/current.rs diff --git a/src/app/browser/window/tab/item/page/search/buffer/tag/found.rs b/src/app/browser/window/tab/item/page/search/buffer/tag/found.rs new file mode 100644 index 00000000..4d7fc5cc --- /dev/null +++ b/src/app/browser/window/tab/item/page/search/buffer/tag/found.rs @@ -0,0 +1,7 @@ +use gtk::{gdk::RGBA, TextTag}; + +pub fn new() -> TextTag { + TextTag::builder() + .background_rgba(&RGBA::new(0.5, 0.5, 0.5, 0.5)) // @TODO use accent colors after adw 1.6 update + .build() +} diff --git a/src/app/browser/window/tab/item/page/search/form.rs b/src/app/browser/window/tab/item/page/search/form.rs new file mode 100644 index 00000000..e3715dd9 --- /dev/null +++ b/src/app/browser/window/tab/item/page/search/form.rs @@ -0,0 +1,144 @@ +mod close; +mod input; +mod match_case; +mod navigation; + +use super::Buffer; +use input::Input; +use navigation::Navigation; + +use gtk::{ + prelude::{BoxExt, ButtonExt, CheckButtonExt, EditableExt, TextBufferExt, WidgetExt}, + Align, Box, Orientation, TextIter, TextSearchFlags, +}; +use std::{cell::RefCell, rc::Rc}; + +pub struct Form { + pub g_box: Box, +} + +impl Form { + // Constructors + + /// Create new `Self` + pub fn new(buffer: &Rc>>) -> Self { + // Init components + let close = close::new(); + let input = Rc::new(Input::new()); + let match_case = match_case::new(); + let navigation = Rc::new(Navigation::new()); + + // Init main container + let g_box = Box::builder() + .orientation(Orientation::Horizontal) + .valign(Align::Center) + .vexpand(false) + .visible(false) + .build(); + + g_box.append(&input.entry); + g_box.append(&navigation.g_box); + g_box.append(&match_case); + g_box.append(&close); + + // Connect events + close.connect_clicked({ + let input = input.clone(); + move |_| input.clean() + }); + + input.entry.connect_changed({ + let input = input.clone(); + let match_case = match_case.clone(); + let navigation = navigation.clone(); + let buffer = buffer.clone(); + move |_| { + navigation.update(find( + &buffer, + input.entry.text().as_str(), + match_case.is_active(), + )); + input.update(navigation.is_match()); + } + }); + + match_case.connect_toggled({ + let input = input.clone(); + let navigation = navigation.clone(); + let buffer = buffer.clone(); + move |this| { + navigation.update(find(&buffer, input.entry.text().as_str(), this.is_active())); + input.update(navigation.is_match()); + } + }); + + // Done + Self { g_box } + } + + // Actions + + pub fn show(&self) { + //self.buffer.get_mut().is_none() + self.g_box.set_visible(true) + } + + pub fn hide(&self) { + self.g_box.set_visible(false) + } + + pub fn toggle(&self) { + if self.g_box.is_visible() { + self.hide() + } else { + self.show() + } + } +} + +// Tools + +fn find( + buffer: &Rc>>, + subject: &str, + is_match_case: bool, +) -> Vec<(TextIter, TextIter)> { + // Init matches holder + let mut result = Vec::new(); + + // Borrow buffer + match buffer.borrow().as_ref() { + Some(buffer) => { + // Get iters + let buffer_start = buffer.text_buffer.start_iter(); + let buffer_end = buffer.text_buffer.end_iter(); + + // Cleanup previous search highlights + buffer + .text_buffer + .remove_tag(&buffer.tag.current, &buffer_start, &buffer_end); + buffer + .text_buffer + .remove_tag(&buffer.tag.found, &buffer_start, &buffer_end); + + // Begin new search + let mut next = buffer_start; + while let Some((match_start, match_end)) = next.forward_search( + subject, + match is_match_case { + true => TextSearchFlags::TEXT_ONLY, + false => TextSearchFlags::CASE_INSENSITIVE, + }, + None, // unlimited + ) { + buffer + .text_buffer + .apply_tag(&buffer.tag.found, &match_start, &match_end); + next = match_end; + result.push((match_start, match_end)); + } + result + } + None => todo!(), // unexpected + } +} diff --git a/src/app/browser/window/tab/item/page/content/text/search/close.rs b/src/app/browser/window/tab/item/page/search/form/close.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/close.rs rename to src/app/browser/window/tab/item/page/search/form/close.rs diff --git a/src/app/browser/window/tab/item/page/content/text/search/input.rs b/src/app/browser/window/tab/item/page/search/form/input.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/input.rs rename to src/app/browser/window/tab/item/page/search/form/input.rs diff --git a/src/app/browser/window/tab/item/page/content/text/search/match_case.rs b/src/app/browser/window/tab/item/page/search/form/match_case.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/match_case.rs rename to src/app/browser/window/tab/item/page/search/form/match_case.rs diff --git a/src/app/browser/window/tab/item/page/content/text/search/navigation.rs b/src/app/browser/window/tab/item/page/search/form/navigation.rs similarity index 51% rename from src/app/browser/window/tab/item/page/content/text/search/navigation.rs rename to src/app/browser/window/tab/item/page/search/form/navigation.rs index 78e8f499..7b57248b 100644 --- a/src/app/browser/window/tab/item/page/content/text/search/navigation.rs +++ b/src/app/browser/window/tab/item/page/search/form/navigation.rs @@ -21,15 +21,13 @@ pub struct Navigation { pub g_box: Box, index: Rc>, matches: Rc>>, - text_buffer: TextBuffer, - current_tag: TextTag, } impl Navigation { // Constructors /// Create new `Self` - pub fn new(text_buffer: TextBuffer, current_tag: TextTag) -> Self { + pub fn new() -> Self { // Init shared matches holder let index = Rc::new(Cell::new(0)); let matches = Rc::new(RefCell::new(Vec::new())); @@ -56,8 +54,6 @@ impl Navigation { g_box, index, matches, - text_buffer, - current_tag, } } @@ -72,55 +68,55 @@ impl Navigation { self.back.update(self.is_match()); self.forward.update(self.is_match()); } + /* + pub fn back(&self) -> Option<(TextIter, TextIter)> { + self.text_buffer.remove_tag( + &self.current_tag, + &self.text_buffer.start_iter(), + &self.text_buffer.end_iter(), + ); - pub fn back(&self) -> Option<(TextIter, TextIter)> { - self.text_buffer.remove_tag( - &self.current_tag, - &self.text_buffer.start_iter(), - &self.text_buffer.end_iter(), - ); + let index = self.index.take(); + match self.matches.borrow().get(back(index)) { + Some((start, end)) => { + self.text_buffer.apply_tag(&self.current_tag, start, end); + self.index.replace(if index == 0 { + len_to_index(self.matches.borrow().len()) + } else { + index + }); + Some((*start, *end)) + } + None => { + self.index + .replace(len_to_index(self.matches.borrow().len())); // go last + None + } + } + } - let index = self.index.take(); - match self.matches.borrow().get(back(index)) { - Some((start, end)) => { - self.text_buffer.apply_tag(&self.current_tag, start, end); - self.index.replace(if index == 0 { - len_to_index(self.matches.borrow().len()) - } else { - index - }); - Some((*start, *end)) - } - None => { - self.index - .replace(len_to_index(self.matches.borrow().len())); // go last - None - } - } - } - - pub fn forward(&self) -> Option<(TextIter, TextIter)> { - self.text_buffer.remove_tag( - &self.current_tag, - &self.text_buffer.start_iter(), - &self.text_buffer.end_iter(), - ); - - let index = self.index.take(); - let next = forward(index); - match self.matches.borrow().get(next) { - Some((start, end)) => { - self.text_buffer.apply_tag(&self.current_tag, start, end); - self.index.replace(next); - Some((*start, *end)) - } - None => { - self.index.replace(0); - None - } - } - } + pub fn forward(&self) -> Option<(TextIter, TextIter)> { + self.text_buffer.remove_tag( + &self.current_tag, + &self.text_buffer.start_iter(), + &self.text_buffer.end_iter(), + ); + let index = self.index.take(); + let next = forward(index); + match self.matches.borrow().get(next) { + Some((start, end)) => { + self.text_buffer.apply_tag(&self.current_tag, start, end); + self.index.replace(next); + Some((*start, *end)) + } + None => { + self.index.replace(0); + None + } + } + } + */ // Getters pub fn is_match(&self) -> bool { diff --git a/src/app/browser/window/tab/item/page/content/text/search/navigation/back.rs b/src/app/browser/window/tab/item/page/search/form/navigation/back.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/navigation/back.rs rename to src/app/browser/window/tab/item/page/search/form/navigation/back.rs diff --git a/src/app/browser/window/tab/item/page/content/text/search/navigation/forward.rs b/src/app/browser/window/tab/item/page/search/form/navigation/forward.rs similarity index 100% rename from src/app/browser/window/tab/item/page/content/text/search/navigation/forward.rs rename to src/app/browser/window/tab/item/page/search/form/navigation/forward.rs diff --git a/src/app/browser/window/tab/item/page/search/placeholder.rs b/src/app/browser/window/tab/item/page/search/placeholder.rs new file mode 100644 index 00000000..4ab78a4b --- /dev/null +++ b/src/app/browser/window/tab/item/page/search/placeholder.rs @@ -0,0 +1,29 @@ +use gtk::{prelude::WidgetExt, Label}; + +pub struct Placeholder { + pub label: Label, +} + +impl Placeholder { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self { + label: Label::builder() + .css_classes(["error"]) + .label("Search action requires activation!") + .build(), + } + } + + // Actions + + pub fn show(&self) { + self.label.set_visible(true) + } + + pub fn hide(&self) { + self.label.set_visible(false) + } +} diff --git a/src/app/browser/window/tab/item/page/widget.rs b/src/app/browser/window/tab/item/page/widget.rs index e44a91ba..10304df7 100644 --- a/src/app/browser/window/tab/item/page/widget.rs +++ b/src/app/browser/window/tab/item/page/widget.rs @@ -14,6 +14,7 @@ impl Widget { // Components navigation: &impl IsA, content: &impl IsA, + search: &impl IsA, input: &impl IsA, ) -> Self { // Init self @@ -24,6 +25,7 @@ impl Widget { g_box.append(navigation); g_box.append(content); + g_box.append(search); g_box.append(input); Self { g_box }