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,50 @@
mod gemini;
use gemini::Gemini;
use gtk::{
gio::SimpleAction,
glib::{GString, Uri},
ScrolledWindow,
};
use std::sync::Arc;
pub struct Meta {
title: Option<GString>,
}
pub struct Text {
meta: Meta,
widget: ScrolledWindow,
}
impl Text {
// Construct
pub fn gemini(gemtext: &str, base: &Uri, action_page_open: Arc<SimpleAction>) -> Self {
// Init components
let gemini = Gemini::new(gemtext, base, action_page_open);
// Init meta
let meta = Meta {
title: gemini.reader_title().clone(),
};
// Init widget
let widget = ScrolledWindow::builder().build();
widget.set_child(Some(gemini.widget()));
// Result
Self { meta, widget }
}
// Getters
pub fn meta_title(&self) -> &Option<GString> {
&self.meta.title
}
pub fn widget(&self) -> &ScrolledWindow {
&self.widget
}
}

View file

@ -0,0 +1,41 @@
mod reader;
use reader::Reader;
use gtk::{
gio::SimpleAction,
glib::{GString, Uri},
Viewport,
};
use std::sync::Arc;
pub struct Gemini {
reader: Reader,
widget: Viewport,
}
impl Gemini {
// Construct
pub fn new(gemtext: &str, base: &Uri, action_page_open: Arc<SimpleAction>) -> Self {
// Init components
let reader = Reader::new(gemtext, base, action_page_open);
// Init widget
let widget = Viewport::builder().scroll_to_focus(false).build();
widget.set_child(Some(reader.widget()));
// Result
Self { reader, widget }
}
// Getters
pub fn reader_title(&self) -> &Option<GString> {
&self.reader.title()
}
pub fn widget(&self) -> &Viewport {
&self.widget
}
}

View file

@ -0,0 +1,124 @@
mod parser;
use parser::header::Header;
use parser::link::Link;
use parser::plain::Plain;
use gtk::{
gio::SimpleAction,
glib::{GString, Propagation, Uri, UriFlags},
prelude::{ActionExt, StyleContextExt, ToVariant, WidgetExt},
Align, CssProvider, Label, STYLE_PROVIDER_PRIORITY_APPLICATION,
};
use std::sync::Arc;
pub struct Reader {
title: Option<GString>,
// css: CssProvider,
widget: Label,
}
impl Reader {
// Construct
pub fn new(gemtext: &str, base: &Uri, action_page_open: Arc<SimpleAction>) -> Self {
// Init title
let mut title = None;
// Init markup
let mut markup = String::new();
for line in gemtext.lines() {
// Is header
if let Some(header) = Header::from(line) {
// Format
markup.push_str(header.markup());
// Set title from first document header tag
if title == None {
title = Some(header.text().clone());
}
continue;
}
// Is link
if let Some(link) = Link::from(line, base) {
// Format
markup.push_str(link.markup());
continue;
}
// Nothing match, escape string just
markup.push_str(Plain::from(line).markup())
}
// Init CSS
let css = CssProvider::new();
/* @TODO Theme parser error: <broken file>
css.load_from_path(
"src/browser/main/tab/page/content/text/gemini/reader/default.css"
); */
css.load_from_data("label{caret-color: transparent;}");
// Init widget
let widget = Label::builder()
.halign(Align::Fill)
.valign(Align::Fill)
.hexpand(true)
.vexpand(true)
.xalign(0.0)
.yalign(0.0)
.margin_start(8)
.margin_end(8)
.wrap(true)
.selectable(true)
.use_markup(true)
.label(markup)
.build();
widget
.style_context()
.add_provider(&css, STYLE_PROVIDER_PRIORITY_APPLICATION);
// Connect actions
widget.connect_activate_link(move |_, href| {
// Detect requested protocol
if let Ok(uri) = Uri::parse(&href, UriFlags::NONE) {
return match uri.scheme().as_str() {
"gemini" => {
// Open new page
action_page_open.activate(Some(&uri.to_str().to_variant()));
// Prevent link open in external application
Propagation::Stop
}
// Protocol not supported
_ => Propagation::Proceed,
};
}
// Delegate unparsable
Propagation::Proceed
});
// Result
Self {
title,
// css,
widget,
}
}
// Getters
pub fn title(&self) -> &Option<GString> {
&self.title
}
pub fn widget(&self) -> &Label {
&self.widget
}
}

View file

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

View file

@ -0,0 +1,3 @@
pub mod header;
pub mod link;
pub mod plain;

View file

