normalize tab items component

This commit is contained in:
yggverse 2024-10-08 00:56:52 +03:00
parent 47e2bc4617
commit 65502c247d
29 changed files with 427 additions and 117 deletions

View file

@ -0,0 +1,79 @@
use gtk::{
gio::SimpleAction,
glib::{gformat, GString, Uri},
prelude::{ActionExt, ButtonExt, WidgetExt},
Button,
};
use std::{cell::RefCell, sync::Arc};
pub struct Base {
// Actions
action_tab_page_navigation_base: Arc<SimpleAction>,
// Mutable URI cache (parsed on update)
uri: RefCell<Option<Uri>>,
// GTK
widget: Button,
}
impl Base {
// Construct
pub fn new(action_tab_page_navigation_base: Arc<SimpleAction>) -> Self {
// Init widget
let widget = Button::builder()
.icon_name("go-home-symbolic")
.tooltip_text("Base")
.sensitive(false)
.build();
// Init events
widget.connect_clicked({
let action_tab_page_navigation_base = action_tab_page_navigation_base.clone();
move |_| {
action_tab_page_navigation_base.activate(None);
}
});
// Return activated struct
Self {
action_tab_page_navigation_base,
uri: RefCell::new(None),
widget,
}
}
// Actions
pub fn update(&self, uri: Option<Uri>) {
// Update sensitivity
let status = match &uri {
Some(uri) => "/" != uri.path(),
None => false,
};
self.action_tab_page_navigation_base.set_enabled(status);
self.widget.set_sensitive(status);
// Update parsed cache
self.uri.replace(uri);
}
// Getters
pub fn widget(&self) -> &Button {
&self.widget
}
pub fn url(&self) -> Option<GString> {
// Build URL from parsed URI cache
if let Some(uri) = self.uri.take() {
let scheme = uri.scheme();
let port = uri.port();
if let Some(host) = uri.host() {
if port.is_positive() {
return Some(gformat!("{scheme}://{host}:{port}/"));
} else {
return Some(gformat!("{scheme}://{host}/"));
} // @TODO auth params
}
}
None
}
}

View file

@ -0,0 +1,28 @@
use gtk::Button;
pub struct Bookmark {
widget: Button,
}
impl Bookmark {
// Construct
pub fn new() -> Self {
Self {
widget: Button::builder()
.icon_name("starred-symbolic")
.tooltip_text("Bookmark")
.sensitive(false)
.build(),
}
}
// Actions
pub fn update(&self) {
// @TODO
}
// Getters
pub fn widget(&self) -> &Button {
&self.widget
}
}

View file

@ -0,0 +1,139 @@
mod back;
mod forward;
use back::Back;
use forward::Forward;
use gtk::{gio::SimpleAction, glib::GString, prelude::BoxExt, Box, Orientation};
use std::{cell::RefCell, sync::Arc};
struct Memory {
request: GString,
// time: SystemTime,
}
pub struct History {
// Components
back: Back,
forward: Forward,
// Extras
memory: RefCell<Vec<Memory>>,
index: RefCell<Option<usize>>,
// GTK
widget: Box,
}
impl History {
// Construct
pub fn new(
action_tab_page_navigation_history_back: Arc<SimpleAction>,
action_tab_page_navigation_history_forward: Arc<SimpleAction>,
) -> Self {
// init components
let back = Back::new(action_tab_page_navigation_history_back);
let forward = Forward::new(action_tab_page_navigation_history_forward);
// Init widget
let widget = Box::builder()
.orientation(Orientation::Horizontal)
.css_classes([
"linked", // merge childs
])
.build();
widget.append(back.widget());
widget.append(forward.widget());
// Init memory
let memory = RefCell::new(Vec::new());
// Init index
let index = RefCell::new(None);
Self {
// Actions
back,
forward,
// Extras
memory,
index,
// GTK
widget,
}
}
// Actions
pub fn add(&self, request: GString, follow_to_index: bool) {
// Append new Memory record
self.memory.borrow_mut().push(Memory {
request: request.clone(),
//time: SystemTime::now(),
});
if follow_to_index {
// Even push action make positive len value, make sure twice
if !self.memory.borrow().is_empty() {
// Navigate to the last record appended
self.index.replace(Some(self.memory.borrow().len() - 1));
} else {
self.index.replace(None);
}
}
}
pub fn back(&self, follow_to_index: bool) -> Option<GString> {
let index = self.index.borrow().clone(); // keep outside as borrow
if let Some(usize) = index {
// Make sure value positive to prevent panic
if usize > 0 {
if let Some(memory) = self.memory.borrow().get(usize - 1) {
if follow_to_index {
self.index.replace(Some(usize - 1));
}
return Some(memory.request.clone());
}
}
}
None
}
pub fn current(&self) -> Option<GString> {
let index = self.index.borrow().clone(); // keep outside as borrow
if let Some(usize) = index {
if let Some(memory) = self.memory.borrow().get(usize) {
return Some(memory.request.clone());
}
}
None
}
pub fn forward(&self, follow_to_index: bool) -> Option<GString> {
let index = self.index.borrow().clone(); // keep outside as borrow
if let Some(usize) = index {
if let Some(memory) = self.memory.borrow().get(usize + 1) {
if follow_to_index {
self.index.replace(Some(usize + 1));
}
return Some(memory.request.clone());
}
}
None
}
pub fn update(&self) {
match self.back(false) {
Some(_) => self.back.update(true),
None => self.back.update(false),
};
match self.forward(false) {
Some(_) => self.forward.update(true),
None => self.forward.update(false),
};
}
// Getters
pub fn widget(&self) -> &Box {
&self.widget
}
}

