use app::App; use arboard::Clipboard; use cosmic_text::Family; use input::Input; use iris::prelude::*; use len_fns::*; use render::Renderer; use std::{ sync::Arc, time::{Duration, Instant}, }; use winit::{ dpi::{LogicalPosition, LogicalSize}, event::{Ime, WindowEvent}, event_loop::ActiveEventLoop, window::Window, }; mod app; mod input; mod render; fn main() { App::run(); } pub struct Client { renderer: Renderer, window: Arc, input: Input, ui: Ui, info: WidgetId, focus: Option>, clipboard: Clipboard, ime: usize, last_click: Instant, } #[derive(Eq, PartialEq, Hash, Clone)] struct Submit; impl DefaultEvent for Submit { type Data = (); } pub struct Selectable; impl WidgetAttr for Selectable { type Input = (); fn run(ui: &mut Ui, id: &WidgetId, _: Self::Input) { let id = id.clone(); ui.register_event( &id.clone(), CursorSense::click_or_drag(), move |client: &mut Client, data| { let now = Instant::now(); let recent = (now - client.last_click) < Duration::from_millis(300); client.last_click = now; client.ui.text(&id).select( data.cursor, data.size, data.sense.is_dragging(), recent, ); if let Some(region) = client.ui.window_region(&id) { client.window.set_ime_allowed(true); client.window.set_ime_cursor_area( LogicalPosition::::from(region.top_left.tuple()), LogicalSize::::from(region.size().tuple()), ); } client.focus = Some(id.clone()); }, ); } } impl Client { pub fn new(window: Arc) -> Self { let renderer = Renderer::new(window.clone()); let mut ui = Ui::new(); let rrect = rect(Color::WHITE).radius(20); let pad_test = ( rrect.color(Color::BLUE), ( rrect .color(Color::RED) .sized((100, 100)) .center() .width(rest(2)), ( rrect.color(Color::ORANGE), rrect.color(Color::LIME).pad(10.0), ) .span(Dir::RIGHT) .width(rest(2)), rrect.color(Color::YELLOW), ) .span(Dir::RIGHT) .pad(10) .width(rest(3)), ) .span(Dir::RIGHT) .add_static(&mut ui); let span_test = ( rrect.color(Color::GREEN).width(100), rrect.color(Color::ORANGE), rrect.color(Color::CYAN), rrect.color(Color::BLUE).width(rel(0.5)), rrect.color(Color::MAGENTA).width(100), rrect.color(Color::RED).width(100), ) .span(Dir::LEFT) .add_static(&mut ui); let span_add = Span::empty(Dir::RIGHT).add_static(&mut ui); let add_button = rect(Color::LIME) .radius(30) .on(CursorSense::click(), move |ctx: &mut Client, _| { let child = ctx .ui .add(image(include_bytes!("assets/sungals.png")).center()) .any(); ctx.ui[span_add].children.push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); let del_button = rect(Color::RED) .radius(30) .on(CursorSense::click(), move |ctx: &mut Client, _| { ctx.ui[span_add].children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); let span_add_test = (span_add, add_button, del_button) .stack() .add_static(&mut ui); let btext = |content| text(content).size(30); let text_test = ( btext("this is a").align(Align::LEFT), btext("teeeeeeeest").align(Align::RIGHT), btext("okkk\nokkkkkk!").align(Align::LEFT), btext("hmm"), btext("a"), ( btext("'").family(Family::Monospace).align(Align::TOP), btext("'").family(Family::Monospace), btext(":gamer mode").family(Family::Monospace), rect(Color::CYAN).sized((10, 10)).center(), rect(Color::RED).sized((100, 100)).center(), rect(Color::PURPLE).sized((50, 50)).align(Align::TOP), ) .span(Dir::RIGHT) .center(), text("pretty cool right?").size(50), ) .span(Dir::DOWN) .add_static(&mut ui); let texts = Span::empty(Dir::DOWN).gap(10).add_static(&mut ui); let msg_area = texts.scroll().masked().background(rect(Color::SKY)); let add_text = text("add") .editable() .text_align(Align::LEFT) .size(30) .attr::(()) .id_on(Submit, move |id, client: &mut Client, _| { let content = client.ui.text(id).take(); let text = text(content) .editable() .size(30) .text_align(Align::LEFT) .wrap(true) .attr::(()); let msg_box = text .background(rect(Color::WHITE.darker(0.5))) .add(&mut client.ui); client.ui[texts].children.push(msg_box.any()); }) .add(&mut ui); let text_edit_scroll = ( msg_area.height(rest(1)), ( Rect::new(Color::WHITE.darker(0.9)), ( add_text.clone().width(rest(1)), Rect::new(Color::GREEN) .on(CursorSense::click(), move |client: &mut Client, _| { client.run_event(&add_text, Submit, ()); }) .sized((40, 40)), ) .span(Dir::RIGHT) .pad(10), ) .stack() .size(StackSize::Child(1)) .offset_layer(1) .align(Align::BOT), ) .span(Dir::DOWN) .add_static(&mut ui); let main = pad_test.pad(10).add_static(&mut ui); let switch_button = |color, to, label| { let rect = rect(color) .id_on(CursorSense::click(), move |id, ui: &mut Ui, _| { ui[main].inner.set_static(to); ui[id].color = color.darker(0.3); }) .edit_on( CursorSense::HoverStart | CursorSense::unclick(), move |r, _| { r.color = color.brighter(0.2); }, ) .edit_on(CursorSense::HoverEnd, move |r, _| { r.color = color; }); (rect, text(label).size(30).text_align(Align::CENTER)).stack() }; let tabs = ( switch_button(Color::RED, pad_test.any(), "pad"), switch_button(Color::GREEN, span_test.any(), "span"), switch_button(Color::BLUE, span_add_test.any(), "image span"), switch_button(Color::MAGENTA, text_test.any(), "text layout"), switch_button( Color::YELLOW.mul_rgb(0.5), text_edit_scroll.any(), "text edit scroll", ), ) .span(Dir::RIGHT); let info = text("").add(&mut ui); let info_sect = info.clone().pad(10).align(Align::RIGHT); ((tabs.height(40), main).span(Dir::DOWN), info_sect) .stack() .set_root(&mut ui); Self { renderer, window, input: Input::default(), ui, info, focus: None, clipboard: Clipboard::new().unwrap(), ime: 0, last_click: Instant::now(), } } pub fn event(&mut self, event: WindowEvent, event_loop: &ActiveEventLoop) { let input_changed = self.input.event(&event); let cursor_state = self.cursor_state().clone(); let old = self.focus.clone(); if cursor_state.buttons.left.is_start() { self.focus = None; } if input_changed { let window_size = self.window_size(); self.run_sensors(&cursor_state, window_size); self.ui.run_sensors(&cursor_state, window_size); } if old != self.focus && let Some(old) = old { self.ui.text(&old).deselect(); } match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { self.ui.update(); self.renderer.update(&mut self.ui); self.renderer.draw() } WindowEvent::Resized(size) => { self.ui.resize((size.width, size.height)); self.renderer.resize(&size) } WindowEvent::KeyboardInput { event, .. } => { if let Some(sel) = &self.focus && event.state.is_pressed() { let mut text = self.ui.text(sel); match text.apply_event(&event, &self.input.modifiers) { TextInputResult::Unfocus => { self.focus = None; } TextInputResult::Submit => { self.run_event(&sel.clone(), Submit, ()); } TextInputResult::Paste => { if let Ok(t) = self.clipboard.get_text() { text.insert(&t); } } TextInputResult::Copy(text) => { if let Err(err) = self.clipboard.set_text(text) { eprintln!("failed to copy text to clipboard: {err}") } } TextInputResult::Unused | TextInputResult::Used => (), } } } WindowEvent::Ime(ime) => { if let Some(sel) = &self.focus { let mut text = self.ui.text(sel); match ime { Ime::Enabled | Ime::Disabled => (), Ime::Preedit(content, _pos) => { // TODO: highlight once that's real text.replace(self.ime, &content); self.ime = content.chars().count(); } Ime::Commit(content) => { text.insert(&content); } } } } _ => (), } let new = format!( "widgets: {}\nactive:{}\nviews: {}", self.ui.num_widgets(), self.ui.active_widgets(), self.renderer.ui.view_count() ); if new != *self.ui[&self.info].content { *self.ui[&self.info].content = new; } if self.ui.needs_redraw() { self.renderer.window().request_redraw(); } self.input.end_frame(); } } impl UiCtx for Client { fn ui(&mut self) -> &mut Ui { &mut self.ui } }