mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-01-30 13:04:13 +00:00
init search on page feature once
This commit is contained in:
parent
4b357f8229
commit
f767c11789
@ -71,7 +71,9 @@ impl Browser {
|
|||||||
|
|
||||||
action.escape.connect_activate({
|
action.escape.connect_activate({
|
||||||
let widget = widget.clone();
|
let widget = widget.clone();
|
||||||
|
let window = window.clone();
|
||||||
move || {
|
move || {
|
||||||
|
window.tab.escape(None); // current tab
|
||||||
widget.application_window.set_focus(gtk::Window::NONE);
|
widget.application_window.set_focus(gtk::Window::NONE);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -81,6 +81,11 @@ impl Window {
|
|||||||
move |_| tab.close_all()
|
move |_| tab.close_all()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
action.find.connect_activate({
|
||||||
|
let tab = tab.clone();
|
||||||
|
move |position| tab.find(position)
|
||||||
|
});
|
||||||
|
|
||||||
action.save_as.connect_activate({
|
action.save_as.connect_activate({
|
||||||
let tab = tab.clone();
|
let tab = tab.clone();
|
||||||
move |position| tab.save_as(position)
|
move |position| tab.save_as(position)
|
||||||
|
@ -166,9 +166,9 @@ impl Tab {
|
|||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Close page at given `position`, `None` to close selected page (if available)
|
/// Close page at given `page_position`, `None` to close selected page (if available)
|
||||||
pub fn close(&self, position: Option<i32>) {
|
pub fn close(&self, page_position: Option<i32>) {
|
||||||
self.widget.close(position);
|
self.widget.close(page_position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close all pages
|
// Close all pages
|
||||||
@ -176,6 +176,20 @@ impl Tab {
|
|||||||
self.widget.close_all();
|
self.widget.close_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle search widget
|
||||||
|
pub fn escape(&self, page_position: Option<i32>) {
|
||||||
|
if let Some(item) = self.item(page_position) {
|
||||||
|
item.page.escape();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle search widget
|
||||||
|
pub fn find(&self, page_position: Option<i32>) {
|
||||||
|
if let Some(item) = self.item(page_position) {
|
||||||
|
item.page.find();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save page at given `position`, `None` to save selected page (if available)
|
// Save page at given `position`, `None` to save selected page (if available)
|
||||||
pub fn save_as(&self, page_position: Option<i32>) {
|
pub fn save_as(&self, page_position: Option<i32>) {
|
||||||
if let Some(item) = self.item(page_position) {
|
if let Some(item) = self.item(page_position) {
|
||||||
|
@ -6,6 +6,7 @@ mod input;
|
|||||||
mod meta;
|
mod meta;
|
||||||
mod navigation;
|
mod navigation;
|
||||||
mod request;
|
mod request;
|
||||||
|
mod search;
|
||||||
mod widget;
|
mod widget;
|
||||||
|
|
||||||
use client::Client;
|
use client::Client;
|
||||||
@ -15,6 +16,7 @@ use input::Input;
|
|||||||
use meta::{Meta, Status};
|
use meta::{Meta, Status};
|
||||||
use navigation::Navigation;
|
use navigation::Navigation;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
|
use search::Search;
|
||||||
use widget::Widget;
|
use widget::Widget;
|
||||||
|
|
||||||
use crate::app::browser::{
|
use crate::app::browser::{
|
||||||
@ -42,6 +44,7 @@ pub struct Page {
|
|||||||
// Components
|
// Components
|
||||||
pub client: Rc<Client>,
|
pub client: Rc<Client>,
|
||||||
pub content: Rc<Content>,
|
pub content: Rc<Content>,
|
||||||
|
pub search: Rc<Search>,
|
||||||
pub input: Rc<Input>,
|
pub input: Rc<Input>,
|
||||||
pub meta: Rc<Meta>,
|
pub meta: Rc<Meta>,
|
||||||
pub navigation: Rc<Navigation>,
|
pub navigation: Rc<Navigation>,
|
||||||
@ -67,6 +70,8 @@ impl Page {
|
|||||||
tab_action.clone(),
|
tab_action.clone(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
|
let search = Rc::new(Search::new());
|
||||||
|
|
||||||
let navigation = Rc::new(Navigation::new(
|
let navigation = Rc::new(Navigation::new(
|
||||||
profile.clone(),
|
profile.clone(),
|
||||||
(
|
(
|
||||||
@ -82,6 +87,7 @@ impl Page {
|
|||||||
&id,
|
&id,
|
||||||
&navigation.widget.g_box,
|
&navigation.widget.g_box,
|
||||||
&content.g_box,
|
&content.g_box,
|
||||||
|
&search.g_box,
|
||||||
&input.widget.clamp,
|
&input.widget.clamp,
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -98,9 +104,10 @@ impl Page {
|
|||||||
// Components
|
// Components
|
||||||
client: Rc::new(Client::new()),
|
client: Rc::new(Client::new()),
|
||||||
content,
|
content,
|
||||||
navigation,
|
search,
|
||||||
input,
|
input,
|
||||||
meta,
|
meta,
|
||||||
|
navigation,
|
||||||
widget,
|
widget,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,6 +129,16 @@ impl Page {
|
|||||||
result
|
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)
|
/// Navigate home URL (parsed from current navigation entry)
|
||||||
/// * this method create new history record in memory as defined in `action_page_open` action
|
/// * this method create new history record in memory as defined in `action_page_open` action
|
||||||
pub fn home(&self) {
|
pub fn home(&self) {
|
||||||
@ -169,6 +186,7 @@ impl Page {
|
|||||||
self.window_action.find.simple_action.set_enabled(false);
|
self.window_action.find.simple_action.set_enabled(false);
|
||||||
|
|
||||||
// Reset widgets
|
// Reset widgets
|
||||||
|
self.search.update(None);
|
||||||
self.input.unset();
|
self.input.unset();
|
||||||
|
|
||||||
// Prevent infinitive redirection
|
// Prevent infinitive redirection
|
||||||
@ -372,21 +390,22 @@ impl Page {
|
|||||||
use gemini::client::connection::response;
|
use gemini::client::connection::response;
|
||||||
|
|
||||||
// Init shared clones
|
// Init shared clones
|
||||||
|
let browser_action = self.browser_action.clone();
|
||||||
let cancellable = self.client.cancellable();
|
let cancellable = self.client.cancellable();
|
||||||
let content = self.content.clone();
|
let content = self.content.clone();
|
||||||
let find = self.window_action.find.clone();
|
let search = self.search.clone();
|
||||||
let id = self.id.clone();
|
let id = self.id.clone();
|
||||||
let input = self.input.clone();
|
let input = self.input.clone();
|
||||||
let meta = self.meta.clone();
|
let meta = self.meta.clone();
|
||||||
let navigation = self.navigation.clone();
|
let navigation = self.navigation.clone();
|
||||||
let tab_action = self.tab_action.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
|
// Listen for connection status updates
|
||||||
self.client.gemini.socket.connect_event({
|
self.client.gemini.socket.connect_event({
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let meta = meta.clone();
|
let meta = meta.clone();
|
||||||
let update = update.clone();
|
let update = browser_action.update.clone();
|
||||||
move |_, event, _, _| {
|
move |_, event, _, _| {
|
||||||
meta.set_status(match event {
|
meta.set_status(match event {
|
||||||
SocketClientEvent::Resolving => Status::Resolving,
|
SocketClientEvent::Resolving => Status::Resolving,
|
||||||
@ -455,7 +474,7 @@ impl Page {
|
|||||||
.set_title(&title);
|
.set_title(&title);
|
||||||
|
|
||||||
// Update page
|
// Update page
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
},
|
},
|
||||||
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-20
|
||||||
response::meta::Status::Success => {
|
response::meta::Status::Success => {
|
||||||
@ -522,7 +541,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// Update window
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
} else { // browse
|
} else { // browse
|
||||||
match response.meta.mime.unwrap().value.to_lowercase().as_str() {
|
match response.meta.mime.unwrap().value.to_lowercase().as_str() {
|
||||||
"text/gemini" => {
|
"text/gemini" => {
|
||||||
@ -532,18 +551,19 @@ impl Page {
|
|||||||
Priority::DEFAULT,
|
Priority::DEFAULT,
|
||||||
cancellable.clone(),
|
cancellable.clone(),
|
||||||
{
|
{
|
||||||
|
let browser_action = browser_action.clone();
|
||||||
let content = content.clone();
|
let content = content.clone();
|
||||||
let find = find.clone();
|
let search = search.clone();
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let meta = meta.clone();
|
let meta = meta.clone();
|
||||||
let update = update.clone();
|
|
||||||
let uri = uri.clone();
|
let uri = uri.clone();
|
||||||
|
let window_action = window_action.clone();
|
||||||
move |result|{
|
move |result|{
|
||||||
match result {
|
match result {
|
||||||
Ok(buffer) => {
|
Ok(buffer) => {
|
||||||
// Set children component,
|
// Set children component,
|
||||||
// extract title from meta parsed
|
// extract title from meta parsed
|
||||||
let text = if is_source {
|
let text_widget = if is_source {
|
||||||
content.to_text_source(
|
content.to_text_source(
|
||||||
&buffer.data
|
&buffer.data
|
||||||
)
|
)
|
||||||
@ -554,16 +574,20 @@ impl Page {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update `find` model with new buffer
|
||||||
|
search.update(Some(text_widget.buffer));
|
||||||
|
|
||||||
// Update page meta
|
// Update page meta
|
||||||
meta.set_status(Status::Success)
|
meta.set_status(Status::Success)
|
||||||
.set_title(&match text.meta.title {
|
.set_title(&match text_widget.meta.title {
|
||||||
Some(meta_title) => meta_title,
|
Some(meta_title) => meta_title,
|
||||||
None => uri_to_title(&uri)
|
None => uri_to_title(&uri)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update window components
|
// Update window components
|
||||||
find.simple_action.set_enabled(text.has_search);
|
window_action.find.simple_action.set_enabled(true);
|
||||||
update.activate(Some(&id));
|
|
||||||
|
browser_action.update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Update widget
|
// Update widget
|
||||||
@ -575,7 +599,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// 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 cancellable = cancellable.clone();
|
||||||
let content = content.clone();
|
let content = content.clone();
|
||||||
let id = id.clone();
|
let id = id.clone();
|
||||||
let meta = meta.clone();
|
let meta = meta.clone();
|
||||||
let update = update.clone();
|
|
||||||
let uri = uri.clone();
|
let uri = uri.clone();
|
||||||
move |result| match result {
|
move |result| match result {
|
||||||
Ok((memory_input_stream, _)) => {
|
Ok((memory_input_stream, _)) => {
|
||||||
@ -626,7 +650,7 @@ impl Page {
|
|||||||
content.to_image(&Texture::for_pixbuf(&buffer));
|
content.to_image(&Texture::for_pixbuf(&buffer));
|
||||||
|
|
||||||
// Update window components
|
// Update window components
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Update widget
|
// Update widget
|
||||||
@ -666,7 +690,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// 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
|
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-60
|
||||||
response::meta::Status::CertificateRequest |
|
response::meta::Status::CertificateRequest |
|
||||||
@ -803,7 +827,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// Update window
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Add history record
|
// Add history record
|
||||||
@ -824,7 +848,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// Update window
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -843,7 +867,7 @@ impl Page {
|
|||||||
.set_title(&status.title());
|
.set_title(&status.title());
|
||||||
|
|
||||||
// Update window
|
// Update window
|
||||||
update.activate(Some(&id));
|
browser_action.update.activate(Some(&id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
mod gemini;
|
mod gemini;
|
||||||
mod search;
|
|
||||||
mod source;
|
mod source;
|
||||||
|
|
||||||
use gemini::Gemini;
|
use gemini::Gemini;
|
||||||
use search::Search;
|
|
||||||
use source::Source;
|
use source::Source;
|
||||||
|
|
||||||
use super::{BrowserAction, TabAction, WindowAction};
|
use super::{BrowserAction, TabAction, WindowAction};
|
||||||
use adw::Clamp;
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib::Uri,
|
glib::Uri,
|
||||||
prelude::{BoxExt, ButtonExt, TextViewExt, WidgetExt},
|
prelude::{BoxExt, TextViewExt},
|
||||||
Box, Orientation, ScrolledWindow,
|
Box, Orientation, ScrolledWindow, TextBuffer,
|
||||||
};
|
};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@ -20,8 +17,8 @@ pub struct Meta {
|
|||||||
} // @TODO move to separated mod
|
} // @TODO move to separated mod
|
||||||
|
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
|
pub buffer: TextBuffer,
|
||||||
pub g_box: Box,
|
pub g_box: Box,
|
||||||
pub has_search: bool,
|
|
||||||
pub meta: Meta,
|
pub meta: Meta,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +36,6 @@ impl Text {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
// Init components
|
// Init components
|
||||||
let gemini = Gemini::new(gemtext, base, (window_action, tab_action));
|
let gemini = Gemini::new(gemtext, base, (window_action, tab_action));
|
||||||
let search = Rc::new(Search::new(&gemini.reader.buffer));
|
|
||||||
|
|
||||||
// Init main widget
|
// Init main widget
|
||||||
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
||||||
@ -50,15 +46,8 @@ impl Text {
|
|||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
g_box.append(
|
|
||||||
&Clamp::builder()
|
|
||||||
.child(&search.g_box)
|
|
||||||
.css_classes(["osd"])
|
|
||||||
.maximum_size(800)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Connect events
|
// Connect events
|
||||||
|
/* @TODO
|
||||||
browser_action.escape.connect_activate({
|
browser_action.escape.connect_activate({
|
||||||
let close = search.close.clone();
|
let close = search.close.clone();
|
||||||
move || {
|
move || {
|
||||||
@ -99,29 +88,28 @@ impl Text {
|
|||||||
move |_| {
|
move |_| {
|
||||||
search.g_box.set_visible(false);
|
search.g_box.set_visible(false);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
buffer: gemini.reader.widget.text_view.buffer(),
|
||||||
meta: Meta {
|
meta: Meta {
|
||||||
title: gemini.reader.title.clone(),
|
title: gemini.reader.title.clone(),
|
||||||
},
|
},
|
||||||
has_search: true,
|
|
||||||
g_box,
|
g_box,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_source(data: &str) -> Self {
|
pub fn new_source(data: &str) -> Self {
|
||||||
|
// Init components
|
||||||
|
let source = Source::new(data);
|
||||||
|
|
||||||
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
let g_box = Box::builder().orientation(Orientation::Vertical).build();
|
||||||
|
|
||||||
g_box.append(
|
g_box.append(&ScrolledWindow::builder().child(&source.text_view).build());
|
||||||
&ScrolledWindow::builder()
|
|
||||||
.child(&Source::new(data).text_view)
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
buffer: source.text_view.buffer(),
|
||||||
meta: Meta { title: None },
|
meta: Meta { title: None },
|
||||||
has_search: false,
|
|
||||||
g_box,
|
g_box,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
const LINK_COLOR_ONHOVER: (f32, f32, f32, f32) = (53.0, 132.0, 228.0, 228.0);
|
||||||
|
|
||||||
pub struct Reader {
|
pub struct Reader {
|
||||||
pub buffer: TextBuffer,
|
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub widget: Rc<Widget>,
|
pub widget: Rc<Widget>,
|
||||||
}
|
}
|
||||||
@ -459,11 +458,7 @@ impl Reader {
|
|||||||
}); // @TODO may be expensive for CPU, add timeout?
|
}); // @TODO may be expensive for CPU, add timeout?
|
||||||
|
|
||||||
// Result
|
// Result
|
||||||
Ok(Self {
|
Ok(Self { title, widget })
|
||||||
buffer,
|
|
||||||
title,
|
|
||||||
widget,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<Input>,
|
|
||||||
pub navigation: Rc<Navigation>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Tag>,
|
|
||||||
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
|
|
||||||
}
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
83
src/app/browser/window/tab/item/page/search.rs
Normal file
83
src/app/browser/window/tab/item/page/search.rs
Normal file
@ -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<RefCell<Option<Buffer>>>,
|
||||||
|
pub form: Rc<Form>,
|
||||||
|
pub placeholder: Rc<Placeholder>,
|
||||||
|
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<TextBuffer>) {
|
||||||
|
self.buffer.replace(match text_buffer {
|
||||||
|
Some(buffer) => Some(Buffer::new(buffer)),
|
||||||
|
None => None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
25
src/app/browser/window/tab/item/page/search/buffer.rs
Normal file
25
src/app/browser/window/tab/item/page/search/buffer.rs
Normal file
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
32
src/app/browser/window/tab/item/page/search/buffer/tag.rs
Normal file
32
src/app/browser/window/tab/item/page/search/buffer/tag.rs
Normal file
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
144
src/app/browser/window/tab/item/page/search/form.rs
Normal file
144
src/app/browser/window/tab/item/page/search/form.rs
Normal file
@ -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<RefCell<Option<Buffer>>>) -> 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<RefCell<Option<Buffer>>>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -21,15 +21,13 @@ pub struct Navigation {
|
|||||||
pub g_box: Box,
|
pub g_box: Box,
|
||||||
index: Rc<Cell<usize>>,
|
index: Rc<Cell<usize>>,
|
||||||
matches: Rc<RefCell<Vec<(TextIter, TextIter)>>>,
|
matches: Rc<RefCell<Vec<(TextIter, TextIter)>>>,
|
||||||
text_buffer: TextBuffer,
|
|
||||||
current_tag: TextTag,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Navigation {
|
impl Navigation {
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(text_buffer: TextBuffer, current_tag: TextTag) -> Self {
|
pub fn new() -> Self {
|
||||||
// Init shared matches holder
|
// Init shared matches holder
|
||||||
let index = Rc::new(Cell::new(0));
|
let index = Rc::new(Cell::new(0));
|
||||||
let matches = Rc::new(RefCell::new(Vec::new()));
|
let matches = Rc::new(RefCell::new(Vec::new()));
|
||||||
@ -56,8 +54,6 @@ impl Navigation {
|
|||||||
g_box,
|
g_box,
|
||||||
index,
|
index,
|
||||||
matches,
|
matches,
|
||||||
text_buffer,
|
|
||||||
current_tag,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +68,7 @@ impl Navigation {
|
|||||||
self.back.update(self.is_match());
|
self.back.update(self.is_match());
|
||||||
self.forward.update(self.is_match());
|
self.forward.update(self.is_match());
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
pub fn back(&self) -> Option<(TextIter, TextIter)> {
|
pub fn back(&self) -> Option<(TextIter, TextIter)> {
|
||||||
self.text_buffer.remove_tag(
|
self.text_buffer.remove_tag(
|
||||||
&self.current_tag,
|
&self.current_tag,
|
||||||
@ -120,7 +116,7 @@ impl Navigation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
// Getters
|
// Getters
|
||||||
|
|
||||||
pub fn is_match(&self) -> bool {
|
pub fn is_match(&self) -> bool {
|
29
src/app/browser/window/tab/item/page/search/placeholder.rs
Normal file
29
src/app/browser/window/tab/item/page/search/placeholder.rs
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ impl Widget {
|
|||||||
// Components
|
// Components
|
||||||
navigation: &impl IsA<gtk::Widget>,
|
navigation: &impl IsA<gtk::Widget>,
|
||||||
content: &impl IsA<gtk::Widget>,
|
content: &impl IsA<gtk::Widget>,
|
||||||
|
search: &impl IsA<gtk::Widget>,
|
||||||
input: &impl IsA<gtk::Widget>,
|
input: &impl IsA<gtk::Widget>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Init self
|
// Init self
|
||||||
@ -24,6 +25,7 @@ impl Widget {
|
|||||||
|
|
||||||
g_box.append(navigation);
|
g_box.append(navigation);
|
||||||
g_box.append(content);
|
g_box.append(content);
|
||||||
|
g_box.append(search);
|
||||||
g_box.append(input);
|
g_box.append(input);
|
||||||
|
|
||||||
Self { g_box }
|
Self { g_box }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user