View file

@ -0,0 +1,50 @@
use gtk::{
gio::SimpleAction,
prelude::{ActionExt, ButtonExt, WidgetExt},
Button,
};
use std::sync::Arc;
pub struct Back {
action_tab_page_navigation_history_back: Arc<SimpleAction>,
widget: Button,
}
impl Back {
// Construct
pub fn new(action_tab_page_navigation_history_back: Arc<SimpleAction>) -> Self {
// Init widget
let widget = Button::builder()
.icon_name("go-previous-symbolic")
.tooltip_text("Back")
.sensitive(false)
.build();
// Init events
widget.connect_clicked({
let action_tab_page_navigation_history_back =
action_tab_page_navigation_history_back.clone();
move |_| {
action_tab_page_navigation_history_back.activate(None);
}
});
// Return activated struct
Self {
action_tab_page_navigation_history_back,
widget,
}
}
// Actions
pub fn update(&self, status: bool) {
self.action_tab_page_navigation_history_back
.set_enabled(status);
self.widget.set_sensitive(status);
}
// Getters
pub fn widget(&self) -> &Button {
&self.widget
}
}

View file

@ -0,0 +1,49 @@
use gtk::{
prelude::{ActionExt, ButtonExt, WidgetExt},
{gio::SimpleAction, Button},
};
use std::sync::Arc;
pub struct Forward {
action_tab_page_navigation_history_forward: Arc<SimpleAction>,
widget: Button,
}
impl Forward {
// Construct
pub fn new(action_tab_page_navigation_history_forward: Arc<SimpleAction>) -> Self {
// Init widget
let widget = Button::builder()
.icon_name("go-next-symbolic")
.tooltip_text("Forward")
.sensitive(false)
.build();
// Init events
widget.connect_clicked({
let action_tab_page_navigation_history_forward =
action_tab_page_navigation_history_forward.clone();
move |_| {
action_tab_page_navigation_history_forward.activate(None);
}
});
// Return activated struct
Self {
action_tab_page_navigation_history_forward,
widget,
}
}
// Actions
pub fn update(&self, status: bool) {
self.action_tab_page_navigation_history_forward
.set_enabled(status);
self.widget.set_sensitive(status);
}
// Getters
pub fn widget(&self) -> &Button {
&self.widget
}
}

View file

@ -0,0 +1,49 @@
use gtk::{
gio::SimpleAction,
prelude::{ActionExt, ButtonExt, WidgetExt},
Button,
};
use std::sync::Arc;
pub struct Reload {
action_tab_page_navigation_reload: Arc<SimpleAction>,
widget: Button,
}
impl Reload {
// Construct
pub fn new(action_tab_page_navigation_reload: Arc<SimpleAction>) -> Self {
// Init widget
let widget = Button::builder()
.icon_name("view-refresh-symbolic")
.tooltip_text("Reload")
.sensitive(false)
.build();
// Init events
widget.connect_clicked({
let action_tab_page_navigation_reload = action_tab_page_navigation_reload.clone();
move |_| {
action_tab_page_navigation_reload.activate(None);
}
});
// Return activated struct
Self {
action_tab_page_navigation_reload,
widget,
}
}
// Actions
pub fn update(&self, is_enabled: bool) {
self.action_tab_page_navigation_reload
.set_enabled(is_enabled);
self.widget.set_sensitive(is_enabled);
}
// Getters
pub fn widget(&self) -> &Button {
&self.widget
}
}

View file

@ -0,0 +1,129 @@
use gtk::{
gio::SimpleAction,
glib::{timeout_add_local, ControlFlow, GString, SourceId, Uri, UriFlags},
prelude::{ActionExt, EditableExt, EntryExt},
Entry,
};
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<f64>,
source_id: RefCell<Option<SourceId>>,
}
// Main
pub struct Request {
progress: Arc<Progress>,
widget: Entry,
}
impl Request {
// Construct
pub fn new(
text: Option<GString>,
// Actions
action_update: Arc<SimpleAction>,
action_tab_page_navigation_reload: Arc<SimpleAction>, // @TODO local `action_page_open`?
) -> Self {
// GTK
let widget = Entry::builder()
.placeholder_text("URL or search term...")
.hexpand(true)
.text(match text {
Some(text) => text,
None => GString::new(),
})
.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
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 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
}
},
)));
}
}
}
}
// Setters
pub fn set_text(&self, value: &GString) {
self.widget.set_text(value);
}
// Getters
pub fn widget(&self) -> &Entry {
&self.widget
}
pub fn is_empty(&self) -> bool {
0 == self.widget.text_length()
}
pub fn text(&self) -> GString {
self.widget.text()
}
pub fn uri(&self) -> Option<Uri> {
match Uri::parse(&self.widget.text(), UriFlags::NONE) {
Ok(uri) => Some(uri),
_ => None,
}
}
}