diff --git a/Cargo.toml b/Cargo.toml index cb84fd6..37b801f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -gtk = "^0.14.0" -relm = "^0.22.0" -relm-derive = "^0.22.0" +druid = { git = "https://github.com/linebender/druid.git" } uwuify = "0.2.2" diff --git a/src/main.rs b/src/main.rs index 1b43a80..a38da6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,127 +1,87 @@ -use gtk::{ - glib::BoolError, - prelude::{FrameExt, OrientableExt, ScrolledWindowExt, TextBufferExt, TextViewExt, WidgetExt}, - Inhibit, - Orientation, - PolicyType, - WrapMode, +use druid::{ + widget::{Flex, Label, TextBox}, + AppLauncher, + Data, + Lens, + LensExt, + PlatformError, + Widget, + WidgetExt, + WindowDesc, }; -use relm::{connect, Relm, Widget}; -use relm_derive::{widget, Msg}; +use std::{cell::RefCell, rc::Rc}; +use util::{ImmutLens, Key}; +use uwuifier::{round_up16, uwuify_sse}; -fn main() -> Result<(), BoolError> { - Win::run(()) +mod util; + +fn main() -> Result<(), PlatformError> { + AppLauncher::with_window(WindowDesc::new(build_ui()).title("guwu")).launch(AppData::default()) } -pub struct Model { - relm: Relm, - uwubuf1: Vec, - uwubuf2: Vec, +#[derive(Default, Data, Lens, Clone)] +struct AppData { + input: String, + output: String, + + uwubuf1: Rc>>, + uwubuf2: Rc>>, } -impl Model { - pub fn prep_uwubufs(&mut self, len: usize) { - if len > self.uwubuf1.len() { - self.uwubuf1.resize(len, 0); +impl AppData { + fn alloc_uwubufs(&self, len: usize) { + let mut uwubuf1 = self.uwubuf1.borrow_mut(); + let mut uwubuf2 = self.uwubuf2.borrow_mut(); + + if len > uwubuf1.len() { + uwubuf1.resize(len, 0); } - if len > self.uwubuf2.len() { - self.uwubuf2.resize(len, 0); - } - } -} - -#[derive(Clone, Msg)] -pub enum Msg { - Quit, - Edited, -} - -#[widget] -impl Widget for Win { - fn model(relm: &Relm, _: ()) -> Model { - Model { - relm: relm.clone(), - uwubuf1: vec![], - uwubuf2: vec![], + if len > uwubuf2.len() { + uwubuf2.resize(len, 0); } } - fn update(&mut self, event: Msg) { - match event { - Msg::Quit => gtk::main_quit(), - Msg::Edited => { - if let (Some(inbuf), Some(outbuf)) = - (self.widgets.input.buffer(), self.widgets.output.buffer()) - { - if let Some(text) = inbuf.text(&inbuf.start_iter(), &inbuf.end_iter(), true) { - let len = uwuifier::round_up16(text.len()) * 16; - self.model.prep_uwubufs(len); - let uwu = uwuifier::uwuify_sse( - text.as_bytes(), - &mut self.model.uwubuf1[..len], - &mut self.model.uwubuf2[..len], - ); + fn uwu(&mut self) { + self.alloc_uwubufs(round_up16(self.input.len()) * 16); - if let Ok(uwu) = std::str::from_utf8(uwu) { - outbuf.set_text(uwu); - } - } - } - }, - } - } + let mut uwubuf1 = self.uwubuf1.borrow_mut(); + let mut uwubuf2 = self.uwubuf2.borrow_mut(); - fn init_view(&mut self) { - self.widgets - .input_scroll - .set_policy(PolicyType::Never, PolicyType::Automatic); + let uwuified = uwuify_sse(self.input.as_bytes(), &mut uwubuf1, &mut uwubuf2); - self.widgets - .output_scroll - .set_policy(PolicyType::Never, PolicyType::Automatic); - - if let Some(buf) = self.widgets.input.buffer() { - connect!(buf, connect_changed(_), self.model.relm, Msg::Edited); - } - } - - view! { - gtk::Window { - gtk::Box { - orientation: Orientation::Horizontal, - - gtk::Frame { - label: Some("Input"), - - #[name = "input_scroll"] - gtk::ScrolledWindow { - hexpand: true, - vexpand: true, - #[name = "input"] - gtk::TextView { - wrap_mode: WrapMode::WordChar, - }, - }, - }, - - gtk::Frame { - label: Some("Output"), - - #[name = "output_scroll"] - gtk::ScrolledWindow { - hexpand: true, - vexpand: true, - #[name = "output"] - gtk::TextView { - editable: false, - wrap_mode: WrapMode::WordChar, - }, - }, - }, - }, - - delete_event(_, _) => (Msg::Quit, Inhibit(false)), - } + // SAFETY: uwuify should always return valid UTF-8 if the input string was valid + // UTF-8, which we can be sure of, as it's a `String`. + self.output = unsafe { String::from_utf8_unchecked(uwuified.to_vec()) }; } } + +fn build_ui() -> impl Widget { + Flex::row() + .with_flex_child( + Flex::column() + .with_child(Label::new("Input")) + .with_flex_child( + TextBox::multiline() + .with_placeholder("Type here...") + .expand() + .lens(AppData::input) + .controller(Key::new(|_, data: &mut AppData, _, _| data.uwu())), + 1., + ), + 1., + ) + .with_spacer(4.) + .with_flex_child( + Flex::column() + .with_child(Label::new("Output")) + .with_flex_child( + TextBox::multiline() + .with_placeholder("UwU") + .lens(AppData::output.then(ImmutLens)) + .expand(), + 1., + ), + 1., + ) +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..22ee08c --- /dev/null +++ b/src/util.rs @@ -0,0 +1,42 @@ +use druid::{widget::Controller, Data, Env, Event, EventCtx, KeyEvent, Widget, Lens}; + +pub struct Key { + action: Box, +} + +impl Key { + pub fn new(action: F) -> Self { + Self { + action: Box::new(action), + } + } +} + +impl> Controller for Key { + fn event( + &mut self, + child: &mut W, + ctx: &mut EventCtx, + event: &druid::Event, + data: &mut T, + env: &Env, + ) { + child.event(ctx, event, data, env); + + if let Event::KeyUp(e) = event { + (self.action)(ctx, data, env, e); + } + } +} + +pub struct ImmutLens; + +impl Lens for ImmutLens { + fn with V>(&self, data: &T, f: F) -> V { + f(data) + } + + fn with_mut V>(&self, data: &mut T, f: F) -> V { + f(&mut data.clone()) + } +}