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

View file

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

View file

@ -1,36 +1,14 @@
use super::Header;
use gtk::{
prelude::{ButtonExt, WidgetExt},
Button,
};
use std::{cell::RefCell, rc::Rc};
use gtk::Button;
pub trait Options {
fn options(header: &Rc<RefCell<Header>>) -> Self;
fn options() -> Self;
}
impl Options for Button {
fn options(header: &Rc<RefCell<Header>>) -> Self {
let button = Button::builder()
fn options() -> Self {
Button::builder()
.icon_name("emblem-system-symbolic")
.tooltip_text("Options")
.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
.build()
}
}

View file

@ -1,8 +1,9 @@
use super::Control;
use super::{Control, Header};
use gtk::{glib::Bytes, Button};
use std::{cell::RefCell, rc::Rc};
pub struct File {
header: Rc<RefCell<Header>>,
buffer: Rc<RefCell<Option<Bytes>>>,
pub button: Button,
}
@ -16,6 +17,11 @@ impl File {
};
// Init components
let header = Rc::new(RefCell::new(Header {
mime: None,
token: None,
}));
let buffer = Rc::new(RefCell::new(None));
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
will not able re-upload existing form on failure @TODO
// Getters
pub fn take_bytes(&self) -> Option<Bytes> {
self.buffer.borrow_mut().take()
} */
/// Get `Header` copy
/// * 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())
}
/// Get size
pub fn size(&self) -> Option<usize> {
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;
use super::Control;
use super::{Control, Header};
use gtk::{
glib::{Bytes, GString},
prelude::{TextBufferExt, TextViewExt},
TextView,
TextBuffer, TextView,
};
use std::rc::Rc;
use std::{cell::RefCell, rc::Rc};
pub trait Text {
fn text(control: &Rc<Control>) -> Self;
fn to_bytes(&self) -> Bytes;
fn to_gstring(&self) -> GString;
fn len(&self) -> usize;
fn count(&self) -> i32;
pub struct Text {
header: Rc<RefCell<Header>>,
pub text_view: TextView,
}
impl Text for TextView {
fn text(control: &Rc<Control>) -> Self {
impl Text {
// Constructors
/// Build new `Self`
pub fn build(control: &Rc<Control>) -> Self {
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();
text_view.buffer().connect_changed({
let control = control.clone();
let text_view = text_view.clone();
move |text_buffer| control.update(Some(text_view.len()), Some(text_buffer.char_count()))
move |text_buffer| {
control.update(
Some(gstring(text_buffer).len()),
Some(text_buffer.char_count()),
)
}
});
text_view
Self { header, text_view }
}
fn to_bytes(&self) -> Bytes {
Bytes::from(self.to_gstring().as_bytes())
// Getters
/// 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 {
let buffer = self.buffer();
self.buffer()
.text(&buffer.start_iter(), &buffer.end_iter(), true)
pub fn bytes(&self) -> Bytes {
Bytes::from(self.gstring().as_bytes())
}
fn count(&self) -> i32 {
self.buffer().char_count()
pub fn gstring(&self) -> GString {
gstring(&self.text_view.buffer())
}
fn len(&self) -> usize {
let buffer = self.buffer();
pub fn count(&self) -> i32 {
self.text_view.buffer().char_count()
}
buffer
.text(&buffer.start_iter(), &buffer.end_iter(), true)
.len()
pub fn len(&self) -> usize {
self.gstring().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)
}