From f2bac2a237f75ed9c1029cdca6907ef9796edb9b Mon Sep 17 00:00:00 2001 From: Shadow Cat Date: Sat, 15 Nov 2025 01:01:43 -0500 Subject: [PATCH] login screen --- Cargo.lock | 32 +++++----- Cargo.toml | 2 +- src/bin/client.rs | 4 ++ src/client/app.rs | 43 +++++++++++-- src/client/input.rs | 2 +- src/client/mod.rs | 108 +++++++------------------------- src/client/render/mod.rs | 6 +- src/client/ui.rs | 131 +++++++++++++++++++++++++++++++++++++++ src/net/client.rs | 13 +++- 9 files changed, 228 insertions(+), 113 deletions(-) create mode 100644 src/client/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 2c3bac5..58d2bb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,6 +1188,21 @@ dependencies = [ "syn", ] +[[package]] +name = "iris" +version = "0.1.0" +dependencies = [ + "arboard", + "bytemuck", + "cosmic-text", + "fxhash", + "image", + "pollster", + "unicode-segmentation", + "wgpu", + "winit", +] + [[package]] name = "itertools" version = "0.12.1" @@ -1909,12 +1924,12 @@ dependencies = [ "anyhow", "arboard", "directories-next", + "iris", "pollster", "quinn", "rcgen", "tokio", "tracing", - "ui", "wgpu", "winit", ] @@ -3225,21 +3240,6 @@ dependencies = [ "core_maths", ] -[[package]] -name = "ui" -version = "0.1.0" -dependencies = [ - "arboard", - "bytemuck", - "cosmic-text", - "fxhash", - "image", - "pollster", - "unicode-segmentation", - "wgpu", - "winit", -] - [[package]] name = "unicode-bidi" version = "0.3.18" diff --git a/Cargo.toml b/Cargo.toml index 0a6dd97..6363ba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,6 @@ quinn = { version = "0.11.9", features = ["rustls-aws-lc-rs"] } rcgen = "0.14.5" tokio = { version = "1.48.0", features = ["full"] } tracing = "0.1.41" -ui = { path = "../ui" } +iris = { path = "../iris" } wgpu = "27.0.1" winit = "0.30.12" diff --git a/src/bin/client.rs b/src/bin/client.rs index 0af21cf..1937fb2 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -1,5 +1,9 @@ use openworm::client::App; fn main() { + quinn::rustls::crypto::aws_lc_rs::default_provider() + .install_default() + .unwrap(); + App::run(); } diff --git a/src/client/app.rs b/src/client/app.rs index 16c154f..62ba4fe 100644 --- a/src/client/app.rs +++ b/src/client/app.rs @@ -1,37 +1,68 @@ +use std::sync::Arc; + use winit::{ application::ApplicationHandler, event::WindowEvent, - event_loop::{ActiveEventLoop, EventLoop}, + event_loop::{ActiveEventLoop, EventLoop, EventLoopProxy}, window::{Window, WindowId}, }; +use crate::client::ClientEvent; + use super::Client; -#[derive(Default)] +#[derive(Clone)] +pub struct AppHandle { + pub proxy: EventLoopProxy, + pub window: Arc, +} + pub struct App { client: Option, + proxy: EventLoopProxy, } impl App { pub fn run() { - let event_loop = EventLoop::new().unwrap(); - event_loop.run_app(&mut App::default()).unwrap(); + let event_loop = EventLoop::with_user_event().build().unwrap(); + let proxy = event_loop.create_proxy(); + event_loop + .run_app(&mut App { + client: Default::default(), + proxy, + }) + .unwrap(); } } -impl ApplicationHandler for App { +impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { if self.client.is_none() { let window = event_loop .create_window(Window::default_attributes()) .unwrap(); - let client = Client::new(window.into()); + let client = Client::new(AppHandle { + proxy: self.proxy.clone(), + window: window.into(), + }); self.client = Some(client); } } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { + let client = self.client.as_mut().unwrap(); + client.window_event(event, event_loop); + } + + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: ClientEvent) { let client = self.client.as_mut().unwrap(); client.event(event, event_loop); } } + +impl AppHandle { + pub fn send(&self, event: ClientEvent) { + self.proxy.send_event(event); + self.window.request_redraw(); + } +} diff --git a/src/client/input.rs b/src/client/input.rs index ac051c0..cb06829 100644 --- a/src/client/input.rs +++ b/src/client/input.rs @@ -1,4 +1,4 @@ -use ui::{ +use iris::{ core::{CursorState, Modifiers}, layout::Vec2, }; diff --git a/src/client/mod.rs b/src/client/mod.rs index 0f2c2a1..ccc441a 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -1,117 +1,57 @@ -use std::sync::Arc; - pub use app::App; use arboard::Clipboard; use input::Input; +use iris::prelude::*; use render::Renderer; -use ui::prelude::*; -use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; +use winit::{event::WindowEvent, event_loop::ActiveEventLoop}; + +use crate::client::ui::{Submit, UiData}; mod app; mod input; mod render; +mod ui; -use len_fns::*; +pub use app::AppHandle; + +pub enum ClientEvent { + Connect, +} pub struct Client { renderer: Renderer, input: Input, ui: Ui, focus: Option>, + ui_data: UiData, clipboard: Clipboard, } -#[derive(Eq, PartialEq, Hash, Clone)] -struct Submit; - -impl DefaultEvent for Submit { - type Data = (); -} - -pub fn msg_widget(content: String) -> impl WidgetLike { - let content = text(content) - .editable() - .size(20) - .text_align(Align::Left) - .wrap(true) - .id_on(CursorSense::click(), |id, client: &mut Client, ctx| { - client.ui.text(id).select(ctx.cursor, ctx.size); - client.focus = Some(id.clone()); - }); - let header = text("some user").size(20); - ( - image(include_bytes!("./assets/sungals.png")) - .sized((70, 70)) - .align(Align::TopLeft), - (header.align(Align::TopLeft), content.align(Align::TopLeft)) - .span(Dir::DOWN) - .gap(10), - ) - .span(Dir::RIGHT) - .gap(10) -} - impl Client { - pub fn new(window: Arc) -> Self { - let renderer = Renderer::new(window); + pub fn new(handle: AppHandle) -> Self { + let renderer = Renderer::new(handle.window.clone()); - let mut ui = Ui::new(); - - let send_text = text("some stuff idk") - .editable() - .size(20) - .text_align(Align::Left) - .add(&mut ui); - - let msg_area = Span::empty(Dir::DOWN).gap(15).add(&mut ui); - - quinn::rustls::crypto::aws_lc_rs::default_provider() - .install_default() - .unwrap(); - // connect().unwrap(); - - let msg_panel = ( - msg_area - .clone() - .background(rect(Color::SKY)) - .align(Align::BotLeft) - .scroll() - .pad(Padding::x(15)) - .height(rest(1)), - send_text - .clone() - .id_on(Submit, move |id, client: &mut Client, _| { - let content = client.ui.text(id).take(); - let msg = msg_widget(content).add(&mut client.ui); - client.ui[&msg_area].children.push(msg.any()); - }) - .pad(15) - .on(CursorSense::click(), move |client: &mut Client, data| { - client.ui.text(&send_text).select(data.cursor, data.size); - client.focus = Some(send_text.clone()); - }) - .background(rect(Color::BLACK.brighter(0.05)).radius(15)) - .pad(15) - .height(80), - ) - .span(Dir::DOWN) - .width(rest(1)) - .background(rect(Color::BLACK.brighter(0.1))); - - (rect(Color::BLACK.brighter(0.05)).width(80), msg_panel) - .span(Dir::RIGHT) - .set_root(&mut ui); + let (ui, ui_data) = ui::ui(handle); Self { renderer, input: Input::default(), ui, + ui_data, focus: None, clipboard: Clipboard::new().unwrap(), } } - pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { + pub fn event(&mut self, event: ClientEvent, _: &ActiveEventLoop) { + match event { + ClientEvent::Connect => { + self.ui.set_root(self.ui_data.main_view.clone()); + } + } + } + + pub fn window_event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { let input_changed = self.input.event(&event); let cursor_state = self.cursor_state().clone(); if let Some(focus) = &self.focus diff --git a/src/client/render/mod.rs b/src/client/render/mod.rs index d57b4ad..df6084c 100644 --- a/src/client/render/mod.rs +++ b/src/client/render/mod.rs @@ -1,9 +1,9 @@ -use pollster::FutureExt; -use std::sync::Arc; -use ui::{ +use iris::{ layout::Ui, render::{UiLimits, UiRenderer}, }; +use pollster::FutureExt; +use std::sync::Arc; use wgpu::{util::StagingBelt, *}; use winit::{dpi::PhysicalSize, window::Window}; diff --git a/src/client/ui.rs b/src/client/ui.rs new file mode 100644 index 0000000..7212ec6 --- /dev/null +++ b/src/client/ui.rs @@ -0,0 +1,131 @@ +use iris::prelude::*; +use len_fns::*; + +use crate::{ + client::{Client, app::AppHandle}, + net::client::connect, +}; + +#[derive(Eq, PartialEq, Hash, Clone)] +pub struct Submit; + +impl DefaultEvent for Submit { + type Data = (); +} + +pub struct UiData { + pub main_view: WidgetId, +} + +pub fn ui(handle: AppHandle) -> (Ui, UiData) { + let mut ui = Ui::new(); + + let msg_panel = msg_panel(&mut ui); + let side_bar = rect(Color::BLACK.brighter(0.05)).width(80); + let main_view = (side_bar, msg_panel).span(Dir::RIGHT).add(&mut ui).any(); + + let field = |name| text(name).editable().size(20); + let ip = field("ip"); + // let username = field("username"); + // let password = field("password"); + + let mut fbx = |field: TextBuilder| { + let field = field.add(&mut ui); + field + .clone() + .pad(10) + .background(rect(Color::BLACK.brighter(0.1)).radius(15)) + .on(CursorSense::click(), focus(field)) + }; + + let color = Color::GREEN; + let submit = rect(color) + .radius(15) + .id_on(CursorSense::click(), move |id, client: &mut Client, _| { + client.ui[id].color = color.darker(0.3); + connect(handle.clone()); + }) + .height(40); + let login = ( + text("login").size(30), + fbx(ip), + // fbx(username), + // fbx(password), + submit, + ) + .span(Dir::DOWN) + .gap(10) + .pad(15) + .background(rect(Color::BLACK.brighter(0.2)).radius(15)) + .width(400) + .align(Align::Center); + login.set_root(&mut ui); + + let data = UiData { main_view }; + (ui, data) +} + +pub fn msg_widget(content: String) -> impl WidgetLike { + let content = text(content) + .editable() + .size(20) + .text_align(Align::Left) + .wrap(true) + .id_on(CursorSense::click(), |id, client: &mut Client, ctx| { + client.ui.text(id).select(ctx.cursor, ctx.size); + client.focus = Some(id.clone()); + }); + let header = text("some user").size(20); + ( + image(include_bytes!("./assets/sungals.png")) + .sized((70, 70)) + .align(Align::TopLeft), + (header.align(Align::TopLeft), content.align(Align::TopLeft)) + .span(Dir::DOWN) + .gap(10), + ) + .span(Dir::RIGHT) + .gap(10) +} + +pub fn focus(id: WidgetId) -> impl Fn(&mut Client, CursorData) { + move |client: &mut Client, data: CursorData| { + client.ui.text(&id).select(data.cursor, data.size); + client.focus = Some(id.clone()); + } +} + +pub fn msg_panel(ui: &mut Ui) -> impl WidgetFn + use<> { + let msg_area = Span::empty(Dir::DOWN).gap(15).add(ui); + + let send_text = text("some stuff idk") + .editable() + .size(20) + .text_align(Align::Left) + .add(ui); + + ( + msg_area + .clone() + .align(Align::BotLeft) + .scroll() + .pad(Padding::x(15)) + .height(rest(1)), + send_text + .clone() + .id_on(Submit, move |id, client: &mut Client, _| { + let content = client.ui.text(id).take(); + let msg = msg_widget(content).add(&mut client.ui); + client.ui[&msg_area].children.push(msg.any()); + }) + .pad(15) + .on(CursorSense::click(), focus(send_text)) + .background(rect(Color::BLACK.brighter(0.05)).radius(15)) + .pad(15) + .height(80) + .z_offset(1), + ) + .span(Dir::DOWN) + .width(rest(1)) + .background(rect(Color::BLACK.brighter(0.1))) +} diff --git a/src/net/client.rs b/src/net/client.rs index 7e5c504..0101b84 100644 --- a/src/net/client.rs +++ b/src/net/client.rs @@ -1,3 +1,4 @@ +use crate::client::{AppHandle, ClientEvent}; use quinn::{crypto::rustls::QuicClientConfig, rustls::pki_types::CertificateDer}; use std::{ fs, @@ -7,8 +8,14 @@ use std::{ sync::Arc, }; +pub fn connect(handle: AppHandle) { + std::thread::spawn(|| { + connect_the(handle).unwrap(); + }); +} + #[tokio::main] -pub async fn connect() -> anyhow::Result<()> { +async fn connect_the(handle: AppHandle) -> anyhow::Result<()> { let dirs = directories_next::ProjectDirs::from("", "", "openworm").unwrap(); let mut roots = quinn::rustls::RootCertStore::empty(); match fs::read(dirs.data_local_dir().join("cert.der")) { @@ -36,13 +43,15 @@ pub async fn connect() -> anyhow::Result<()> { .await .map_err(|e| anyhow::anyhow!("failed to connect: {}", e))?; - let (mut send, mut recv) = conn + let (mut send, recv) = conn .open_bi() .await .map_err(|e| anyhow::anyhow!("failed to open stream: {}", e))?; drop(recv); + handle.send(ClientEvent::Connect); + send.write_all(&[39]).await.expect("failed to send"); send.finish().unwrap(); send.stopped().await.unwrap();