diff --git a/src/bin/test/main.rs b/src/bin/test/main.rs index 2cd7af8..ba10e70 100644 --- a/src/bin/test/main.rs +++ b/src/bin/test/main.rs @@ -11,7 +11,7 @@ fn main() { } pub struct Client { - info: WidgetId, + info: WidgetRef, } event_ctx!(Client); @@ -63,7 +63,7 @@ impl DefaultAppState for Client { .ui .add(image(include_bytes!("assets/sungals.png")).center()) .any(); - ctx.ui[&span_add_].children.push(child); + span_add_.get_mut().children.push(child); }) .sized((150, 150)) .align(Align::BOT_RIGHT); @@ -71,8 +71,8 @@ impl DefaultAppState for Client { let span_add_ = span_add.clone(); let del_button = rect(Color::RED) .radius(30) - .on(CursorSense::click(), move |ctx| { - ctx.ui[&span_add_].children.pop(); + .on(CursorSense::click(), move |_| { + span_add_.get_mut().children.pop(); }) .sized((150, 150)) .align(Align::BOT_LEFT); @@ -110,7 +110,7 @@ impl DefaultAppState for Client { .size(30) .attr::(()) .on(Submit, move |ctx| { - let content = ctx.ui.text(ctx.id).take(); + let content = ctx.widget.get_mut().take(); let text = wtext(content) .editable(false) .size(30) @@ -118,7 +118,7 @@ impl DefaultAppState for Client { .wrap(true) .attr::(()); let msg_box = text.background(rect(Color::WHITE.darker(0.5))).add(ctx.ui); - ctx.ui[&texts].children.push(msg_box.any()); + texts.get_mut().children.push(msg_box.any()); }) .add(ui); let text_edit_scroll = ( @@ -146,21 +146,21 @@ impl DefaultAppState for Client { let main = pad_test.clone().pad(10).add(ui); - let switch_button = |color, to: WidgetId, label| { + let switch_button = |color, to: WidgetRef, label| { let main_ = main.clone(); let rect = rect(color) .on(CursorSense::click(), move |ctx| { - ctx.ui[&main_.clone()].inner = to.clone(); - ctx.ui[ctx.id].color = color.darker(0.3); + main_.get_mut().inner = to.clone(); + ctx.widget.get_mut().color = color.darker(0.3); }) .on( CursorSense::HoverStart | CursorSense::unclick(), move |ctx| { - ctx.ui[ctx.id].color = color.brighter(0.2); + ctx.widget.get_mut().color = color.brighter(0.2); }, ) .on(CursorSense::HoverEnd, move |ctx| { - ctx.ui[ctx.id].color = color; + ctx.widget.get_mut().color = color; }); (rect, wtext(label).size(30).text_align(Align::CENTER)).stack() }; @@ -195,11 +195,8 @@ impl DefaultAppState for Client { ui.active_widgets(), state.renderer.ui.view_count() ); - if new != *ui[&self.info].content { - *ui[&self.info].content = new; - } - if ui.needs_redraw() { - state.window.request_redraw(); + if new != *self.info.get().content { + *self.info.get_mut().content = new; } } } diff --git a/src/core/mask.rs b/src/core/mask.rs index 3cdad7d..6b9b582 100644 --- a/src/core/mask.rs +++ b/src/core/mask.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct Masked { - pub inner: WidgetId, + pub inner: WidgetRef, } impl Widget for Masked { diff --git a/src/core/position/align.rs b/src/core/position/align.rs index ad00d27..9310399 100644 --- a/src/core/position/align.rs +++ b/src/core/position/align.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct Aligned { - pub inner: WidgetId, + pub inner: WidgetRef, pub align: Align, } diff --git a/src/core/position/layer.rs b/src/core/position/layer.rs index 58bb7bd..e002871 100644 --- a/src/core/position/layer.rs +++ b/src/core/position/layer.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct LayerOffset { - pub inner: WidgetId, + pub inner: WidgetRef, pub offset: usize, } diff --git a/src/core/position/max_size.rs b/src/core/position/max_size.rs index 30e3ca9..635bfbb 100644 --- a/src/core/position/max_size.rs +++ b/src/core/position/max_size.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct MaxSize { - pub inner: WidgetId, + pub inner: WidgetRef, pub x: Option, pub y: Option, } diff --git a/src/core/position/offset.rs b/src/core/position/offset.rs index 6b49f83..43cdf6c 100644 --- a/src/core/position/offset.rs +++ b/src/core/position/offset.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct Offset { - pub inner: WidgetId, + pub inner: WidgetRef, pub amt: UiVec2, } diff --git a/src/core/position/pad.rs b/src/core/position/pad.rs index 5ccc7f8..2c23eb1 100644 --- a/src/core/position/pad.rs +++ b/src/core/position/pad.rs @@ -2,7 +2,7 @@ use crate::prelude::*; pub struct Pad { pub padding: Padding, - pub inner: WidgetId, + pub inner: WidgetRef, } impl Widget for Pad { diff --git a/src/core/position/scroll.rs b/src/core/position/scroll.rs index dd13783..6acc299 100644 --- a/src/core/position/scroll.rs +++ b/src/core/position/scroll.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct Scroll { - inner: WidgetId, + inner: WidgetRef, axis: Axis, amt: f32, snap_end: bool, @@ -41,7 +41,7 @@ impl Widget for Scroll { } impl Scroll { - pub fn new(inner: WidgetId, axis: Axis) -> Self { + pub fn new(inner: WidgetRef, axis: Axis) -> Self { Self { inner, axis, diff --git a/src/core/position/sized.rs b/src/core/position/sized.rs index 567178f..b4def5c 100644 --- a/src/core/position/sized.rs +++ b/src/core/position/sized.rs @@ -1,7 +1,7 @@ use crate::prelude::*; pub struct Sized { - pub inner: WidgetId, + pub inner: WidgetRef, pub x: Option, pub y: Option, } diff --git a/src/core/position/span.rs b/src/core/position/span.rs index 0e3d53d..b3b1e3e 100644 --- a/src/core/position/span.rs +++ b/src/core/position/span.rs @@ -2,7 +2,7 @@ use crate::prelude::*; use std::marker::PhantomData; pub struct Span { - pub children: Vec, + pub children: Vec, pub dir: Dir, pub gap: f32, } @@ -182,7 +182,7 @@ impl, Tag> SpanBuilder; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.children diff --git a/src/core/position/stack.rs b/src/core/position/stack.rs index 178ecc2..b4e90cc 100644 --- a/src/core/position/stack.rs +++ b/src/core/position/stack.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use crate::prelude::*; pub struct Stack { - pub children: Vec, + pub children: Vec, pub size: StackSize, } diff --git a/src/core/ptr.rs b/src/core/ptr.rs index a650c29..c969b40 100644 --- a/src/core/ptr.rs +++ b/src/core/ptr.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Default)] pub struct WidgetPtr { - pub inner: Option, + pub inner: Option, } impl Widget for WidgetPtr { diff --git a/src/core/text/build.rs b/src/core/text/build.rs index ac4468c..cdc9872 100644 --- a/src/core/text/build.rs +++ b/src/core/text/build.rs @@ -75,7 +75,7 @@ impl TextBuilderOutput for TextOutput { builder.attrs.line_height, )); let hint = builder.hint.get(ui); - let font_system = &mut ui.data.text.font_system; + let font_system = &mut ui.data.text.borrow_mut().font_system; buf.set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); let mut text = Text { content: builder.content.into(), @@ -101,8 +101,9 @@ impl TextBuilderOutput for TextEditOutput { let mut text = TextEdit::new( TextView::new(buf, builder.attrs, builder.hint.get(ui)), builder.output.single_line, + ui.data.text.clone(), ); - let font_system = &mut ui.data.text.font_system; + let font_system = &mut ui.data.text.borrow_mut().font_system; text.buf .set_text(font_system, &builder.content, &Attrs::new(), SHAPING, None); builder.attrs.apply(font_system, &mut text.buf, None); diff --git a/src/core/text/edit.rs b/src/core/text/edit.rs index c176e05..66da6cb 100644 --- a/src/core/text/edit.rs +++ b/src/core/text/edit.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; use crate::prelude::*; -use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, LayoutRun, Motion}; +use cosmic_text::{Affinity, Attrs, Cursor, LayoutRun, Motion}; use unicode_segmentation::UnicodeSegmentation; use winit::{ event::KeyEvent, @@ -13,17 +13,19 @@ pub struct TextEdit { selection: TextSelection, history: Vec<(String, TextSelection)>, double_hit: Option, + data: TextData, pub single_line: bool, } impl TextEdit { - pub fn new(view: TextView, single_line: bool) -> Self { + pub fn new(view: TextView, single_line: bool, data: TextData) -> Self { Self { view, selection: Default::default(), history: Default::default(), double_hit: None, single_line, + data, } } pub fn select_content(&self, start: Cursor, end: Cursor) -> String { @@ -42,6 +44,369 @@ impl TextEdit { str } } + + pub fn take(&mut self) -> String { + let text = self + .buf + .lines + .drain(..) + .map(|l| l.into_text()) + .collect::>() + .join("\n"); + self.set(""); + text + } + + pub fn set(&mut self, text: &str) { + let text = self.string(text); + self.view.buf.set_text( + &mut self.data.borrow_mut().font_system, + &text, + &Attrs::new(), + SHAPING, + None, + ); + self.selection.clear(); + } + + pub fn motion(&mut self, motion: Motion, select: bool) { + if let TextSelection::Pos(cursor) = self.selection + && let Some(new) = self.buf_motion(cursor, motion) + { + self.selection = if select { + TextSelection::Span { + start: cursor, + end: new, + } + } else { + TextSelection::Pos(new) + }; + } else if let TextSelection::Span { start, end } = self.selection { + if select { + if let Some(cursor) = self.buf_motion(end, motion) { + self.selection = TextSelection::Span { start, end: cursor }; + } + } else { + let (start, end) = sort_cursors(start, end); + match motion { + Motion::Left | Motion::LeftWord => self.selection = TextSelection::Pos(start), + Motion::Right | Motion::RightWord => self.selection = TextSelection::Pos(end), + _ => { + if let Some(cursor) = self.buf_motion(end, motion) { + self.selection = TextSelection::Pos(cursor); + } + } + } + } + } + } + + pub fn replace(&mut self, len: usize, text: &str) { + let text = self.string(text); + for _ in 0..len { + self.delete(false); + } + self.insert_inner(&text, false); + } + + fn string(&self, text: &str) -> String { + if self.single_line { + text.replace('\n', "") + } else { + text.to_string() + } + } + + pub fn insert(&mut self, text: &str) { + let text = self.string(text); + let mut lines = text.split('\n'); + let Some(first) = lines.next() else { + return; + }; + self.insert_inner(first, true); + for line in lines { + self.newline(); + self.insert_inner(line, true); + } + } + + pub fn clear_span(&mut self) -> bool { + if let TextSelection::Span { start, end } = self.selection { + self.delete_between(start, end); + let (start, _) = sort_cursors(start, end); + self.selection = TextSelection::Pos(start); + true + } else { + false + } + } + + pub fn delete_between(&mut self, start: Cursor, end: Cursor) { + let lines = &mut self.view.buf.lines; + let (start, end) = sort_cursors(start, end); + if start.line == end.line { + let line = &mut lines[start.line]; + let text = line.text(); + let text = text[..start.index].to_string() + &text[end.index..]; + edit_line(line, text); + } else { + // start + let start_text = lines[start.line].text()[..start.index].to_string(); + let end_text = &lines[end.line].text()[end.index..]; + let text = start_text + end_text; + edit_line(&mut lines[start.line], text); + } + // between + let range = (start.line + 1)..=end.line; + if !range.is_empty() { + lines.splice(range, None); + } + } + + fn insert_inner(&mut self, text: &str, mov: bool) { + self.clear_span(); + if let TextSelection::Pos(cursor) = self.selection { + let line = &mut self.view.buf.lines[cursor.line]; + let mut line_text = line.text().to_string(); + line_text.insert_str(cursor.index, text); + edit_line(line, line_text); + if mov { + for _ in 0..text.chars().count() { + self.motion(Motion::Right, false); + } + } + } + } + + pub fn newline(&mut self) { + if self.single_line { + return; + } + self.clear_span(); + if let TextSelection::Pos(cursor) = &mut self.selection { + let lines = &mut self.view.buf.lines; + let line = &mut lines[cursor.line]; + let new = line.split_off(cursor.index); + cursor.line += 1; + lines.insert(cursor.line, new); + cursor.index = 0; + } + } + + pub fn backspace(&mut self, word: bool) { + if !self.clear_span() + && let TextSelection::Pos(cursor) = &mut self.selection + && (cursor.index != 0 || cursor.line != 0) + { + self.motion(if word { Motion::LeftWord } else { Motion::Left }, false); + self.delete(word); + } + } + + pub fn delete(&mut self, word: bool) { + if self.clear_span() { + return; + } + if let TextSelection::Pos(cursor) = &mut self.selection { + if word { + let start = *cursor; + if let Some(end) = self.buf_motion(start, Motion::RightWord) { + self.delete_between(start, end); + } + } else { + let lines = &mut self.view.buf.lines; + let line = &mut lines[cursor.line]; + if cursor.index == line.text().len() { + if cursor.line == lines.len() - 1 { + return; + } + let add = lines.remove(cursor.line + 1).into_text(); + let line = &mut lines[cursor.line]; + let mut cur = line.text().to_string(); + cur.push_str(&add); + edit_line(line, cur); + } else { + let mut text = line.text().to_string(); + text.remove(cursor.index); + edit_line(line, text); + } + } + } + } + + fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option { + self.view + .buf + .cursor_motion( + &mut self.data.borrow_mut().font_system, + cursor, + None, + motion, + ) + .map(|r| r.0) + } + + pub fn select_word_at(&mut self, cursor: Cursor) { + if let (Some(start), Some(end)) = ( + self.buf_motion(cursor, Motion::LeftWord), + self.buf_motion(cursor, Motion::RightWord), + ) { + self.selection = TextSelection::Span { start, end }; + } + } + + pub fn select_line_at(&mut self, cursor: Cursor) { + let end = self.buf.lines[cursor.line].text().len(); + self.selection = TextSelection::Span { + start: Cursor::new(cursor.line, 0), + end: Cursor::new(cursor.line, end), + } + } + + pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) { + let pos = pos - self.region().top_left().to_abs(size); + let hit = self.buf.hit(pos.x, pos.y); + let sel = &mut self.selection; + match sel { + TextSelection::None => { + if !drag && let Some(hit) = hit { + *sel = TextSelection::Pos(hit) + } + } + TextSelection::Pos(pos) => match (hit, drag) { + (None, false) => *sel = TextSelection::None, + (None, true) => (), + (Some(hit), false) => { + if recent && hit == *pos { + self.double_hit = Some(hit); + return self.select_word_at(hit); + } else { + *pos = hit + } + } + (Some(end), true) => *sel = TextSelection::Span { start: *pos, end }, + }, + TextSelection::Span { start, end } => match (hit, drag) { + (None, false) => *sel = TextSelection::None, + (None, true) => *sel = TextSelection::Pos(*start), + (Some(hit), false) => { + if recent + && let Some(double) = self.double_hit + && double == hit + { + return self.select_line_at(hit); + } else { + *sel = TextSelection::Pos(hit) + } + } + (Some(hit), true) => *end = hit, + }, + } + if let TextSelection::Span { start, end } = sel + && start == end + { + *sel = TextSelection::Pos(*start); + } + } + + pub fn deselect(&mut self) { + self.selection = TextSelection::None; + } + + pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { + let old = (self.content(), self.selection); + let mut undo = false; + let res = self.apply_event_inner(event, modifiers, &mut undo); + if undo && let Some((old, selection)) = self.history.pop() { + self.set(&old); + self.selection = selection; + } else if self.content() != old.0 { + self.history.push(old); + } + res + } + + fn apply_event_inner( + &mut self, + event: &KeyEvent, + modifiers: &Modifiers, + undo: &mut bool, + ) -> TextInputResult { + match &event.logical_key { + Key::Named(named) => match named { + NamedKey::Backspace => self.backspace(modifiers.control), + NamedKey::Delete => self.delete(modifiers.control), + NamedKey::Space => self.insert(" "), + NamedKey::Enter => { + if modifiers.shift { + self.newline(); + } else { + return TextInputResult::Submit; + } + } + NamedKey::ArrowRight => { + if modifiers.control { + self.motion(Motion::RightWord, modifiers.shift) + } else { + self.motion(Motion::Right, modifiers.shift) + } + } + NamedKey::ArrowLeft => { + if modifiers.control { + self.motion(Motion::LeftWord, modifiers.shift) + } else { + self.motion(Motion::Left, modifiers.shift) + } + } + NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift), + NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift), + NamedKey::Escape => { + self.deselect(); + return TextInputResult::Unfocus; + } + _ => return TextInputResult::Unused, + }, + Key::Character(text) => { + if modifiers.control { + match text.as_str() { + "v" => return TextInputResult::Paste, + "c" => { + if let TextSelection::Span { start, end } = self.selection { + let content = self.select_content(start, end); + return TextInputResult::Copy(content); + } + } + "x" => { + if let TextSelection::Span { start, end } = self.selection { + let content = self.select_content(start, end); + self.clear_span(); + return TextInputResult::Copy(content); + } + } + "a" => { + if !self.buf.lines[0].text().is_empty() || self.buf.lines.len() > 1 { + let lines = &self.buf.lines; + let last_line = lines.len() - 1; + let last_idx = lines[last_line].text().len(); + self.selection = TextSelection::Span { + start: Cursor::new(0, 0), + end: Cursor::new(last_line, last_idx), + }; + } + } + "z" => { + *undo = true; + } + _ => self.insert(text), + } + } else { + self.insert(text); + } + } + _ => return TextInputResult::Unused, + } + TextInputResult::Used + } } impl Widget for TextEdit { @@ -175,373 +540,6 @@ fn cursor_pos(cursor: Cursor, buf: &TextBuffer) -> Option { prev } -pub struct TextEditCtx<'a> { - pub text: &'a mut TextEdit, - pub font_system: &'a mut FontSystem, -} - -impl<'a> TextEditCtx<'a> { - pub fn take(&mut self) -> String { - let text = self - .text - .buf - .lines - .drain(..) - .map(|l| l.into_text()) - .collect::>() - .join("\n"); - self.text - .buf - .set_text(self.font_system, "", &Attrs::new(), SHAPING, None); - self.text.selection.clear(); - text - } - - pub fn set(&mut self, text: &str) { - let text = self.string(text); - self.text - .buf - .set_text(self.font_system, &text, &Attrs::new(), SHAPING, None); - self.text.selection.clear(); - } - - pub fn motion(&mut self, motion: Motion, select: bool) { - if let TextSelection::Pos(cursor) = self.text.selection - && let Some(new) = self.buf_motion(cursor, motion) - { - if select { - self.text.selection = TextSelection::Span { - start: cursor, - end: new, - }; - } else { - self.text.selection = TextSelection::Pos(new); - } - } else if let TextSelection::Span { start, end } = self.text.selection { - if select { - if let Some(cursor) = self.buf_motion(end, motion) { - self.text.selection = TextSelection::Span { start, end: cursor }; - } - } else { - let (start, end) = sort_cursors(start, end); - let sel = &mut self.text.selection; - match motion { - Motion::Left | Motion::LeftWord => *sel = TextSelection::Pos(start), - Motion::Right | Motion::RightWord => *sel = TextSelection::Pos(end), - _ => { - if let Some(cursor) = self.buf_motion(end, motion) { - self.text.selection = TextSelection::Pos(cursor); - } - } - } - } - } - } - - pub fn replace(&mut self, len: usize, text: &str) { - let text = self.string(text); - for _ in 0..len { - self.delete(false); - } - self.insert_inner(&text, false); - } - - fn string(&self, text: &str) -> String { - if self.text.single_line { - text.replace('\n', "") - } else { - text.to_string() - } - } - - pub fn insert(&mut self, text: &str) { - let text = self.string(text); - let mut lines = text.split('\n'); - let Some(first) = lines.next() else { - return; - }; - self.insert_inner(first, true); - for line in lines { - self.newline(); - self.insert_inner(line, true); - } - } - - pub fn clear_span(&mut self) -> bool { - if let TextSelection::Span { start, end } = self.text.selection { - self.delete_between(start, end); - let (start, _) = sort_cursors(start, end); - self.text.selection = TextSelection::Pos(start); - true - } else { - false - } - } - - pub fn delete_between(&mut self, start: Cursor, end: Cursor) { - let lines = &mut self.text.view.buf.lines; - let (start, end) = sort_cursors(start, end); - if start.line == end.line { - let line = &mut lines[start.line]; - let text = line.text(); - let text = text[..start.index].to_string() + &text[end.index..]; - edit_line(line, text); - } else { - // start - let start_text = lines[start.line].text()[..start.index].to_string(); - let end_text = &lines[end.line].text()[end.index..]; - let text = start_text + end_text; - edit_line(&mut lines[start.line], text); - } - // between - let range = (start.line + 1)..=end.line; - if !range.is_empty() { - lines.splice(range, None); - } - } - - fn insert_inner(&mut self, text: &str, mov: bool) { - self.clear_span(); - if let TextSelection::Pos(cursor) = &mut self.text.selection { - let line = &mut self.text.view.buf.lines[cursor.line]; - let mut line_text = line.text().to_string(); - line_text.insert_str(cursor.index, text); - edit_line(line, line_text); - if mov { - for _ in 0..text.chars().count() { - self.motion(Motion::Right, false); - } - } - } - } - - pub fn newline(&mut self) { - if self.text.single_line { - return; - } - self.clear_span(); - if let TextSelection::Pos(cursor) = &mut self.text.selection { - let lines = &mut self.text.view.buf.lines; - let line = &mut lines[cursor.line]; - let new = line.split_off(cursor.index); - cursor.line += 1; - lines.insert(cursor.line, new); - cursor.index = 0; - } - } - - pub fn backspace(&mut self, word: bool) { - if !self.clear_span() - && let TextSelection::Pos(cursor) = &mut self.text.selection - && (cursor.index != 0 || cursor.line != 0) - { - self.motion(if word { Motion::LeftWord } else { Motion::Left }, false); - self.delete(word); - } - } - - pub fn delete(&mut self, word: bool) { - if !self.clear_span() - && let TextSelection::Pos(cursor) = &mut self.text.selection - { - if word { - let start = *cursor; - if let Some(end) = self.buf_motion(start, Motion::RightWord) { - self.delete_between(start, end); - } - } else { - let lines = &mut self.text.view.buf.lines; - let line = &mut lines[cursor.line]; - if cursor.index == line.text().len() { - if cursor.line == lines.len() - 1 { - return; - } - let add = lines.remove(cursor.line + 1).into_text(); - let line = &mut lines[cursor.line]; - let mut cur = line.text().to_string(); - cur.push_str(&add); - edit_line(line, cur); - } else { - let mut text = line.text().to_string(); - text.remove(cursor.index); - edit_line(line, text); - } - } - } - } - - fn buf_motion(&mut self, cursor: Cursor, motion: Motion) -> Option { - self.text - .buf - .cursor_motion(self.font_system, cursor, None, motion) - .map(|r| r.0) - } - - pub fn select_word_at(&mut self, cursor: Cursor) { - if let (Some(start), Some(end)) = ( - self.buf_motion(cursor, Motion::LeftWord), - self.buf_motion(cursor, Motion::RightWord), - ) { - self.text.selection = TextSelection::Span { start, end }; - } - } - - pub fn select_line_at(&mut self, cursor: Cursor) { - let end = self.text.buf.lines[cursor.line].text().len(); - self.text.selection = TextSelection::Span { - start: Cursor::new(cursor.line, 0), - end: Cursor::new(cursor.line, end), - } - } - - pub fn select(&mut self, pos: Vec2, size: Vec2, drag: bool, recent: bool) { - let pos = pos - self.text.region().top_left().to_abs(size); - let hit = self.text.buf.hit(pos.x, pos.y); - let sel = &mut self.text.selection; - match sel { - TextSelection::None => { - if !drag && let Some(hit) = hit { - *sel = TextSelection::Pos(hit) - } - } - TextSelection::Pos(pos) => match (hit, drag) { - (None, false) => *sel = TextSelection::None, - (None, true) => (), - (Some(hit), false) => { - if recent && hit == *pos { - self.text.double_hit = Some(hit); - return self.select_word_at(hit); - } else { - *pos = hit - } - } - (Some(end), true) => *sel = TextSelection::Span { start: *pos, end }, - }, - TextSelection::Span { start, end } => match (hit, drag) { - (None, false) => *sel = TextSelection::None, - (None, true) => *sel = TextSelection::Pos(*start), - (Some(hit), false) => { - if recent - && let Some(double) = self.text.double_hit - && double == hit - { - return self.select_line_at(hit); - } else { - *sel = TextSelection::Pos(hit) - } - } - (Some(hit), true) => *end = hit, - }, - } - if let TextSelection::Span { start, end } = sel - && start == end - { - *sel = TextSelection::Pos(*start); - } - } - - pub fn deselect(&mut self) { - self.text.selection = TextSelection::None; - } - - pub fn apply_event(&mut self, event: &KeyEvent, modifiers: &Modifiers) -> TextInputResult { - let old = (self.text.content(), self.text.selection); - let mut undo = false; - let res = self.apply_event_inner(event, modifiers, &mut undo); - if undo && let Some((old, selection)) = self.text.history.pop() { - self.set(&old); - self.text.selection = selection; - } else if self.text.content() != old.0 { - self.text.history.push(old); - } - res - } - - fn apply_event_inner( - &mut self, - event: &KeyEvent, - modifiers: &Modifiers, - undo: &mut bool, - ) -> TextInputResult { - match &event.logical_key { - Key::Named(named) => match named { - NamedKey::Backspace => self.backspace(modifiers.control), - NamedKey::Delete => self.delete(modifiers.control), - NamedKey::Space => self.insert(" "), - NamedKey::Enter => { - if modifiers.shift { - self.newline(); - } else { - return TextInputResult::Submit; - } - } - NamedKey::ArrowRight => { - if modifiers.control { - self.motion(Motion::RightWord, modifiers.shift) - } else { - self.motion(Motion::Right, modifiers.shift) - } - } - NamedKey::ArrowLeft => { - if modifiers.control { - self.motion(Motion::LeftWord, modifiers.shift) - } else { - self.motion(Motion::Left, modifiers.shift) - } - } - NamedKey::ArrowUp => self.motion(Motion::Up, modifiers.shift), - NamedKey::ArrowDown => self.motion(Motion::Down, modifiers.shift), - NamedKey::Escape => { - self.deselect(); - return TextInputResult::Unfocus; - } - _ => return TextInputResult::Unused, - }, - Key::Character(text) => { - if modifiers.control { - match text.as_str() { - "v" => return TextInputResult::Paste, - "c" => { - if let TextSelection::Span { start, end } = self.text.selection { - let content = self.text.select_content(start, end); - return TextInputResult::Copy(content); - } - } - "x" => { - if let TextSelection::Span { start, end } = self.text.selection { - let content = self.text.select_content(start, end); - self.clear_span(); - return TextInputResult::Copy(content); - } - } - "a" => { - if !self.text.buf.lines[0].text().is_empty() - || self.text.buf.lines.len() > 1 - { - let lines = &self.text.buf.lines; - let last_line = lines.len() - 1; - let last_idx = lines[last_line].text().len(); - self.text.selection = TextSelection::Span { - start: Cursor::new(0, 0), - end: Cursor::new(last_line, last_idx), - }; - } - } - "z" => { - *undo = true; - } - _ => self.insert(text), - } - } else { - self.insert(text); - } - } - _ => return TextInputResult::Unused, - } - TextInputResult::Used - } -} - #[derive(Default)] pub struct Modifiers { pub shift: bool, diff --git a/src/core/text/mod.rs b/src/core/text/mod.rs index 11ac44c..bd1fd45 100644 --- a/src/core/text/mod.rs +++ b/src/core/text/mod.rs @@ -21,11 +21,11 @@ pub struct TextView { // cache tex: Option, width: Option, - pub hint: Option, + pub hint: Option, } impl TextView { - pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option) -> Self { + pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option) -> Self { Self { attrs: attrs.into(), buf: buf.into(), @@ -67,9 +67,12 @@ impl TextView { return tex.clone(); } self.width = width; - let font_system = &mut ctx.text.font_system; - self.attrs.apply(font_system, &mut self.buf, width); - self.buf.shape_until_scroll(font_system, false); + let mut text_data = ctx.text.borrow_mut(); + self.attrs + .apply(&mut text_data.font_system, &mut self.buf, width); + self.buf + .shape_until_scroll(&mut text_data.font_system, false); + drop(text_data); let tex = ctx.draw_text(&mut self.buf, &self.attrs); self.tex = Some(tex.clone()); self.attrs.changed = false; @@ -136,7 +139,7 @@ impl Text { if self.content.changed { self.content.changed = false; self.view.buf.set_text( - &mut ctx.text.font_system, + &mut ctx.text.borrow_mut().font_system, &self.content, &Attrs::new().family(self.view.attrs.family), SHAPING, diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index f3cb5c2..7d35650 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -45,7 +45,7 @@ impl, Tag> CoreWidget for W { fn label(self, label: impl Into) -> impl WidgetIdFn { |ui| { let id = self.add(ui); - ui.set_label(&id, label.into()); + id.set_label(label); id } } @@ -106,7 +106,7 @@ impl, Tag> CoreWidget for W { move |ui| { Scroll::new(self.add(ui).any(), Axis::Y) .on(CursorSense::Scroll, |ctx| { - let s = &mut ctx.ui[ctx.id]; + let s = &mut *ctx.widget.get_mut(); s.scroll(ctx.data.scroll_delta.y * 50.0); }) .add(ui) diff --git a/src/layout/attr.rs b/src/layout/attr.rs index 5c55866..5165ec0 100644 --- a/src/layout/attr.rs +++ b/src/layout/attr.rs @@ -1,8 +1,8 @@ -use crate::layout::{Ui, WidgetId, WidgetIdFn, WidgetLike}; +use crate::layout::{Ui, WidgetRef, WidgetIdFn, WidgetLike}; pub trait WidgetAttr { type Input; - fn run(ui: &mut Ui, id: &WidgetId, input: Self::Input); + fn run(ui: &mut Ui, id: &WidgetRef, input: Self::Input); } pub trait Attrable { diff --git a/src/layout/event.rs b/src/layout/event.rs index 4ea16ac..f6ec760 100644 --- a/src/layout/event.rs +++ b/src/layout/event.rs @@ -1,7 +1,7 @@ use std::{hash::Hash, rc::Rc}; use crate::{ - layout::{IdFnTag, Ui, UiModule, WidgetId, WidgetIdFn, WidgetLike}, + layout::{IdFnTag, Ui, UiModule, WidgetIdFn, WidgetLike, WidgetRef}, util::{HashMap, Id}, }; @@ -18,7 +18,7 @@ pub struct EventCtx<'a, Ctx, Data> { pub type ECtx<'a, Ctx, Data, W> = EventIdCtx<'a, Ctx, Data, W>; pub struct EventIdCtx<'a, Ctx, Data, W> { - pub id: &'a WidgetId, + pub widget: &'a WidgetRef, pub ui: &'a mut Ui, pub state: &'a mut Ctx, pub data: Data, @@ -84,7 +84,7 @@ pub mod eventable { let id_ = id.weak(); ui.register_event(&id, event, move |ctx| { f(EventIdCtx { - id: &id_.strong(), + widget: &id_.expect_strong(), state: ctx.state, data: ctx.data, ui: ctx.ui, @@ -195,7 +195,7 @@ impl Ui { pub fn run_event( &mut self, ctx: &mut Ctx, - id: &WidgetId, + id: &WidgetRef, event: E, data: E::Data, ) { @@ -203,7 +203,7 @@ impl Ui { .data .modules .get_mut::>() - .run(&id.id, event) + .run(&id.id(), event) { f(EventCtx { ui: self, diff --git a/src/layout/id.rs b/src/layout/id.rs index a107b80..a812b8b 100644 --- a/src/layout/id.rs +++ b/src/layout/id.rs @@ -1,11 +1,14 @@ -use std::{any::TypeId, marker::PhantomData, sync::mpsc::Sender}; - -use crate::{ - layout::{Ui, WidgetLike}, - util::{Id, RefCounter}, +use std::{ + cell::{Ref, RefCell, RefMut}, + marker::Unsize, + rc::{Rc, Weak}, + sync::mpsc::Sender, }; -pub struct AnyWidget; +use crate::{ + layout::{Ui, Widget, WidgetLike}, + util::Id, +}; /// An identifier for a widget that can index a UI to get the associated widget. /// It should always remain valid; it keeps a ref count and removes the widget from the UI if all @@ -15,156 +18,148 @@ pub struct AnyWidget; /// Instead, add generic bounds on methods that take an ID if they need specific data. /// /// TODO: ergonomic clones when they get put in rust-analyzer & don't cause ICEs? -#[repr(C)] -pub struct WidgetId { - pub(super) ty: TypeId, - pub(super) id: Id, - counter: RefCounter, - send: Sender, - _pd: PhantomData, +pub struct WidgetRef(Rc>>); + +struct Inner { + id: Id, + send: Sender, + label: String, + widget: W, } -#[repr(C)] -pub struct WeakWidgetId { - pub(super) ty: TypeId, - pub(super) id: Id, - counter: RefCounter, - send: Sender, - _pd: PhantomData, +pub enum WidgetUpdate { + Drop(Id), + Mutate(Id), } -impl PartialEq for WidgetId { +pub struct WeakWidgetRef(Weak>>); + +impl PartialEq for WidgetRef { fn eq(&self, other: &Self) -> bool { - self.ty == other.ty && self.id == other.id + self.id() == other.id() } } -impl std::fmt::Debug for WidgetId { +impl std::fmt::Debug for WidgetRef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.id.fmt(f) + self.id().fmt(f) } } -impl Clone for WidgetId { +impl Clone for WidgetRef { fn clone(&self) -> Self { - Self { - id: self.id, - ty: self.ty, - counter: self.counter.clone(), - send: self.send.clone(), - _pd: PhantomData, - } + Self(self.0.clone()) } } -impl WidgetId { - pub(super) fn new(id: Id, ty: TypeId, send: Sender) -> Self { - Self { - ty, +impl WidgetRef { + pub(super) fn new(id: Id, widget: W, send: Sender) -> Self { + let mut label = std::any::type_name::().to_string(); + if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) { + label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1; + } + Self(Rc::new(RefCell::new(Inner { + widget, id, - counter: RefCounter::new(), send, - _pd: PhantomData, - } - } - - pub fn any(self) -> WidgetId { - self.cast_type() - } - - pub fn as_any(&self) -> &WidgetId { - // SAFETY: self is repr(C) and generic only used for phantom data - unsafe { std::mem::transmute(self) } - } - - pub fn key(&self) -> Id { - self.id - } - - pub(super) fn cast_type(self) -> WidgetId { - // SAFETY: self is repr(C) and generic only used for phantom data - unsafe { std::mem::transmute(self) } - } - - pub fn refs(&self) -> u32 { - self.counter.refs() - } - - pub fn weak(&self) -> WeakWidgetId { - let Self { - ty, - id, - ref counter, - ref send, - _pd, - } = *self; - WeakWidgetId { - ty, - id, - counter: counter.quiet_clone(), - send: send.clone(), - _pd, - } + label, + }))) } } -impl WeakWidgetId { +impl> WidgetRef { + pub fn any(self) -> WidgetRef { + WidgetRef(self.0) + } + pub fn as_any(&self) -> WidgetRef { + WidgetRef(self.0.clone()) + } +} + +impl WidgetRef { + pub fn id(&self) -> Id { + self.0.borrow().id + } + + pub fn get(&self) -> Ref<'_, W> { + Ref::map(self.0.borrow(), |i| &i.widget) + } + + pub fn get_mut(&self) -> RefMut<'_, W> { + let inner = self.0.borrow_mut(); + let _ = inner.send.send(WidgetUpdate::Mutate(inner.id)); + RefMut::map(inner, |i| &mut i.widget) + } + + pub fn get_mut_quiet(&self) -> RefMut<'_, W> { + RefMut::map(self.0.borrow_mut(), |i| &mut i.widget) + } + + pub fn get_label(&self) -> Ref<'_, String> { + Ref::map(self.0.borrow(), |i| &i.label) + } + + pub fn set_label(&self, label: impl Into) { + self.0.borrow_mut().label = label.into(); + } + + pub fn refs(&self) -> usize { + Rc::strong_count(&self.0) + } + + pub fn weak(&self) -> WeakWidgetRef { + WeakWidgetRef(Rc::downgrade(&self.0)) + } +} + +impl WeakWidgetRef { /// should guarantee that widget is still valid to prevent indexing failures - pub(crate) fn strong(&self) -> WidgetId { - let Self { - ty, - id, - ref counter, - ref send, - _pd, - } = *self; - WidgetId { - ty, - id, - counter: counter.clone(), - send: send.clone(), - _pd, - } + pub(crate) fn expect_strong(&self) -> WidgetRef { + WidgetRef(self.0.upgrade().expect("widget should not be dropped")) } } -impl Drop for WidgetId { +impl> WeakWidgetRef { + pub fn any(self) -> WeakWidgetRef { + WeakWidgetRef(self.0.clone()) + } +} + +impl Drop for Inner { fn drop(&mut self) { - if self.counter.drop() { - let _ = self.send.send(self.id); - } + let _ = self.send.send(WidgetUpdate::Drop(self.id)); } } pub struct IdTag; pub struct IdFnTag; -pub trait WidgetIdFn: FnOnce(&mut Ui) -> WidgetId {} -impl WidgetId> WidgetIdFn for F {} +pub trait WidgetIdFn: FnOnce(&mut Ui) -> WidgetRef {} +impl WidgetRef> WidgetIdFn for F {} -pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetId {} -impl WidgetId> WidgetRet for F {} +pub trait WidgetRet: FnOnce(&mut Ui) -> WidgetRef {} +impl WidgetRef> WidgetRet for F {} -impl WidgetLike for WidgetId { +impl WidgetLike for WidgetRef { type Widget = W; - fn add(self, _: &mut Ui) -> WidgetId { + fn add(self, _: &mut Ui) -> WidgetRef { self } } -impl WidgetId> WidgetLike for F { +impl WidgetRef> WidgetLike for F { type Widget = W; - fn add(self, ui: &mut Ui) -> WidgetId { + fn add(self, ui: &mut Ui) -> WidgetRef { self(ui) } } pub trait WidgetIdLike { - fn id(self, send: &Sender) -> WidgetId; + fn rf(self, send: &Sender) -> WidgetRef; } -impl WidgetIdLike for &WidgetId { - fn id(self, _: &Sender) -> WidgetId { +impl WidgetIdLike for &WidgetRef { + fn rf(self, _: &Sender) -> WidgetRef { self.clone() } } @@ -173,8 +168,8 @@ pub trait IdLike { fn id(&self) -> Id; } -impl IdLike for WidgetId { +impl IdLike for WidgetRef { fn id(&self) -> Id { - self.id + self.id() } } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index 8d04ec3..c1b23c6 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,7 +1,9 @@ +use std::{cell::Ref, marker::Unsize, sync::mpsc::Sender}; + use crate::{ layout::{ Axis, Len, Modules, PrimitiveLayers, RenderedText, Size, TextAttrs, TextBuffer, TextData, - TextureHandle, Textures, UiRegion, UiVec2, Vec2, WidgetId, Widgets, + TextureHandle, Textures, UiRegion, UiVec2, Vec2, Widget, WidgetRef, WidgetUpdate, Widgets, }, render::{Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst}, util::{HashMap, HashSet, Id, TrackedArena}, @@ -10,6 +12,8 @@ use crate::{ /// makes your surfaces look pretty pub struct Painter<'a, 'c> { ctx: &'a mut PainterCtx<'c>, + widget: WidgetRef, + id: Id, region: UiRegion, mask: MaskIdx, textures: Vec, @@ -18,7 +22,6 @@ pub struct Painter<'a, 'c> { children_width: HashMap, children_height: HashMap, pub layer: usize, - id: Id, } /// context for a painter; lets you draw and redraw widgets @@ -60,7 +63,6 @@ pub struct WidgetInstance { } /// data to be stored in Ui to create PainterCtxs easily -#[derive(Default)] pub struct PainterData { pub widgets: Widgets, pub active: HashMap, @@ -73,11 +75,28 @@ pub struct PainterData { pub masks: TrackedArena, } +impl PainterData { + pub fn new(send: Sender) -> Self { + Self { + widgets: Widgets::new(send), + active: Default::default(), + layers: Default::default(), + textures: Default::default(), + text: Default::default(), + output_size: Default::default(), + modules: Default::default(), + px_dependent: Default::default(), + masks: Default::default(), + } + } +} + impl<'a> PainterCtx<'a> { /// redraws a widget that's currently active (drawn) /// can be called on something already drawn or removed, /// will just return if so - pub fn redraw(&mut self, id: Id) { + pub fn redraw>(&mut self, widget: &WidgetRef) { + let id = widget.id(); self.needs_redraw.remove(&id); if self.draw_started.contains(&id) { return; @@ -105,17 +124,16 @@ impl<'a> PainterCtx<'a> { cache_height: &mut self.cache_height, text: self.text, textures: self.textures, - widgets: self.widgets, outer: *outer, output_size: self.output_size, checked_width: &mut Default::default(), checked_height: &mut Default::default(), id, } - .width_inner(id); + .width(widget); if new_desired != *old_desired { // unsure if I need to walk down the tree here - self.redraw(*rid); + self.redraw(&self.widgets.get(*rid)); *old_desired = new_desired; if self.draw_started.contains(&id) { ret = true; @@ -131,16 +149,15 @@ impl<'a> PainterCtx<'a> { cache_height: &mut self.cache_height, text: self.text, textures: self.textures, - widgets: self.widgets, outer: *outer, output_size: self.output_size, checked_width: &mut Default::default(), checked_height: &mut Default::default(), id, } - .height_inner(id); + .height(widget); if new_desired != *old_desired { - self.redraw(*rid); + self.redraw(&self.widgets.get(*rid)); *old_desired = new_desired; if self.draw_started.contains(&id) { ret = true; @@ -158,7 +175,7 @@ impl<'a> PainterCtx<'a> { self.draw_inner( active.layer, - id, + widget, active.region, active.parent, active.mask, @@ -167,15 +184,16 @@ impl<'a> PainterCtx<'a> { finish(self, resize); } - fn draw_inner( + fn draw_inner>( &mut self, layer: usize, - id: Id, + widget: &WidgetRef, region: UiRegion, parent: Option, mask: MaskIdx, old_children: Option>, ) { + let id = widget.id(); // I have no idea if these checks work lol // the idea is u can't redraw stuff u already drew, // and if parent is different then there's another copy with a different parent @@ -218,6 +236,7 @@ impl<'a> PainterCtx<'a> { region, mask, layer, + widget: widget.as_any(), id, textures: Vec::new(), primitives: Vec::new(), @@ -227,7 +246,7 @@ impl<'a> PainterCtx<'a> { children_height: Default::default(), }; - painter.ctx.widgets.get_dyn_dynamic(id).draw(&mut painter); + widget.get_mut_quiet().draw(&mut painter); let children_width = painter.children_width; let children_height = painter.children_height; @@ -340,7 +359,7 @@ impl PainterData { } } - pub fn draw(&mut self, id: Id) { + pub fn draw>(&mut self, id: &WidgetRef) { let mut ctx = self.ctx(Default::default()); ctx.draw_started.clear(); ctx.layers.clear(); @@ -350,7 +369,7 @@ impl PainterData { pub fn redraw(&mut self, ids: HashSet) { let mut ctx = self.ctx(ids); while let Some(&id) = ctx.needs_redraw.iter().next() { - ctx.redraw(id); + ctx.redraw(&ctx.widgets.get(id)); } } } @@ -360,7 +379,7 @@ impl<'a, 'c> Painter<'a, 'c> { let h = self.ctx.layers.write( self.layer, PrimitiveInst { - id: self.id, + id: self.id(), primitive, region, mask_idx: self.mask, @@ -388,20 +407,28 @@ impl<'a, 'c> Painter<'a, 'c> { } /// Draws a widget within this widget's region. - pub fn widget(&mut self, id: &WidgetId) { + pub fn widget>(&mut self, id: &WidgetRef) { self.widget_at(id, self.region); } /// Draws a widget somewhere within this one. /// Useful for drawing child widgets in select areas. - pub fn widget_within(&mut self, id: &WidgetId, region: UiRegion) { + pub fn widget_within>( + &mut self, + id: &WidgetRef, + region: UiRegion, + ) { self.widget_at(id, region.within(&self.region)); } - fn widget_at(&mut self, id: &WidgetId, region: UiRegion) { - self.children.push(id.id); + fn widget_at>( + &mut self, + id: &WidgetRef, + region: UiRegion, + ) { + self.children.push(id.id()); self.ctx - .draw_inner(self.layer, id.id, region, Some(self.id), self.mask, None); + .draw_inner(self.layer, id, region, Some(self.id()), self.mask, None); } pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) { @@ -421,18 +448,21 @@ impl<'a, 'c> Painter<'a, 'c> { /// returns (handle, offset from top left) pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { - self.ctx.text.draw(buffer, attrs, self.ctx.textures) + self.ctx + .text + .borrow_mut() + .draw(buffer, attrs, self.ctx.textures) } pub fn region(&self) -> UiRegion { self.region } - pub fn size(&mut self, id: &WidgetId) -> Size { + pub fn size(&mut self, id: &WidgetRef) -> Size { self.size_ctx().size(id) } - pub fn len_axis(&mut self, id: &WidgetId, axis: Axis) -> Len { + pub fn len_axis(&mut self, id: &WidgetRef, axis: Axis) -> Len { match axis { Axis::X => self.size_ctx().width(id), Axis::Y => self.size_ctx().height(id), @@ -441,16 +471,15 @@ impl<'a, 'c> Painter<'a, 'c> { pub fn size_ctx(&mut self) -> SizeCtx<'_> { SizeCtx { + source: self.id(), + id: self.id(), text: self.ctx.text, textures: self.ctx.textures, - widgets: self.ctx.widgets, output_size: self.ctx.output_size, checked_width: &mut self.children_width, checked_height: &mut self.children_height, cache_width: &mut self.ctx.cache_width, cache_height: &mut self.ctx.cache_height, - source: self.id, - id: self.id, outer: self.region.size(), } } @@ -475,12 +504,12 @@ impl<'a, 'c> Painter<'a, 'c> { self.layer = self.ctx.layers.next(self.layer); } - pub fn label(&self) -> &str { - &self.ctx.widgets.data(&self.id).unwrap().label + pub fn label(&self) -> Ref<'_, String> { + self.widget.get_label() } - pub fn id(&self) -> &Id { - &self.id + pub fn id(&self) -> Id { + self.id } } @@ -488,7 +517,6 @@ pub struct SizeCtx<'a> { pub text: &'a mut TextData, pub textures: &'a mut Textures, source: Id, - widgets: &'a Widgets, cache_width: &'a mut HashMap, cache_height: &'a mut HashMap, checked_width: &'a mut HashMap, @@ -508,7 +536,7 @@ impl SizeCtx<'_> { &self.source } - fn width_inner(&mut self, id: Id) -> Len { + pub fn width(&mut self, widget: &WidgetRef) -> Len { // first check cache // TODO: is this needed? broken rn bc does not store children during upper size check, // so if something actually using check_* hits cache it fails to add them @@ -519,11 +547,12 @@ impl SizeCtx<'_> { // return len; // } // store self vars that need to be maintained + let id = widget.id(); let self_outer = self.outer; let self_id = self.id; // get size of input id self.id = id; - let len = self.widgets.get_dyn_dynamic(id).desired_width(self); + let len = widget.get_mut_quiet().desired_width(self); // restore vars & update cache + checked self.outer = self_outer; self.id = self_id; @@ -533,17 +562,18 @@ impl SizeCtx<'_> { } // TODO: should be refactored to share code w width_inner - fn height_inner(&mut self, id: Id) -> Len { + pub fn height(&mut self, widget: &WidgetRef) -> Len { // if let Some(&(outer, len)) = self.cache_height.get(&id) // && outer == self.outer // { // self.checked_height.insert(id, (self.outer, len)); // return len; // } + let id = widget.id(); let self_outer = self.outer; let self_id = self.id; self.id = id; - let len = self.widgets.get_dyn_dynamic(id).desired_height(self); + let len = widget.get_mut_quiet().desired_height(self); self.outer = self_outer; self.id = self_id; self.cache_height.insert(id, (self.outer, len)); @@ -551,22 +581,14 @@ impl SizeCtx<'_> { len } - pub fn width(&mut self, id: &WidgetId) -> Len { - self.width_inner(id.id) - } - - pub fn height(&mut self, id: &WidgetId) -> Len { - self.height_inner(id.id) - } - - pub fn len_axis(&mut self, id: &WidgetId, axis: Axis) -> Len { + pub fn len_axis(&mut self, id: &WidgetRef, axis: Axis) -> Len { match axis { Axis::X => self.width(id), Axis::Y => self.height(id), } } - pub fn size(&mut self, id: &WidgetId) -> Size { + pub fn size(&mut self, id: &WidgetRef) -> Size { Size { x: self.width(id), y: self.height(id), @@ -582,10 +604,6 @@ impl SizeCtx<'_> { } pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> RenderedText { - self.text.draw(buffer, attrs, self.textures) - } - - pub fn label(&self, id: &Id) -> &String { - self.widgets.label(id) + self.text.borrow_mut().draw(buffer, attrs, self.textures) } } diff --git a/src/layout/text.rs b/src/layout/text.rs index 169d166..457e7ec 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,4 +1,8 @@ -use std::simd::{Simd, num::SimdUint}; +use std::{ + cell::RefCell, + rc::Rc, + simd::{Simd, num::SimdUint}, +}; use crate::layout::{Align, RegionAlign, TextureHandle, Textures, UiColor, Vec2}; use cosmic_text::{ @@ -12,13 +16,15 @@ pub mod text_lib { pub use cosmic_text::*; } -pub struct TextData { +pub type TextData = Rc>; + +pub struct TextDataInner { pub font_system: FontSystem, pub swash_cache: SwashCache, glyph_cache: Vec<(Placement, CacheKey, Color)>, } -impl Default for TextData { +impl Default for TextDataInner { fn default() -> Self { Self { font_system: FontSystem::new(), @@ -73,7 +79,7 @@ impl Default for TextAttrs { pub const LINE_HEIGHT_MULT: f32 = 1.1; -impl TextData { +impl TextDataInner { pub fn draw( &mut self, buffer: &mut TextBuffer, diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 5bf6891..87666f4 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,52 +1,32 @@ use image::DynamicImage; use crate::{ - core::{TextEdit, TextEditCtx}, layout::{ Event, EventFn, EventModule, IdLike, PainterData, PixelRegion, TextureHandle, Vec2, Widget, - WidgetId, WidgetInstance, WidgetLike, + WidgetInstance, WidgetLike, WidgetRef, WidgetUpdate, }, util::{HashSet, Id}, }; -use std::{ - any::{Any, TypeId}, - ops::{Index, IndexMut}, - sync::mpsc::{Receiver, Sender, channel}, -}; +use std::sync::mpsc::{Receiver, channel}; pub struct Ui { // TODO: make this at least pub(super) pub(crate) data: PainterData, - root: Option, + root: Option, updates: HashSet, - recv: Receiver, - pub(super) send: Sender, + free: Vec, + recv: Receiver, full_redraw: bool, resized: bool, } impl Ui { - pub fn add(&mut self, w: impl WidgetLike) -> WidgetId { + pub fn add(&mut self, w: impl WidgetLike) -> WidgetRef { w.add(self) } - /// useful for debugging - pub fn set_label(&mut self, id: &WidgetId, label: String) { - self.data.widgets.data_mut(&id.id).unwrap().label = label; - } - - pub fn label(&self, id: &WidgetId) -> &String { - &self.data.widgets.data(&id.id).unwrap().label - } - - pub fn add_widget(&mut self, w: W) -> WidgetId { - self.push(w) - } - - pub fn push(&mut self, w: W) -> WidgetId { - let id = self.new_id(); - self.data.widgets.insert(id.id, w); - id + pub fn add_widget(&mut self, w: W) -> WidgetRef { + self.data.widgets.insert(w) } pub fn set_root(&mut self, w: impl WidgetLike) { @@ -58,36 +38,20 @@ impl Ui { Self::default() } - pub fn get(&self, id: &impl IdLike) -> Option<&W> { - self.data.widgets.get(id) - } - - pub fn get_mut(&mut self, id: &impl IdLike) -> Option<&mut W> { - self.data.widgets.get_mut(id) - } - - fn new_id(&mut self) -> WidgetId { - WidgetId::new( - self.data.widgets.reserve(), - TypeId::of::(), - self.send.clone(), - ) - } - pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle { self.data.textures.add(image) } pub fn register_event( &mut self, - id: &WidgetId, + id: &WidgetRef, event: E, f: impl EventFn, ) { self.data .modules .get_mut::>() - .register(id.id, event, f); + .register(id.id(), event, f); } pub fn resize(&mut self, size: impl Into) { @@ -95,7 +59,7 @@ impl Ui { self.resized = true; } - pub fn redraw_all(&mut self) { + fn redraw_all(&mut self) { for (_, inst) in self.data.active.drain() { for m in self.data.modules.iter_mut() { m.on_undraw(&inst); @@ -104,30 +68,35 @@ impl Ui { // free before bc nothing should exist self.free(); if let Some(root) = &self.root { - self.data.draw(root.id); + self.data.draw(root); } } - pub fn update(&mut self) { + pub fn update(&mut self) -> bool { + for update in self.recv.try_iter() { + match update { + WidgetUpdate::Drop(id) => { + self.free.push(id); + } + WidgetUpdate::Mutate(id) => { + self.updates.insert(id); + } + } + } if self.full_redraw { self.redraw_all(); self.full_redraw = false; + true + } else if self.resized { + self.resized = false; + self.redraw_all(); + true } else if !self.updates.is_empty() { self.redraw_updates(); + true + } else { + false } - if self.resized { - self.resized = false; - self.redraw_size(); - } - } - - fn redraw_size(&mut self) { - // let mut ctx = PainterCtx::new(&mut self.data); - // let dep = ctx.px_dependent.clone(); - // for id in dep { - // ctx.redraw(id); - // } - self.redraw_all(); } fn redraw_updates(&mut self) { @@ -137,7 +106,7 @@ impl Ui { /// free any resources that don't have references anymore fn free(&mut self) { - for id in self.recv.try_iter() { + for id in self.free.drain(..) { for m in self.data.modules.iter_mut() { m.on_remove(&id); } @@ -158,14 +127,6 @@ impl Ui { self.data.active.len() } - pub fn text(&mut self, id: &impl IdLike) -> TextEditCtx<'_> { - self.updates.insert(id.id()); - TextEditCtx { - text: self.data.widgets.get_mut(id).unwrap(), - font_system: &mut self.data.text.font_system, - } - } - pub fn debug_layers(&self) { for ((idx, depth), primitives) in self.data.layers.iter_depth() { let indent = " ".repeat(depth * 2); @@ -185,46 +146,25 @@ impl Ui { pub fn debug(&self, label: &str) -> impl Iterator { self.data.active.iter().filter_map(move |(id, inst)| { - let l = &self.data.widgets.label(id); - if *l == label { Some(inst) } else { None } + let widget = &self.data.widgets.get(*id); + if widget.get_label().as_str() == label { + Some(inst) + } else { + None + } }) } } -impl Index<&WidgetId> for Ui { - type Output = W; - - fn index(&self, id: &WidgetId) -> &Self::Output { - self.get(id).unwrap() - } -} - -impl IndexMut<&WidgetId> for Ui { - fn index_mut(&mut self, id: &WidgetId) -> &mut Self::Output { - self.updates.insert(id.id); - self.get_mut(id).unwrap() - } -} - -impl dyn Widget { - pub fn as_any(&self) -> &dyn Any { - self - } - - pub fn as_any_mut(&mut self) -> &mut dyn Any { - self - } -} - impl Default for Ui { fn default() -> Self { let (send, recv) = channel(); Self { - data: PainterData::default(), + data: PainterData::new(send), root: Default::default(), updates: Default::default(), + free: Default::default(), full_redraw: false, - send, recv, resized: false, } diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 56d18f5..7ead356 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,6 +1,6 @@ use crate::{ core::WidgetPtr, - layout::{Len, Painter, SizeCtx, Ui, WidgetId, WidgetIdFn}, + layout::{Len, Painter, SizeCtx, Ui, WidgetIdFn, WidgetRef}, }; use std::{any::Any, marker::PhantomData}; @@ -25,13 +25,13 @@ pub struct WidgetTag; pub struct FnTag; pub trait WidgetLike { - type Widget: 'static; + type Widget: Widget + 'static; - fn add(self, ui: &mut Ui) -> WidgetId; + fn add(self, ui: &mut Ui) -> WidgetRef; fn with_id( self, - f: impl FnOnce(&mut Ui, WidgetId) -> WidgetId, + f: impl FnOnce(&mut Ui, WidgetRef) -> WidgetRef, ) -> impl WidgetIdFn where Self: Sized, @@ -49,11 +49,12 @@ pub trait WidgetLike { ui.set_root(self); } - fn set_ptr(self, ptr: &WidgetId, ui: &mut Ui) + fn set_ptr(self, ptr: &WidgetRef, ui: &mut Ui) where Self: Sized, + Self::Widget: Widget, { - ui[ptr].inner = Some(self.add(ui).any()); + ptr.get_mut().inner = Some(self.add(ui).any()); } } @@ -65,25 +66,25 @@ impl W> WidgetFn for F {} impl W> WidgetLike for F { type Widget = W; - fn add(self, ui: &mut Ui) -> WidgetId { + fn add(self, ui: &mut Ui) -> WidgetRef { self(ui).add(ui) } } impl WidgetLike for W { type Widget = W; - fn add(self, ui: &mut Ui) -> WidgetId { + fn add(self, ui: &mut Ui) -> WidgetRef { ui.add_widget(self) } } pub struct WidgetArr { - pub arr: [WidgetId; LEN], + pub arr: [WidgetRef; LEN], _pd: PhantomData, } impl WidgetArr { - pub fn new(arr: [WidgetId; LEN]) -> Self { + pub fn new(arr: [WidgetRef; LEN]) -> Self { Self { arr, _pd: PhantomData, @@ -123,7 +124,7 @@ macro_rules! impl_widget_arr { #[allow(non_snake_case)] let ($($W,)*) = self; WidgetArr::new( - [$($W.add(ui).cast_type(),)*], + [$($W.add(ui).any(),)*], ) } } @@ -144,17 +145,17 @@ impl_widget_arr!(11;A B C D E F G H I J K); impl_widget_arr!(12;A B C D E F G H I J K L); pub trait WidgetOption { - fn get(self, ui: &mut Ui) -> Option; + fn get(self, ui: &mut Ui) -> Option; } impl WidgetOption for () { - fn get(self, _: &mut Ui) -> Option { + fn get(self, _: &mut Ui) -> Option { None } } -impl Option> WidgetOption for F { - fn get(self, ui: &mut Ui) -> Option { +impl Option> WidgetOption for F { + fn get(self, ui: &mut Ui) -> Option { self(ui) } } diff --git a/src/layout/widgets.rs b/src/layout/widgets.rs index d29af12..feec35f 100644 --- a/src/layout/widgets.rs +++ b/src/layout/widgets.rs @@ -1,89 +1,34 @@ +use std::sync::mpsc::Sender; + use crate::{ - layout::{IdLike, Widget}, - util::{DynBorrower, HashMap, Id, IdTracker}, + layout::{WeakWidgetRef, Widget, WidgetRef, WidgetUpdate}, + util::{HashMap, Id, IdTracker}, }; -#[derive(Default)] pub struct Widgets { ids: IdTracker, - map: HashMap, -} - -pub struct WidgetData { - pub widget: Box, - pub label: String, - /// dynamic borrow checking - pub borrowed: bool, + map: HashMap, + send: Sender, } impl Widgets { - pub fn new() -> Self { + pub fn new(send: Sender) -> Self { Self { ids: IdTracker::default(), map: HashMap::default(), + send, } } - pub fn get_dyn(&self, id: Id) -> Option<&dyn Widget> { - Some(self.map.get(&id)?.widget.as_ref()) + pub fn get(&self, id: Id) -> WidgetRef { + self.map.get(&id).unwrap().expect_strong() } - pub fn get_dyn_mut(&mut self, id: Id) -> Option<&mut dyn Widget> { - Some(self.map.get_mut(&id)?.widget.as_mut()) - } - - /// get_dyn but dynamic borrow checking of widgets - /// lets you do recursive (tree) operations, like the painter does - pub fn get_dyn_dynamic(&self, id: Id) -> WidgetWrapper<'_> { - // SAFETY: must guarantee no other mutable references to this widget exist - // done through the borrow variable - #[allow(mutable_transmutes)] - let data = unsafe { - std::mem::transmute::<&WidgetData, &mut WidgetData>(self.map.get(&id).unwrap()) - }; - if data.borrowed { - panic!("tried to mutably borrow the same widget twice"); - } - WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed) - } - - pub fn get(&self, id: &impl IdLike) -> Option<&W> { - self.get_dyn(id.id())?.as_any().downcast_ref() - } - - pub fn get_mut(&mut self, id: &impl IdLike) -> Option<&mut W> { - self.get_dyn_mut(id.id())?.as_any_mut().downcast_mut() - } - - pub fn insert(&mut self, id: Id, widget: W) { - let mut label = std::any::type_name::().to_string(); - if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) { - label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1; - } - self.insert_any(id, Box::new(widget), label); - } - - pub fn data(&self, id: &Id) -> Option<&WidgetData> { - self.map.get(id) - } - - pub fn label(&self, id: &Id) -> &String { - &self.data(id).unwrap().label - } - - pub fn data_mut(&mut self, id: &Id) -> Option<&mut WidgetData> { - self.map.get_mut(id) - } - - pub fn insert_any(&mut self, id: Id, widget: Box, label: String) { - self.map.insert( - id, - WidgetData { - widget, - label, - borrowed: false, - }, - ); + pub fn insert(&mut self, widget: W) -> WidgetRef { + let id = self.ids.next(); + let rf = WidgetRef::new(id, widget, self.send.clone()); + self.map.insert(id, rf.weak().any()); + rf } pub fn delete(&mut self, id: Id) { @@ -103,5 +48,3 @@ impl Widgets { self.map.is_empty() } } - -pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>; diff --git a/src/lib.rs b/src/lib.rs index 61bfdf3..dc76b9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ #![feature(portable_simd)] #![feature(gen_blocks)] #![feature(associated_type_defaults)] +#![feature(unsize)] pub mod core; pub mod layout; diff --git a/src/winit/attr.rs b/src/winit/attr.rs index e89a9d6..ab8b2aa 100644 --- a/src/winit/attr.rs +++ b/src/winit/attr.rs @@ -5,9 +5,9 @@ use winit::dpi::{LogicalPosition, LogicalSize}; pub struct Selector; impl WidgetAttr for Selector { - type Input = WidgetId; + type Input = WidgetRef; - fn run(ui: &mut Ui, container: &WidgetId, id: Self::Input) { + fn run(ui: &mut Ui, container: &WidgetRef, id: Self::Input) { let container = container.clone(); ui.register_event( &container.clone(), @@ -30,7 +30,7 @@ pub struct Selectable; impl WidgetAttr for Selectable { type Input = (); - fn run(ui: &mut Ui, id: &WidgetId, _: Self::Input) { + fn run(ui: &mut Ui, id: &WidgetRef, _: Self::Input) { let id = id.clone(); ui.register_event(&id.clone(), CursorSense::click_or_drag(), move |ctx| { select(ctx.ui, id.clone(), ctx.state, ctx.data); @@ -38,11 +38,11 @@ impl WidgetAttr for Selectable { } } -fn select(ui: &mut Ui, id: WidgetId, state: &mut UiState, data: CursorData) { +fn select(ui: &mut Ui, id: WidgetRef, state: &mut UiState, data: CursorData) { let now = Instant::now(); let recent = (now - state.last_click) < Duration::from_millis(300); state.last_click = now; - ui.text(&id) + id.get_mut() .select(data.cursor, data.size, data.sense.is_dragging(), recent); if let Some(region) = ui.window_region(&id) { state.window.set_ime_allowed(true); diff --git a/src/winit/mod.rs b/src/winit/mod.rs index 774e3c2..ab1c0e0 100644 --- a/src/winit/mod.rs +++ b/src/winit/mod.rs @@ -28,7 +28,7 @@ pub struct DefaultState { pub struct UiState { pub renderer: UiRenderer, pub input: Input, - pub focus: Option>, + pub focus: Option>, pub clipboard: Clipboard, pub window: Arc, pub ime: usize, @@ -105,12 +105,11 @@ impl AppState for DefaultState { if old != ui_state.focus && let Some(old) = old { - ui.text(&old).deselect(); + old.get_mut().deselect(); } match &event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::RedrawRequested => { - ui.update(); ui_state.renderer.update(ui); ui_state.renderer.draw(); } @@ -123,8 +122,8 @@ impl AppState for DefaultState { && event.state.is_pressed() { let sel = &sel.clone(); - let mut text = ui.text(sel); - match text.apply_event(event, &ui_state.input.modifiers) { + let res = sel.get_mut().apply_event(event, &ui_state.input.modifiers); + match res { TextInputResult::Unfocus => { ui_state.focus = None; ui_state.window.set_ime_allowed(false); @@ -134,7 +133,7 @@ impl AppState for DefaultState { } TextInputResult::Paste => { if let Ok(t) = ui_state.clipboard.get_text() { - text.insert(&t); + sel.get_mut().insert(&t); } ui.run_event(app_state, sel, Edited, ()); } @@ -152,16 +151,15 @@ impl AppState for DefaultState { } WindowEvent::Ime(ime) => { if let Some(sel) = &ui_state.focus { - let mut text = ui.text(sel); match ime { Ime::Enabled | Ime::Disabled => (), Ime::Preedit(content, _pos) => { // TODO: highlight once that's real - text.replace(ui_state.ime, content); + sel.get_mut().replace(ui_state.ime, content); ui_state.ime = content.chars().count(); } Ime::Commit(content) => { - text.insert(content); + sel.get_mut().insert(content); } } } @@ -169,7 +167,7 @@ impl AppState for DefaultState { _ => (), } app_state.window_event(event, ui, ui_state); - if ui.needs_redraw() { + if ui.update() { ui_state.renderer.window().request_redraw(); } ui_state.input.end_frame();