diff --git a/src/app/browser/window/tab/item/page/content/text/gemini/reader/default.css b/src/app/browser/window/tab/item/page/content/text/gemini/reader/default.css new file mode 100644 index 00000000..2d752721 --- /dev/null +++ b/src/app/browser/window/tab/item/page/content/text/gemini/reader/default.css @@ -0,0 +1,8 @@ +/* @TODO + * not in use as defined inline: + * src/browser/main/tab/page/content/text/gemini/reader.rs + */ +label +{ + caret-color: transparent; +} \ No newline at end of file diff --git a/src/app/browser/window/tab/item/page/navigation.rs b/src/app/browser/window/tab/item/page/navigation.rs index fb47041b..587abe9b 100644 --- a/src/app/browser/window/tab/item/page/navigation.rs +++ b/src/app/browser/window/tab/item/page/navigation.rs @@ -29,7 +29,7 @@ pub struct Navigation { base: Base, history: History, reload: Reload, - request: Request, + request: Arc, bookmark: Bookmark, } @@ -48,7 +48,7 @@ impl Navigation { action_tab_page_navigation_history_forward, ); let reload = Reload::new(action_tab_page_navigation_reload.clone()); - let request = Request::new( + let request = Request::new_arc( action_update.clone(), action_tab_page_navigation_reload.clone(), ); @@ -66,8 +66,8 @@ impl Navigation { widget.append(base.widget()); widget.append(history.widget()); widget.append(reload.widget()); - widget.append(request.widget()); - widget.append(bookmark.widget()); + widget.append(request.gobject()); + widget.append(bookmark.widget()); // @TODO update api to gobject // Result Self { @@ -82,7 +82,7 @@ impl Navigation { // Actions pub fn request_grab_focus(&self) { - self.request.widget().grab_focus(); + self.request.gobject().grab_focus(); } pub fn history_add(&self, request: GString) { diff --git a/src/app/browser/window/tab/item/page/navigation/request.rs b/src/app/browser/window/tab/item/page/navigation/request.rs index 3b2d8ae4..f3f3705f 100644 --- a/src/app/browser/window/tab/item/page/navigation/request.rs +++ b/src/app/browser/window/tab/item/page/navigation/request.rs @@ -1,104 +1,37 @@ mod database; +mod widget; use database::Database; +use widget::Widget; use gtk::{ gio::SimpleAction, - glib::{timeout_add_local, ControlFlow, GString, SourceId, Uri, UriFlags}, - prelude::{ActionExt, EditableExt, EntryExt}, + glib::{GString, Uri, UriFlags}, Entry, }; use sqlite::Transaction; -use std::{cell::RefCell, sync::Arc, time::Duration}; - -// Progressbar animation setup -const PROGRESS_ANIMATION_STEP: f64 = 0.05; -const PROGRESS_ANIMATION_TIME: u64 = 20; //ms - -struct Progress { - fraction: RefCell, - source_id: RefCell>, -} +use std::sync::Arc; // Main pub struct Request { - progress: Arc, - widget: Entry, + widget: Arc, } impl Request { // Construct - pub fn new( + pub fn new_arc( // Actions action_update: Arc, action_tab_page_navigation_reload: Arc, // @TODO local `action_page_open`? - ) -> Self { - // GTK - let widget = Entry::builder() - .placeholder_text("URL or search term...") - .hexpand(true) - .build(); - - // Connect events - widget.connect_changed(move |_| { - action_update.activate(None); - }); - - widget.connect_activate(move |_| { - action_tab_page_navigation_reload.activate(None); - }); - - // Init animated progressbar state - let progress = Arc::new(Progress { - fraction: RefCell::new(0.0), - source_id: RefCell::new(None), - }); - - // Result - Self { progress, widget } + ) -> Arc { + Arc::new(Self { + widget: Widget::new_arc(action_update, action_tab_page_navigation_reload), + }) } // Actions pub fn update(&self, progress_fraction: Option) { - // Skip update animation for Non value - if let Some(value) = progress_fraction { - // Update shared fraction value for async progressbar function, animate on changed only - if value != self.progress.fraction.replace(value) { - // Start new frame on previous process function completed (`source_id` changed to None) - // If previous process still active, we have just updated shared fraction value before, to use it inside the active process - if self.progress.source_id.borrow().is_none() { - // Start new animation frame iterator, update `source_id` - self.progress.source_id.replace(Some(timeout_add_local( - Duration::from_millis(PROGRESS_ANIMATION_TIME), - { - // Clone async pointers dependency - let widget = self.widget.clone(); - let progress = self.progress.clone(); - - // Frame - move || { - // Animate - if *progress.fraction.borrow() > widget.progress_fraction() { - widget.set_progress_fraction( - // Currently, here is no outrange validation, seems that wrapper make this work @TODO - widget.progress_fraction() + PROGRESS_ANIMATION_STEP, - ); - return ControlFlow::Continue; - } - // Deactivate - progress.source_id.replace(None); - - // Reset (to hide progress widget) - widget.set_progress_fraction(0.0); - - // Stop iteration - ControlFlow::Break - } - }, - ))); - } - } - } + self.widget.update(progress_fraction); } pub fn clean( @@ -112,7 +45,7 @@ impl Request { match Database::delete(transaction, &record.id) { Ok(_) => { // Delegate clean action to the item childs - // nothing yet.. + self.widget.clean(transaction, &record.id)?; } Err(e) => return Err(e.to_string()), } @@ -132,12 +65,8 @@ impl Request { match Database::records(transaction, app_browser_window_tab_item_page_navigation_id) { Ok(records) => { for record in records { - if let Some(text) = record.text { - self.widget.set_text(&text); - } - // Delegate restore action to the item childs - // nothing yet.. + self.widget.restore(transaction, &record.id)?; } } Err(e) => return Err(e.to_string()), @@ -151,22 +80,12 @@ impl Request { transaction: &Transaction, app_browser_window_tab_item_page_navigation_id: &i64, ) -> Result<(), String> { - // Keep value in memory until operation complete - let text = self.widget.text(); - - match Database::add( - transaction, - app_browser_window_tab_item_page_navigation_id, - match text.is_empty() { - true => None, - false => Some(text.as_str()), - }, - ) { + match Database::add(transaction, app_browser_window_tab_item_page_navigation_id) { Ok(_) => { - // let id = Database::last_insert_id(transaction); + let id = Database::last_insert_id(transaction); // Delegate save action to childs - // nothing yet.. + self.widget.save(transaction, &id)?; } Err(e) => return Err(e.to_string()), } @@ -180,12 +99,12 @@ impl Request { } // Getters - pub fn widget(&self) -> &Entry { - &self.widget + pub fn gobject(&self) -> &Entry { + &self.widget.gobject() } pub fn is_empty(&self) -> bool { - 0 == self.widget.text_length() + self.widget.is_empty() } pub fn text(&self) -> GString { @@ -207,7 +126,7 @@ impl Request { } // Delegate migration to childs - // nothing yet.. + Widget::migrate(tx)?; // Success Ok(()) diff --git a/src/app/browser/window/tab/item/page/navigation/request/database.rs b/src/app/browser/window/tab/item/page/navigation/request/database.rs index b66fdb80..87bc5a7a 100644 --- a/src/app/browser/window/tab/item/page/navigation/request/database.rs +++ b/src/app/browser/window/tab/item/page/navigation/request/database.rs @@ -3,7 +3,6 @@ use sqlite::{Error, Transaction}; pub struct Table { pub id: i64, // pub app_browser_window_tab_item_page_navigation_id: i64, not in use - pub text: Option, // can be stored as NULL } pub struct Database { @@ -16,8 +15,7 @@ impl Database { "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `app_browser_window_tab_item_page_navigation_id` INTEGER NOT NULL, - `text` VARCHAR(1024) + `app_browser_window_tab_item_page_navigation_id` INTEGER NOT NULL )", [], ) @@ -26,14 +24,12 @@ impl Database { pub fn add( tx: &Transaction, app_browser_window_tab_item_page_navigation_id: &i64, - text: Option<&str>, ) -> Result { tx.execute( "INSERT INTO `app_browser_window_tab_item_page_navigation_request` ( - `app_browser_window_tab_item_page_navigation_id`, - `text` - ) VALUES (?, ?)", - (app_browser_window_tab_item_page_navigation_id, text), + `app_browser_window_tab_item_page_navigation_id` + ) VALUES (?)", + [app_browser_window_tab_item_page_navigation_id], ) } @@ -43,8 +39,7 @@ impl Database { ) -> Result, Error> { let mut stmt = tx.prepare( "SELECT `id`, - `app_browser_window_tab_item_page_navigation_id`, - `text` + `app_browser_window_tab_item_page_navigation_id` FROM `app_browser_window_tab_item_page_navigation_request` WHERE `app_browser_window_tab_item_page_navigation_id` = ?", )?; @@ -53,7 +48,6 @@ impl Database { Ok(Table { id: row.get(0)?, // app_browser_window_tab_item_page_navigation_id: row.get(1)?, not in use - text: row.get(2)?, }) })?; @@ -74,8 +68,7 @@ impl Database { ) } - /* not in use pub fn last_insert_id(tx: &Transaction) -> i64 { tx.last_insert_rowid() - } */ + } } diff --git a/src/app/browser/window/tab/item/page/navigation/request/widget.rs b/src/app/browser/window/tab/item/page/navigation/request/widget.rs new file mode 100644 index 00000000..ff1ef570 --- /dev/null +++ b/src/app/browser/window/tab/item/page/navigation/request/widget.rs @@ -0,0 +1,214 @@ +mod database; + +use database::Database; + +use gtk::{ + gio::SimpleAction, + glib::{timeout_add_local, ControlFlow, GString, SourceId}, + prelude::{ActionExt, EditableExt, EntryExt}, + Entry, +}; +use sqlite::Transaction; +use std::{cell::RefCell, sync::Arc, time::Duration}; + +const PLACEHOLDER_TEXT: &str = "URL or search term..."; + +// Progress bar animation setup +const PROGRESS_ANIMATION_STEP: f64 = 0.05; +const PROGRESS_ANIMATION_TIME: u64 = 20; //ms + +struct Progress { + fraction: RefCell, + source_id: RefCell>, +} + +pub struct Widget { + gobject: Entry, + progress: Arc, +} + +impl Widget { + // Construct + pub fn new_arc( + action_update: Arc, + action_tab_page_navigation_reload: Arc, // @TODO local `action_page_open`? + ) -> Arc { + // Init animated progress bar state + let progress = Arc::new(Progress { + fraction: RefCell::new(0.0), + source_id: RefCell::new(None), + }); + + // Init widget + let gobject = Entry::builder() + .placeholder_text(PLACEHOLDER_TEXT) + .hexpand(true) + .build(); + + // Connect events + gobject.connect_changed(move |_| { + action_update.activate(None); + }); + + gobject.connect_activate(move |_| { + action_tab_page_navigation_reload.activate(None); + }); + + // Return activated struct + Arc::new(Self { gobject, progress }) + } + + // Actions + pub fn clean( + &self, + transaction: &Transaction, + app_browser_window_tab_item_page_navigation_request_id: &i64, + ) -> Result<(), String> { + match Database::records( + transaction, + app_browser_window_tab_item_page_navigation_request_id, + ) { + Ok(records) => { + for record in records { + match Database::delete(transaction, &record.id) { + Ok(_) => { + // Delegate clean action to the item childs + // nothing yet.. + } + Err(e) => return Err(e.to_string()), + } + } + } + Err(e) => return Err(e.to_string()), + } + + Ok(()) + } + + pub fn restore( + &self, + transaction: &Transaction, + app_browser_window_tab_item_page_navigation_request_id: &i64, + ) -> Result<(), String> { + match Database::records( + transaction, + app_browser_window_tab_item_page_navigation_request_id, + ) { + Ok(records) => { + for record in records { + if let Some(text) = record.text { + self.gobject.set_text(&text); + } + + // Delegate restore action to the item childs + // nothing yet.. + } + } + Err(e) => return Err(e.to_string()), + } + + Ok(()) + } + + pub fn save( + &self, + transaction: &Transaction, + app_browser_window_tab_item_page_navigation_request_id: &i64, + ) -> Result<(), String> { + // Keep value in memory until operation complete + let text = self.gobject.text(); + + match Database::add( + transaction, + app_browser_window_tab_item_page_navigation_request_id, + match text.is_empty() { + true => None, + false => Some(text.as_str()), + }, + ) { + Ok(_) => { + // let id = Database::last_insert_id(transaction); + + // Delegate save action to childs + // nothing yet.. + } + Err(e) => return Err(e.to_string()), + } + + Ok(()) + } + + pub fn update(&self, progress_fraction: Option) { + // Skip update animation for Non value + if let Some(value) = progress_fraction { + // Update shared fraction value for async progressbar function, animate on changed only + if value != self.progress.fraction.replace(value) { + // Start new frame on previous process function completed (`source_id` changed to None) + // If previous process still active, we have just updated shared fraction value before, to use it inside the active process + if self.progress.source_id.borrow().is_none() { + // Start new animation frame iterator, update `source_id` + self.progress.source_id.replace(Some(timeout_add_local( + Duration::from_millis(PROGRESS_ANIMATION_TIME), + { + // Clone async pointers dependency + let gobject = self.gobject.clone(); + let progress = self.progress.clone(); + + // Frame + move || { + // Animate + if *progress.fraction.borrow() > gobject.progress_fraction() { + gobject.set_progress_fraction( + // Currently, here is no outrange validation, seems that wrapper make this work @TODO + gobject.progress_fraction() + PROGRESS_ANIMATION_STEP, + ); + return ControlFlow::Continue; + } + // Deactivate + progress.source_id.replace(None); + + // Reset (to hide progress widget) + gobject.set_progress_fraction(0.0); + + // Stop iteration + ControlFlow::Break + } + }, + ))); + } + } + } + } + + // Setters + pub fn set_text(&self, value: &GString) { + self.gobject.set_text(value); + } + + // Getters + pub fn gobject(&self) -> &Entry { + &self.gobject + } + + pub fn is_empty(&self) -> bool { + 0 == self.gobject.text_length() + } + + pub fn text(&self) -> GString { + self.gobject.text() + } + + // Tools + pub fn migrate(tx: &Transaction) -> Result<(), String> { + // Migrate self components + if let Err(e) = Database::init(&tx) { + return Err(e.to_string()); + } + + // Delegate migration to childs + // nothing yet.. + + // Success + Ok(()) + } +} diff --git a/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs b/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs new file mode 100644 index 00000000..c0b70cc6 --- /dev/null +++ b/src/app/browser/window/tab/item/page/navigation/request/widget/database.rs @@ -0,0 +1,84 @@ +use sqlite::{Error, Transaction}; + +pub struct Table { + pub id: i64, + // pub app_browser_window_tab_item_page_navigation_request_id: i64, not in use + pub text: Option, // can be stored as NULL +} + +pub struct Database { + // nothing yet.. +} + +impl Database { + pub fn init(tx: &Transaction) -> Result { + tx.execute( + "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request_widget` + ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `app_browser_window_tab_item_page_navigation_request_id` INTEGER NOT NULL, + `text` VARCHAR(1024) + )", + [], + ) + } + + pub fn add( + tx: &Transaction, + app_browser_window_tab_item_page_navigation_request_id: &i64, + text: Option<&str>, + ) -> Result { + tx.execute( + "INSERT INTO `app_browser_window_tab_item_page_navigation_request_widget` ( + `app_browser_window_tab_item_page_navigation_request_id`, + `text` + ) VALUES (?, ?)", + (app_browser_window_tab_item_page_navigation_request_id, text), + ) + } + + pub fn records( + tx: &Transaction, + app_browser_window_tab_item_page_navigation_request_id: &i64, + ) -> Result, Error> { + let mut stmt = tx.prepare( + "SELECT `id`, + `app_browser_window_tab_item_page_navigation_request_id`, + `text` + FROM `app_browser_window_tab_item_page_navigation_request_widget` + WHERE `app_browser_window_tab_item_page_navigation_request_id` = ?", + )?; + + let result = stmt.query_map( + [app_browser_window_tab_item_page_navigation_request_id], + |row| { + Ok(Table { + id: row.get(0)?, + // app_browser_window_tab_item_page_navigation_request_id: row.get(1)?, not in use + text: row.get(2)?, + }) + }, + )?; + + let mut records = Vec::new(); + + for record in result { + let table = record?; + records.push(table); + } + + Ok(records) + } + + pub fn delete(tx: &Transaction, id: &i64) -> Result { + tx.execute( + "DELETE FROM `app_browser_window_tab_item_page_navigation_request_widget` WHERE `id` = ?", + [id], + ) + } + + /* not in use + pub fn last_insert_id(tx: &Transaction) -> i64 { + tx.last_insert_rowid() + } */ +}