diff --git a/src/app/browser/window/tab/item/client.rs b/src/app/browser/window/tab/item/client.rs index 974f1906..55e8bb8d 100644 --- a/src/app/browser/window/tab/item/client.rs +++ b/src/app/browser/window/tab/item/client.rs @@ -84,6 +84,7 @@ impl Client { // route by scheme Ok(uri) => match uri.scheme().as_str() { "gemini" => driver.gemini.handle(uri, feature, cancellable), + "titan" => subject.page.input.set_new_titan(|_data| todo!()), scheme => { // no scheme match driver, complete with failure message let status = subject.page.content.to_status_failure(); diff --git a/src/app/browser/window/tab/item/page/input.rs b/src/app/browser/window/tab/item/page/input.rs index 75672da5..b12a6d8c 100644 --- a/src/app/browser/window/tab/item/page/input.rs +++ b/src/app/browser/window/tab/item/page/input.rs @@ -1,5 +1,6 @@ mod response; mod sensitive; +mod titan; mod widget; use super::TabAction; @@ -7,6 +8,7 @@ use gtk::glib::Uri; use response::Response; use sensitive::Sensitive; use std::rc::Rc; +use titan::Titan; use widget::Widget; pub struct Input { @@ -62,4 +64,9 @@ impl Input { .g_box, )); } + + pub fn set_new_titan(&self, on_send: impl Fn(&[u8]) + 'static) { + self.widget + .update(Some(&Titan::build(on_send).widget.g_box)); + } } diff --git a/src/app/browser/window/tab/item/page/input/response/control/counter/widget.rs b/src/app/browser/window/tab/item/page/input/response/control/counter/widget.rs index 298e509d..23030056 100644 --- a/src/app/browser/window/tab/item/page/input/response/control/counter/widget.rs +++ b/src/app/browser/window/tab/item/page/input/response/control/counter/widget.rs @@ -23,8 +23,11 @@ impl Widget { match bytes_left { Some(value) => { // Update color on chars left reached - self.label - .set_css_classes(&[if value > 0 { "success" } else { "error" }]); // @TODO add warning step? + self.label.set_css_classes(&[if value.is_positive() { + "success" + } else { + "error" + }]); // @TODO add warning step? // Update text self.label.set_label(&value.to_string()); diff --git a/src/app/browser/window/tab/item/page/input/titan.rs b/src/app/browser/window/tab/item/page/input/titan.rs new file mode 100644 index 00000000..5df9f53f --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan.rs @@ -0,0 +1,52 @@ +mod control; +mod form; +mod title; +mod widget; + +use control::Control; +use form::Form; +use title::Title; +use widget::Widget; + +use gtk::{gio::SimpleAction, glib::uuid_string_random}; +use std::rc::Rc; + +pub struct Titan { + // Components + pub widget: Rc, +} + +impl Titan { + // Constructors + + /// Build new `Self` + pub fn build(on_send: impl Fn(&[u8]) + 'static) -> Self { + // Init local actions + let action_update = SimpleAction::new(&uuid_string_random(), None); + let action_send = SimpleAction::new(&uuid_string_random(), None); + + // Init components + let control = Rc::new(Control::build(action_send.clone())); + let form = Rc::new(Form::build(action_update.clone())); + let title = Title::build(None); + + // Init widget + let widget = Rc::new(Widget::build( + &title.label, + &form.widget.text_view, + &control.widget.g_box, + )); + + // Init events + action_update.connect_activate({ + let control = control.clone(); + let form = form.clone(); + move |_, _| control.update(Some(form.widget.text().as_bytes().len())) + }); + + action_send.connect_activate(move |_, _| on_send(form.widget.text().as_bytes())); + + // Return activated struct + Self { widget } + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/control.rs b/src/app/browser/window/tab/item/page/input/titan/control.rs new file mode 100644 index 00000000..7668e6e1 --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/control.rs @@ -0,0 +1,47 @@ +mod counter; +mod send; +mod widget; + +use counter::Counter; +use send::Send; +use widget::Widget; + +use gtk::gio::SimpleAction; +use std::rc::Rc; + +pub struct Control { + pub counter: Rc, + pub send: Rc, + pub widget: Rc, +} + +impl Control { + // Constructors + + /// Build new `Self` + pub fn build(action_send: SimpleAction) -> Self { + // Init components + let counter = Rc::new(Counter::new()); + let send = Rc::new(Send::build(action_send)); + + // Init widget + let widget = Rc::new(Widget::build(&counter.label, &send.button)); + + // Return activated struct + Self { + counter, + send, + widget, + } + } + + // Actions + pub fn update(&self, bytes_total: Option) { + // Update children components + self.counter.update(bytes_total); + self.send.update(match bytes_total { + Some(total) => total > 0, + None => false, + }); + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/control/counter.rs b/src/app/browser/window/tab/item/page/input/titan/control/counter.rs new file mode 100644 index 00000000..ae61f391 --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/control/counter.rs @@ -0,0 +1,31 @@ +use gtk::{prelude::WidgetExt, Label}; + +pub struct Counter { + pub label: Label, +} + +impl Default for Counter { + fn default() -> Self { + Self::new() + } +} + +impl Counter { + // Construct + pub fn new() -> Self { + Self { + label: Label::builder().css_classes(["dim-label"]).build(), // @TODO use `dimmed` in Adw 1.6, + } + } + + // Actions + pub fn update(&self, bytes_total: Option) { + match bytes_total { + Some(value) => { + self.label.set_label(&value.to_string()); + self.label.set_visible(value > 0); + } + None => self.label.set_visible(false), + } + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/control/send.rs b/src/app/browser/window/tab/item/page/input/titan/control/send.rs new file mode 100644 index 00000000..0d55a0f7 --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/control/send.rs @@ -0,0 +1,38 @@ +use gtk::{ + gio::SimpleAction, + prelude::{ActionExt, ButtonExt, WidgetExt}, + Button, +}; + +pub struct Send { + pub button: Button, +} + +impl Send { + // Constructors + + /// Build new `Self` + pub fn build(action_send: SimpleAction) -> Self { + // Init main widget + let button = Button::builder() + .css_classes(["accent"]) // | `suggested-action` + .label("Send") + .sensitive(false) + .build(); + + // Init events + button.connect_clicked({ + move |_| { + action_send.activate(None); + } + }); + + // Return activated `Self` + Self { button } + } + + // Actions + pub fn update(&self, is_sensitive: bool) { + self.button.set_sensitive(is_sensitive); + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/control/widget.rs b/src/app/browser/window/tab/item/page/input/titan/control/widget.rs new file mode 100644 index 00000000..7242866e --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/control/widget.rs @@ -0,0 +1,27 @@ +use gtk::{prelude::BoxExt, Align, Box, Button, Label, Orientation}; + +const SPACING: i32 = 8; + +pub struct Widget { + pub g_box: Box, +} + +impl Widget { + // Constructors + + /// Build new `Self` + pub fn build(limit: &Label, send: &Button) -> Self { + // Init main widget + let g_box = Box::builder() + .halign(Align::End) + .orientation(Orientation::Horizontal) + .spacing(SPACING) + .build(); + + g_box.append(limit); + g_box.append(send); + + // Return new `Self` + Self { g_box } + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/form.rs b/src/app/browser/window/tab/item/page/input/titan/form.rs new file mode 100644 index 00000000..1fbc0bbd --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/form.rs @@ -0,0 +1,21 @@ +mod widget; + +use widget::Widget; + +use gtk::gio::SimpleAction; +use std::rc::Rc; + +pub struct Form { + pub widget: Rc, +} + +impl Form { + // Constructors + + /// Build new `Self` + pub fn build(action_update: SimpleAction) -> Self { + Self { + widget: Rc::new(Widget::build(action_update)), + } + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/form/widget.rs b/src/app/browser/window/tab/item/page/input/titan/form/widget.rs new file mode 100644 index 00000000..be21415e --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/form/widget.rs @@ -0,0 +1,63 @@ +use gtk::{ + gio::SimpleAction, + glib::GString, + prelude::{ActionExt, TextBufferExt, TextViewExt, WidgetExt}, + TextView, WrapMode, +}; +use libspelling::{Checker, TextBufferAdapter}; +use sourceview::Buffer; + +const MARGIN: i32 = 8; + +pub struct Widget { + pub text_view: TextView, +} + +impl Widget { + // Constructors + + /// Build new `Self` + pub fn build(action_update: SimpleAction) -> Self { + // Init [SourceView](https://gitlab.gnome.org/GNOME/gtksourceview) type buffer + let buffer = Buffer::builder().build(); + + // Init [libspelling](https://gitlab.gnome.org/GNOME/libspelling) + let checker = Checker::default(); + let adapter = TextBufferAdapter::new(&buffer, &checker); + adapter.set_enabled(true); + + // Init main widget + let text_view = TextView::builder() + .bottom_margin(MARGIN) + .buffer(&buffer) + .css_classes(["frame", "view"]) + .extra_menu(&adapter.menu_model()) + .left_margin(MARGIN) + .margin_bottom(MARGIN / 4) + .right_margin(MARGIN) + .top_margin(MARGIN) + .wrap_mode(WrapMode::Word) + .build(); + + text_view.insert_action_group("spelling", Some(&adapter)); + + // Init events + text_view.buffer().connect_changed(move |_| { + action_update.activate(None); + }); + + text_view.connect_realize(move |this| { + this.grab_focus(); + }); + + // Return activated `Self` + Self { text_view } + } + + // Getters + + pub fn text(&self) -> GString { + let buffer = self.text_view.buffer(); + buffer.text(&buffer.start_iter(), &buffer.end_iter(), true) + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/title.rs b/src/app/browser/window/tab/item/page/input/titan/title.rs new file mode 100644 index 00000000..970422be --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/title.rs @@ -0,0 +1,21 @@ +use gtk::{Align, Label}; + +pub struct Title { + pub label: Label, +} + +impl Title { + // Constructors + + /// Build new `Self` + pub fn build(title: Option<&str>) -> Self { + Self { + label: Label::builder() + .css_classes(["heading"]) + .halign(Align::Start) + .label(title.unwrap_or("Titan input")) + .visible(false) + .build(), + } + } +} diff --git a/src/app/browser/window/tab/item/page/input/titan/widget.rs b/src/app/browser/window/tab/item/page/input/titan/widget.rs new file mode 100644 index 00000000..857b6cb4 --- /dev/null +++ b/src/app/browser/window/tab/item/page/input/titan/widget.rs @@ -0,0 +1,30 @@ +use gtk::{prelude::BoxExt, Box, Label, Orientation, TextView}; + +const MARGIN: i32 = 6; +const SPACING: i32 = 8; + +pub struct Widget { + pub g_box: Box, +} + +impl Widget { + // Constructors + + /// Build new `Self` + pub fn build(title: &Label, response: &TextView, control: &Box) -> Self { + let g_box = Box::builder() + .margin_bottom(MARGIN) + .margin_end(MARGIN) + .margin_start(MARGIN) + .margin_top(MARGIN) + .spacing(SPACING) + .orientation(Orientation::Vertical) + .build(); + + g_box.append(title); + g_box.append(response); + g_box.append(control); + + Self { g_box } + } +}