Browse Source

implement separated widget mod for navigation request feature

master
yggverse 2 months ago
parent
commit
deb3ea3469
  1. 8
      src/app/browser/window/tab/item/page/content/text/gemini/reader/default.css
  2. 10
      src/app/browser/window/tab/item/page/navigation.rs
  3. 121
      src/app/browser/window/tab/item/page/navigation/request.rs
  4. 19
      src/app/browser/window/tab/item/page/navigation/request/database.rs
  5. 214
      src/app/browser/window/tab/item/page/navigation/request/widget.rs
  6. 84
      src/app/browser/window/tab/item/page/navigation/request/widget/database.rs

8
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;
}

10
src/app/browser/window/tab/item/page/navigation.rs

@ -29,7 +29,7 @@ pub struct Navigation {
base: Base, base: Base,
history: History, history: History,
reload: Reload, reload: Reload,
request: Request, request: Arc<Request>,
bookmark: Bookmark, bookmark: Bookmark,
} }
@ -48,7 +48,7 @@ impl Navigation {
action_tab_page_navigation_history_forward, action_tab_page_navigation_history_forward,
); );
let reload = Reload::new(action_tab_page_navigation_reload.clone()); let reload = Reload::new(action_tab_page_navigation_reload.clone());
let request = Request::new( let request = Request::new_arc(
action_update.clone(), action_update.clone(),
action_tab_page_navigation_reload.clone(), action_tab_page_navigation_reload.clone(),
); );
@ -66,8 +66,8 @@ impl Navigation {
widget.append(base.widget()); widget.append(base.widget());
widget.append(history.widget()); widget.append(history.widget());
widget.append(reload.widget()); widget.append(reload.widget());
widget.append(request.widget()); widget.append(request.gobject());
widget.append(bookmark.widget()); widget.append(bookmark.widget()); // @TODO update api to gobject
// Result // Result
Self { Self {
@ -82,7 +82,7 @@ impl Navigation {
// Actions // Actions
pub fn request_grab_focus(&self) { pub fn request_grab_focus(&self) {
self.request.widget().grab_focus(); self.request.gobject().grab_focus();
} }
pub fn history_add(&self, request: GString) { pub fn history_add(&self, request: GString) {

121
src/app/browser/window/tab/item/page/navigation/request.rs

@ -1,104 +1,37 @@
mod database; mod database;
mod widget;
use database::Database; use database::Database;
use widget::Widget;
use gtk::{ use gtk::{
gio::SimpleAction, gio::SimpleAction,
glib::{timeout_add_local, ControlFlow, GString, SourceId, Uri, UriFlags}, glib::{GString, Uri, UriFlags},
prelude::{ActionExt, EditableExt, EntryExt},
Entry, Entry,
}; };
use sqlite::Transaction; use sqlite::Transaction;
use std::{cell::RefCell, sync::Arc, time::Duration}; use std::sync::Arc;
// Progressbar animation setup
const PROGRESS_ANIMATION_STEP: f64 = 0.05;
const PROGRESS_ANIMATION_TIME: u64 = 20; //ms
struct Progress {
fraction: RefCell<f64>,
source_id: RefCell<Option<SourceId>>,
}
// Main // Main
pub struct Request { pub struct Request {
progress: Arc<Progress>, widget: Arc<Widget>,
widget: Entry,
} }
impl Request { impl Request {
// Construct // Construct
pub fn new( pub fn new_arc(
// Actions // Actions
action_update: Arc<SimpleAction>, action_update: Arc<SimpleAction>,
action_tab_page_navigation_reload: Arc<SimpleAction>, // @TODO local `action_page_open`? action_tab_page_navigation_reload: Arc<SimpleAction>, // @TODO local `action_page_open`?
) -> Self { ) -> Arc<Self> {
// GTK Arc::new(Self {
let widget = Entry::builder() widget: Widget::new_arc(action_update, action_tab_page_navigation_reload),
.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 }
} }
// Actions // Actions
pub fn update(&self, progress_fraction: Option<f64>) { pub fn update(&self, progress_fraction: Option<f64>) {
// Skip update animation for Non value self.widget.update(progress_fraction);
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
}
},
)));
}
}
}
} }
pub fn clean( pub fn clean(
@ -112,7 +45,7 @@ impl Request {
match Database::delete(transaction, &record.id) { match Database::delete(transaction, &record.id) {
Ok(_) => { Ok(_) => {
// Delegate clean action to the item childs // Delegate clean action to the item childs
// nothing yet.. self.widget.clean(transaction, &record.id)?;
} }
Err(e) => return Err(e.to_string()), 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) { match Database::records(transaction, app_browser_window_tab_item_page_navigation_id) {
Ok(records) => { Ok(records) => {
for record in records { for record in records {
if let Some(text) = record.text {
self.widget.set_text(&text);
}
// Delegate restore action to the item childs // Delegate restore action to the item childs
// nothing yet.. self.widget.restore(transaction, &record.id)?;
} }
} }
Err(e) => return Err(e.to_string()), Err(e) => return Err(e.to_string()),
@ -151,22 +80,12 @@ impl Request {
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> { ) -> Result<(), String> {
// Keep value in memory until operation complete match Database::add(transaction, app_browser_window_tab_item_page_navigation_id) {
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()),
},
) {
Ok(_) => { Ok(_) => {
// let id = Database::last_insert_id(transaction); let id = Database::last_insert_id(transaction);
// Delegate save action to childs // Delegate save action to childs
// nothing yet.. self.widget.save(transaction, &id)?;
} }
Err(e) => return Err(e.to_string()), Err(e) => return Err(e.to_string()),
} }
@ -180,12 +99,12 @@ impl Request {
} }
// Getters // Getters
pub fn widget(&self) -> &Entry { pub fn gobject(&self) -> &Entry {
&self.widget &self.widget.gobject()
} }
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
0 == self.widget.text_length() self.widget.is_empty()
} }
pub fn text(&self) -> GString { pub fn text(&self) -> GString {
@ -207,7 +126,7 @@ impl Request {
} }
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. Widget::migrate(tx)?;
// Success // Success
Ok(()) Ok(())

19
src/app/browser/window/tab/item/page/navigation/request/database.rs

@ -3,7 +3,6 @@ use sqlite::{Error, Transaction};
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
// pub app_browser_window_tab_item_page_navigation_id: i64, not in use // pub app_browser_window_tab_item_page_navigation_id: i64, not in use
pub text: Option<String>, // can be stored as NULL
} }
pub struct Database { pub struct Database {
@ -16,8 +15,7 @@ impl Database {
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`app_browser_window_tab_item_page_navigation_id` INTEGER NOT NULL, `app_browser_window_tab_item_page_navigation_id` INTEGER NOT NULL
`text` VARCHAR(1024)
)", )",
[], [],
) )
@ -26,14 +24,12 @@ impl Database {
pub fn add( pub fn add(
tx: &Transaction, tx: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
text: Option<&str>,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab_item_page_navigation_request` ( "INSERT INTO `app_browser_window_tab_item_page_navigation_request` (
`app_browser_window_tab_item_page_navigation_id`, `app_browser_window_tab_item_page_navigation_id`
`text` ) VALUES (?)",
) VALUES (?, ?)", [app_browser_window_tab_item_page_navigation_id],
(app_browser_window_tab_item_page_navigation_id, text),
) )
} }
@ -43,8 +39,7 @@ impl Database {
) -> Result<Vec<Table>, Error> { ) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_tab_item_page_navigation_id`, `app_browser_window_tab_item_page_navigation_id`
`text`
FROM `app_browser_window_tab_item_page_navigation_request` FROM `app_browser_window_tab_item_page_navigation_request`
WHERE `app_browser_window_tab_item_page_navigation_id` = ?", WHERE `app_browser_window_tab_item_page_navigation_id` = ?",
)?; )?;
@ -53,7 +48,6 @@ impl Database {
Ok(Table { Ok(Table {
id: row.get(0)?, id: row.get(0)?,
// app_browser_window_tab_item_page_navigation_id: row.get(1)?, not in use // 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 { pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid() tx.last_insert_rowid()
} */ }
} }

214
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<f64>,
source_id: RefCell<Option<SourceId>>,
}
pub struct Widget {
gobject: Entry,
progress: Arc<Progress>,
}
impl Widget {
// Construct
pub fn new_arc(
action_update: Arc<SimpleAction>,
action_tab_page_navigation_reload: Arc<SimpleAction>, // @TODO local `action_page_open`?
) -> Arc<Self> {
// 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<f64>) {
// 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(())
}
}

84
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<String>, // can be stored as NULL
}
pub struct Database {
// nothing yet..
}
impl Database {
pub fn init(tx: &Transaction) -> Result<usize, Error> {
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<usize, Error> {
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<Vec<Table>, 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<usize, Error> {
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()
} */
}
Loading…
Cancel
Save