Files
iris/src/bin/test/main.rs
2025-11-22 15:33:28 -05:00

358 lines
12 KiB
Rust

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<Window>,
input: Input,
ui: Ui,
info: WidgetId<Text>,
focus: Option<WidgetId<TextEdit>>,
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<TextEdit> for Selectable {
type Input = ();
fn run(ui: &mut Ui, id: &WidgetId<TextEdit>, _: 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::<f32>::from(region.top_left.tuple()),
LogicalSize::<f32>::from(region.size().tuple()),
);
}
client.focus = Some(id.clone());
},
);
}
}
impl Client {
pub fn new(window: Arc<Window>) -> 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::<Selectable>(())
.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::<Selectable>(());
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
}
}