use separated headers for tabs, use plain/text by default for text

This commit is contained in:
yggverse 2025-02-07 23:16:44 +02:00
parent 0caca74efe
commit 005b7574ca
5 changed files with 147 additions and 88 deletions

View file

@ -17,17 +17,13 @@ pub trait Titan {
impl Titan for gtk::Box { impl Titan for gtk::Box {
fn titan(callback: impl Fn(Header, Bytes, Box<dyn Fn()>) + 'static) -> Self { fn titan(callback: impl Fn(Header, Bytes, Box<dyn Fn()>) + 'static) -> Self {
use gtk::{glib::uuid_string_random, prelude::ButtonExt, Label, TextView}; use gtk::{glib::uuid_string_random, prelude::ButtonExt, Label};
use std::{cell::RefCell, rc::Rc}; use std::rc::Rc;
// Init components // Init components
let header = Rc::new(RefCell::new(Header { let control = Rc::new(Control::build());
mime: None,
token: None,
}));
let control = Rc::new(Control::build(&header));
let file = Rc::new(File::build(&control)); let file = Rc::new(File::build(&control));
let text = TextView::text(&control); let text = Rc::new(Text::build(&control));
let notebook = { let notebook = {
let notebook = Notebook::builder() let notebook = Notebook::builder()
@ -35,7 +31,7 @@ impl Titan for gtk::Box {
.show_border(false) .show_border(false)
.build(); .build();
notebook.append_page(&text, Some(&Label::tab("Text"))); notebook.append_page(&text.text_view, Some(&Label::tab("Text")));
notebook.append_page(&file.button, Some(&Label::tab("File"))); notebook.append_page(&file.button, Some(&Label::tab("File")));
notebook.connect_switch_page({ notebook.connect_switch_page({
@ -75,14 +71,49 @@ impl Titan for gtk::Box {
}; };
// Init events // Init events
control.upload.connect_clicked(move |this| { control.options.connect_clicked({
let text = text.clone();
let file = file.clone();
let notebook = notebook.clone();
move |this| {
use gtk::prelude::WidgetExt;
this.set_sensitive(false); // lock
let page = notebook.current_page().unwrap();
match page {
0 => text.header(),
1 => file.header(),
_ => panic!(),
}
.dialog(Some(this), {
let this = this.clone();
let text = text.clone();
let file = file.clone();
move |header| {
match page {
0 => text.set_header(header),
1 => file.set_header(header),
_ => panic!(),
};
this.set_sensitive(true); // unlock
}
})
}
});
control.upload.connect_clicked({
move |this| {
use control::Upload; use control::Upload;
this.set_uploading(); this.set_uploading();
let page = notebook.current_page().unwrap();
callback( callback(
header.borrow().clone(), // keep original header to have re-send ability match page {
match notebook.current_page().unwrap() { 0 => text.header(),
0 => text.to_bytes(), 1 => file.header(),
1 => file.to_bytes().unwrap(), _ => panic!(),
},
match page {
0 => text.bytes(),
1 => file.bytes().unwrap(),
_ => panic!(), _ => panic!(),
}, },
Box::new({ Box::new({
@ -90,6 +121,7 @@ impl Titan for gtk::Box {
move || this.set_resend() // re-activate button on failure move || this.set_resend() // re-activate button on failure
}), }),
) )
}
}); });
g_box g_box

View file

@ -2,18 +2,17 @@ mod counter;
mod options; mod options;
mod upload; mod upload;
use super::Header;
use counter::Counter; use counter::Counter;
use gtk::{ use gtk::{
prelude::{BoxExt, WidgetExt}, prelude::{BoxExt, WidgetExt},
Align, Box, Button, Label, Orientation, Align, Box, Button, Label, Orientation,
}; };
use options::Options; use options::Options;
use std::{cell::RefCell, rc::Rc};
pub use upload::Upload; pub use upload::Upload;
pub struct Control { pub struct Control {
pub counter: Label, pub counter: Label,
pub options: Button,
pub upload: Button, pub upload: Button,
pub g_box: Box, pub g_box: Box,
} }
@ -22,10 +21,10 @@ impl Control {
// Constructors // Constructors
/// Build new `Self` /// Build new `Self`
pub fn build(header: &Rc<RefCell<Header>>) -> Self { pub fn build() -> Self {
// Init components // Init components
let counter = Label::counter(); let counter = Label::counter();
let options = Button::options(header); let options = Button::options();
let upload = Button::upload(); let upload = Button::upload();
// Init main widget // Init main widget
@ -46,6 +45,7 @@ impl Control {
// Return activated struct // Return activated struct
Self { Self {
counter, counter,
options,
upload, upload,
g_box, g_box,
} }

View file

@ -1,36 +1,14 @@
use super::Header; use gtk::Button;
use gtk::{
prelude::{ButtonExt, WidgetExt},
Button,
};
use std::{cell::RefCell, rc::Rc};
pub trait Options { pub trait Options {
fn options(header: &Rc<RefCell<Header>>) -> Self; fn options() -> Self;
} }
impl Options for Button { impl Options for Button {
fn options(header: &Rc<RefCell<Header>>) -> Self { fn options() -> Self {
let button = Button::builder() Button::builder()
.icon_name("emblem-system-symbolic") .icon_name("emblem-system-symbolic")
.tooltip_text("Options") .tooltip_text("Options")
.build(); .build()
button.connect_clicked({
let header = header.clone();
move |this| {
this.set_sensitive(false); // lock
header.take().dialog(Some(this), {
let this = this.clone();
let header = header.clone();
move |options| {
header.replace(options);
this.set_sensitive(true); // unlock
}
})
}
});
button
} }
} }

View file

@ -1,8 +1,9 @@
use super::Control; use super::{Control, Header};
use gtk::{glib::Bytes, Button}; use gtk::{glib::Bytes, Button};
use std::{cell::RefCell, rc::Rc}; use std::{cell::RefCell, rc::Rc};
pub struct File { pub struct File {
header: Rc<RefCell<Header>>,
buffer: Rc<RefCell<Option<Bytes>>>, buffer: Rc<RefCell<Option<Bytes>>>,
pub button: Button, pub button: Button,
} }
@ -16,6 +17,11 @@ impl File {
}; };
// Init components // Init components
let header = Rc::new(RefCell::new(Header {
mime: None,
token: None,
}));
let buffer = Rc::new(RefCell::new(None)); let buffer = Rc::new(RefCell::new(None));
let button = Button::builder() let button = Button::builder()
@ -77,21 +83,37 @@ impl File {
} }
}); });
Self { buffer, button } Self {
header,
buffer,
button,
}
} }
/* this method is less-expensive but not useful as user // Getters
will not able re-upload existing form on failure @TODO
pub fn take_bytes(&self) -> Option<Bytes> { /// Get `Header` copy
self.buffer.borrow_mut().take() /// * borrow, do not take to have form re-send ability
} */ pub fn header(&self) -> Header {
self.header.borrow().clone()
}
pub fn to_bytes(&self) -> Option<Bytes> { /// Get cloned [Bytes](https://docs.gtk.org/glib/struct.Bytes.html)
// * borrow, do not take to have form re-send ability
pub fn bytes(&self) -> Option<Bytes> {
self.buffer.borrow().as_ref().map(|bytes| bytes.clone()) self.buffer.borrow().as_ref().map(|bytes| bytes.clone())
} }
/// Get size
pub fn size(&self) -> Option<usize> { pub fn size(&self) -> Option<usize> {
self.buffer.borrow().as_ref().map(|bytes| bytes.len()) self.buffer.borrow().as_ref().map(|bytes| bytes.len())
} }
// Setters
/// Replace current `Header`
/// * return previous object
pub fn set_header(&self, header: Header) -> Header {
self.header.replace(header)
}
} }

View file

@ -1,55 +1,82 @@
mod form; mod form;
use super::Control; use super::{Control, Header};
use gtk::{ use gtk::{
glib::{Bytes, GString}, glib::{Bytes, GString},
prelude::{TextBufferExt, TextViewExt}, prelude::{TextBufferExt, TextViewExt},
TextView, TextBuffer, TextView,
}; };
use std::rc::Rc; use std::{cell::RefCell, rc::Rc};
pub trait Text { pub struct Text {
fn text(control: &Rc<Control>) -> Self; header: Rc<RefCell<Header>>,
fn to_bytes(&self) -> Bytes; pub text_view: TextView,
fn to_gstring(&self) -> GString;
fn len(&self) -> usize;
fn count(&self) -> i32;
} }
impl Text for TextView { impl Text {
fn text(control: &Rc<Control>) -> Self { // Constructors
/// Build new `Self`
pub fn build(control: &Rc<Control>) -> Self {
use form::Form; use form::Form;
// Init components
let header = Rc::new(RefCell::new(Header {
mime: Some("text/plain".into()), // some servers may reject request without MIME @TODO optional defaults
token: None,
}));
// Init main widget
let text_view = TextView::form(); let text_view = TextView::form();
text_view.buffer().connect_changed({ text_view.buffer().connect_changed({
let control = control.clone(); let control = control.clone();
let text_view = text_view.clone(); move |text_buffer| {
move |text_buffer| control.update(Some(text_view.len()), Some(text_buffer.char_count())) control.update(
Some(gstring(text_buffer).len()),
Some(text_buffer.char_count()),
)
}
}); });
text_view Self { header, text_view }
} }
fn to_bytes(&self) -> Bytes { // Getters
Bytes::from(self.to_gstring().as_bytes())
/// Get `Header` copy
/// * borrow, do not take to have form re-send ability
pub fn header(&self) -> Header {
self.header.borrow().clone()
} }
fn to_gstring(&self) -> GString { pub fn bytes(&self) -> Bytes {
let buffer = self.buffer(); Bytes::from(self.gstring().as_bytes())
self.buffer()
.text(&buffer.start_iter(), &buffer.end_iter(), true)
} }
fn count(&self) -> i32 { pub fn gstring(&self) -> GString {
self.buffer().char_count() gstring(&self.text_view.buffer())
} }
fn len(&self) -> usize { pub fn count(&self) -> i32 {
let buffer = self.buffer(); self.text_view.buffer().char_count()
}
buffer pub fn len(&self) -> usize {
.text(&buffer.start_iter(), &buffer.end_iter(), true) self.gstring().len()
.len() }
// Setters
/// Replace current `Header`
/// * return previous object
pub fn set_header(&self, header: Header) -> Header {
self.header.replace(header)
} }
} }
// Tools
fn gstring(text_buffer: &TextBuffer) -> GString {
text_buffer.text(&text_buffer.start_iter(), &text_buffer.end_iter(), true)
}