implement gesture actions for link tags

This commit is contained in:
yggverse 2024-10-13 06:09:15 +03:00
parent 1a410860e8
commit fb44222e4e
2 changed files with 102 additions and 91 deletions

View File

@ -6,99 +6,83 @@ use parser::link::Link;
use widget::Widget; use widget::Widget;
use gtk::{ use gtk::{
gdk::{BUTTON_MIDDLE, BUTTON_PRIMARY},
gio::SimpleAction, gio::SimpleAction,
glib::{GString, TimeZone, Uri}, glib::{GString, TimeZone, Uri},
prelude::{TextBufferExt, TextBufferExtManual}, prelude::{ActionExt, TextBufferExt, TextBufferExtManual, TextViewExt, ToVariant},
TextBuffer, TextTag, TextTagTable, TextView, WrapMode, EventControllerMotion, GestureClick, TextBuffer, TextTag, TextView, TextWindowType, WrapMode,
}; };
use std::sync::Arc; use std::{collections::HashMap, sync::Arc};
pub struct Reader { pub struct Reader {
title: Option<GString>, title: Option<GString>,
// css: CssProvider,
widget: Arc<Widget>, widget: Arc<Widget>,
} }
impl Reader { impl Reader {
// Construct // Construct
pub fn new_arc(gemtext: &str, base: &Uri, action_page_open: Arc<SimpleAction>) -> Arc<Self> { pub fn new_arc(gemtext: &str, base: &Uri, action_page_open: Arc<SimpleAction>) -> Arc<Self> {
// Init title // Init default values
let mut title = None; let mut title = None;
// Init tag table // Init HashMap storage for event controllers
let tags = TextTagTable::new(); let mut links: HashMap<TextTag, Uri> = HashMap::new();
// Init header tags // Init new text buffer
let h1 = TextTag::builder() let buffer = TextBuffer::new(None);
.name("h1")
.scale(1.6)
.weight(500)
.wrap_mode(gtk::WrapMode::Word)
.build();
tags.add(&h1);
let h2 = TextTag::builder()
.name("h2")
.scale(1.4)
.weight(400)
.wrap_mode(gtk::WrapMode::Word)
.build();
tags.add(&h2);
let h3 = TextTag::builder()
.name("h3")
.scale(1.2)
.weight(400)
.wrap_mode(WrapMode::Word)
.build();
tags.add(&h3);
// Init link tag
let link = TextTag::builder()
.name("link")
.wrap_mode(WrapMode::Word)
.build();
tags.add(&link);
// Parse lines
let buffer = TextBuffer::new(Some(&tags));
// Parse gemtext lines
for line in gemtext.lines() { for line in gemtext.lines() {
// Is header // Is header
if let Some(header) = Header::from(line) { if let Some(header) = Header::from(line) {
// Detect level // Build tag from level parsed
let tag = match header.level { let tag = match header.level {
parser::header::Level::H1 => "h1", parser::header::Level::H1 => TextTag::builder()
parser::header::Level::H2 => "h2", .scale(1.6)
parser::header::Level::H3 => "h3", .weight(500)
.wrap_mode(gtk::WrapMode::Word)
.build(),
parser::header::Level::H2 => TextTag::builder()
.scale(1.4)
.weight(400)
.wrap_mode(gtk::WrapMode::Word)
.build(),
parser::header::Level::H3 => TextTag::builder()
.scale(1.2)
.weight(400)
.wrap_mode(WrapMode::Word)
.build(),
}; };
// Insert tag line // Register tag in buffer
buffer.insert_with_tags_by_name( buffer.tag_table().add(&tag);
&mut buffer.end_iter(),
header.value.as_str(),
&[tag],
);
// Append value to buffer
buffer.insert_with_tags(&mut buffer.end_iter(), header.value.as_str(), &[&tag]);
buffer.insert(&mut buffer.end_iter(), "\n"); buffer.insert(&mut buffer.end_iter(), "\n");
// Set title if empty, on first document header match // Update reader title using first gemtext header match
// this feature wanted to update parent elements like tab title
if title == None { if title == None {
title = Some(header.value.clone()); title = Some(header.value.clone());
} }
// Skip other actions for this line
continue; continue;
} }
// Is link // Is link
if let Some(link) = Link::from(line, Some(base), Some(&TimeZone::local())) { if let Some(link) = Link::from(line, Some(base), Some(&TimeZone::local())) {
// Build link alt from optional values // Init new tag for link
let tag = TextTag::builder().wrap_mode(WrapMode::Word).build();
// Append tag to buffer
buffer.tag_table().add(&tag);
// Append tag to HashMap storage
links.insert(tag.clone(), link.uri.clone());
// Create vector for alt values
let mut alt = Vec::new(); let mut alt = Vec::new();
// Append external indicator on exist // Append external indicator on exist
@ -116,47 +100,63 @@ impl Reader {
} }
} }
// Append alt on exist or use URL // Append alt value on exist or use URL
alt.push(match link.alt { alt.push(match link.alt {
Some(alt) => alt.to_string(), Some(alt) => alt.to_string(),
None => link.uri.to_string(), None => link.uri.to_string(),
}); });
buffer.insert_with_tags_by_name(&mut buffer.end_iter(), &alt.join(" "), &["link"]); // Append alt vector values to buffer
buffer.insert_with_tags(&mut buffer.end_iter(), &alt.join(" "), &[&tag]);
buffer.insert(&mut buffer.end_iter(), "\n"); buffer.insert(&mut buffer.end_iter(), "\n");
// Skip other actions for this line
continue; continue;
} }
// Nothing match, use plain text @TODO // Nothing match tags, use plain text @TODO
buffer.insert(&mut buffer.end_iter(), line); buffer.insert(&mut buffer.end_iter(), line);
buffer.insert(&mut buffer.end_iter(), "\n"); buffer.insert(&mut buffer.end_iter(), "\n");
} }
// Init widget // Init additional controllers
let widget = Widget::new_arc(&buffer); let primary_button_controller = GestureClick::builder().button(BUTTON_PRIMARY).build();
let middle_button_controller = GestureClick::builder().button(BUTTON_MIDDLE).build();
let motion_controller = EventControllerMotion::new();
// Connect actions // Init widget
/* @TODO let widget = Widget::new_arc(
widget.connect_activate_link(move |_, href| { &buffer,
// Detect requested protocol primary_button_controller.clone(),
if let Ok(uri) = Uri::parse(&href, UriFlags::NONE) { middle_button_controller.clone(),
return match uri.scheme().as_str() { motion_controller.clone(),
);
// Init events
primary_button_controller.connect_released({
let action_page_open = action_page_open.clone();
let gobject = widget.gobject().clone();
move |_, _, x, y| {
gobject.window_to_buffer_coords(TextWindowType::Widget, x as i32, y as i32);
if let Some(iter) = gobject.iter_at_location(x as i32, y as i32) {
for tag in iter.tags() {
if let Some(uri) = links.get(&tag) {
match uri.scheme().as_str() {
"gemini" => { "gemini" => {
// Open new page // Open new page
action_page_open.activate(Some(&uri.to_str().to_variant())); action_page_open.activate(Some(&uri.to_str().to_variant()));
// Prevent link open in external application
Propagation::Stop
} }
// Protocol not supported _ => (), // Protocol not supported, delegate to the external app @TODO
_ => Propagation::Proceed,
}; };
} }
}
}
}
});
// Delegate unparsable // @TODO on middle-click, on hover events
Propagation::Proceed // primary_button_controller(|_, _, _, _| {});
}); */ // motion_controller.connect_motion(|_, _, _| {});
// Result // Result
Arc::new(Self { title, widget }) Arc::new(Self { title, widget })

View File

@ -1,4 +1,6 @@
use gtk::{TextBuffer, TextView, WrapMode}; use gtk::{
prelude::WidgetExt, EventControllerMotion, GestureClick, TextBuffer, TextView, WrapMode,
};
use std::sync::Arc; use std::sync::Arc;
pub struct Widget { pub struct Widget {
@ -7,16 +9,25 @@ pub struct Widget {
impl Widget { impl Widget {
// Construct // Construct
pub fn new_arc(buffer: &TextBuffer) -> Arc<Self> { pub fn new_arc(
Arc::new(Self { buffer: &TextBuffer,
gobject: TextView::builder() primary_button_controller: GestureClick,
middle_button_controller: GestureClick,
motion_controller: EventControllerMotion,
) -> Arc<Self> {
let gobject = TextView::builder()
.editable(false) .editable(false)
.cursor_visible(false) .cursor_visible(false)
.wrap_mode(WrapMode::Word) .wrap_mode(WrapMode::Word)
.vexpand(true) .vexpand(true)
.buffer(buffer) .buffer(buffer)
.build(), .build();
})
gobject.add_controller(primary_button_controller);
gobject.add_controller(middle_button_controller);
gobject.add_controller(motion_controller);
Arc::new(Self { gobject })
} }
// Getters // Getters