@ -0,0 +1,78 @@
use gtk::glib::{gformat, markup_escape_text, GString, Regex, RegexCompileFlags, RegexMatchFlags};
pub enum Level {
H1,
H2,
H3,
}
pub struct Header {
// level: Level,
text: GString,
markup: GString,
}
impl Header {
pub fn from(line: &str) -> Option<Header> {
// Parse line
let parsed = Regex::split_simple(
r"^(#{1,3})\s*(.+)$",
line,
RegexCompileFlags::DEFAULT,
RegexMatchFlags::DEFAULT,
);
// Validate match results
if let Some(text) = parsed.get(2) {
if let Some(level) = parsed.get(1) {
// Init level
let level = match level.len() {
1 => Level::H1,
2 => Level::H2,
3 => Level::H3,
_ => return None,
};
// Init text
let text = GString::from(text.as_str());
if text.trim().is_empty() {
return None;
}
// Init markup
let markup = match level {
Level::H1 => gformat!(
"<span size=\"xx-large\">{}</span>\n",
markup_escape_text(&text)
),
Level::H2 => gformat!(
"<span size=\"x-large\">{}</span>\n",
markup_escape_text(&text)
),
Level::H3 => gformat!(
"<span size=\"large\">{}</span>\n",
markup_escape_text(&text)
),
};
// Result
return Some(Header {
// level,
text,
markup,
});
}
}
None // not header line given
}
pub fn text(&self) -> &GString {
&self.text
}
pub fn markup(&self) -> &GString {
&self.markup
}
}

View file

@ -0,0 +1,133 @@
use gtk::glib::{
gformat, markup_escape_text, GString, Regex, RegexCompileFlags, RegexMatchFlags, Uri, UriFlags,
};
pub struct Link {
// alt: Option<GString>, // [optional] alternative text
// date: Option<GString>, // [optional] date @TODO store in UnixTime?
// external: bool, // external link indicator
// link: GString, // original link, wanted for title tooltip
markup: GString, // pango markup with escaped special chars
// uri: Uri, // parsed link object (currently not in use)
}
impl Link {
// Link structure parser
// line - gemtext subject to parse
// base - Uri object, required for:
// 1. relative to absolute address conversion
// 2. external links indication
// returns new Link struct or None
pub fn from(line: &str, base: &Uri) -> Option<Link> {
// Init struct members
// let mut alt: Option<GString> = None;
// let mut date: Option<GString> = None;
let external: bool;
let link: GString;
let markup: GString;
let uri: Uri;
// Parse line
let parsed = Regex::split_simple(
r"^=>\s*([^\s]+)\s*(\d{4}-\d{2}-\d{2})?\s*(.+)?$",
line,
RegexCompileFlags::DEFAULT,
RegexMatchFlags::DEFAULT,
);
// Address
match parsed.get(1) {
Some(address) => {
// Define original link value (used in titles or when alt is empty)
link = GString::from(address.as_str());
// Links in document usually relative, make them absolute to base given
match Uri::resolve_relative(Some(&base.to_str()), address.as_str(), UriFlags::NONE)
{
Ok(resolved) => {
// Make URI parsed as always valid (no idea why does lib operate strings, not objects)
match Uri::parse(&resolved, UriFlags::NONE) {
Ok(object) => {
// Set external status
external =
object.host() != base.host() || object.port() != base.port();
// Set struct URI
uri = object;
}
Err(_) => return None,
}
}
Err(_) => return None,
}
}
None => return None,
}
// Create link name based on external status, date and alt values
let mut name = Vec::new();
if external {
name.push("".to_string());
}
// Date
if let Some(this) = parsed.get(2) {
// date = Some(GString::from(this.to_string()));
name.push(this.to_string());
}
// Alt
match parsed.get(3) {
// Not empty
Some(this) => {
// alt = Some(GString::from(this.to_string()));
name.push(this.to_string());
}
// Empty, use resolved address
None => name.push(link.to_string()),
};
// Markup
markup = gformat!(
"<a href=\"{}\" title=\"{}\"><span underline=\"none\">{}</span></a>\n",
markup_escape_text(&uri.to_str()), // use resolved address for href
markup_escape_text(&link), // show original address for title
markup_escape_text(&name.join(" ")),
);
Some(Self {
// alt,
// date,
// external,
// link,
markup,
// uri,
})
}
// Getters
/* @TODO
pub fn alt(&self) -> &Option<GString> {
&self.alt
}
pub fn date(&self) -> &Option<GString> {
&self.date
}
pub fn external(&self) -> &bool {
&self.external
}
pub fn link(&self) -> &GString {
&self.link
}
pub fn uri(&self) -> &Uri {
&self.uri
}*/
pub fn markup(&self) -> &GString {
&self.markup
}
}

View file

@ -0,0 +1,17 @@
use gtk::glib::{gformat, markup_escape_text, GString};
pub struct Plain {
markup: GString,
}
impl Plain {
pub fn from(line: &str) -> Plain {
Self {
markup: gformat!("{}\n", markup_escape_text(line)),
}
}
pub fn markup(&self) -> &GString {
&self.markup
}
}

View file

@ -0,0 +1,27 @@
use gtk::{Align, Label};
pub struct Reader {
widget: Label,
}
impl Reader {
// Construct
pub fn new() -> Self {
Self {
widget: Label::builder()
.halign(Align::Start)
.valign(Align::Start)
.margin_start(8)
.margin_end(8)
.wrap(true)
.selectable(true)
.use_markup(true)
.build(),
}
}
// Getters
pub fn widget(&self) -> &Label {
&self.widget
}
}