iris: move text state into shared resources
This commit is contained in:
1 parent
7c23c5f146
commit
51719ed121
17 files changed
+631
-434
No files matched your search
+58
-107
@@ -4,87 +4,65 @@ mod selection;
|
||||
|
||||
pub use build::*;
|
||||
pub use edit::*;
|
||||
use iris_core::util::MutDetect;
|
||||
pub use selection::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct Text {
|
||||
pub content: MutDetect<String>,
|
||||
view: TextView,
|
||||
}
|
||||
|
||||
pub struct TextView {
|
||||
pub attrs: MutDetect<TextAttrs>,
|
||||
pub buf: MutDetect<TextBuffer>,
|
||||
tex: Option<RenderedText>,
|
||||
width: Option<f32>,
|
||||
text: TextHandle,
|
||||
pub hint: Option<StrongWidget>,
|
||||
selection: TextSelection,
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
fn is_blank(&self) -> bool {
|
||||
self.buf.is_empty()
|
||||
self.text.text().is_empty()
|
||||
}
|
||||
|
||||
pub fn wrap_width(&self) -> Option<f32> {
|
||||
self.width
|
||||
self.text.width()
|
||||
}
|
||||
|
||||
pub fn new(buf: TextBuffer, attrs: TextAttrs, hint: Option<StrongWidget>) -> Self {
|
||||
pub fn new(text: TextHandle, hint: Option<StrongWidget>) -> Self {
|
||||
Self {
|
||||
attrs: attrs.into(),
|
||||
buf: buf.into(),
|
||||
tex: None,
|
||||
width: None,
|
||||
text,
|
||||
hint,
|
||||
selection: TextSelection::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn region(&self) -> UiRegion {
|
||||
self.tex()
|
||||
let align = self.text.attrs().align;
|
||||
self.text
|
||||
.rendered()
|
||||
.map(|t| t.size)
|
||||
.unwrap_or(Vec2::ZERO)
|
||||
.align(self.align)
|
||||
.align(align)
|
||||
}
|
||||
|
||||
fn render(&mut self, painter: &mut Painter) -> RenderedText {
|
||||
let width = if self.attrs.wrap {
|
||||
let width = if self.text.attrs().wrap {
|
||||
Some(painter.px_size().x)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let generation = painter.atlas_generation();
|
||||
if width == self.width
|
||||
&& let Some(tex) = &self.tex
|
||||
&& tex.generation == generation
|
||||
&& !self.attrs.changed
|
||||
&& !self.buf.changed
|
||||
{
|
||||
return tex.clone();
|
||||
}
|
||||
self.width = width;
|
||||
let tex = painter.render_text(&mut self.buf, &self.attrs, width);
|
||||
let tex = painter.render_text(&self.text, width);
|
||||
if crate::diagnostics::trace_enabled() {
|
||||
log::debug!(
|
||||
target: "iris::frame",
|
||||
"iris text render: chars={} width={width:?} glyphs={} size={:?}",
|
||||
self.buf.text().chars().count(),
|
||||
self.text.text().chars().count(),
|
||||
tex.glyphs.len(),
|
||||
tex.size,
|
||||
);
|
||||
}
|
||||
self.tex = Some(tex.clone());
|
||||
self.attrs.changed = false;
|
||||
self.buf.changed = false;
|
||||
tex
|
||||
}
|
||||
pub fn tex(&self) -> Option<&RenderedText> {
|
||||
self.tex.as_ref()
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let tex = self.render(painter);
|
||||
if self.is_blank()
|
||||
@@ -92,7 +70,7 @@ impl TextView {
|
||||
{
|
||||
return painter.widget(hint).size();
|
||||
}
|
||||
let region = tex.size.align(self.align);
|
||||
let region = tex.size.align(self.text.attrs().align);
|
||||
let within = region.within(&painter.region());
|
||||
painter.glyphs(&tex, within);
|
||||
Size::abs(tex.size)
|
||||
@@ -108,8 +86,10 @@ impl TextView {
|
||||
let Some(selection) = self.selection.range else {
|
||||
return used;
|
||||
};
|
||||
let layout = self.buf.layout();
|
||||
for (rect, _) in selection.geometry(layout) {
|
||||
let geometry = self
|
||||
.text
|
||||
.with_layout(|layout, _| selection.geometry(layout));
|
||||
for (rect, _) in geometry {
|
||||
let size = vec2(rect.width() as f32, rect.height() as f32);
|
||||
let top_left = vec2(rect.x0 as f32, rect.y0 as f32);
|
||||
let paint = painter.paint(&PaintId::SKY);
|
||||
@@ -119,7 +99,9 @@ impl TextView {
|
||||
);
|
||||
}
|
||||
if caret {
|
||||
let caret = selection.focus().geometry(layout, CARET_WIDTH);
|
||||
let caret = self
|
||||
.text
|
||||
.with_layout(|layout, _| selection.focus().geometry(layout, CARET_WIDTH));
|
||||
let size = vec2(caret.width() as f32, caret.height() as f32);
|
||||
let top_left = vec2(caret.x0 as f32, caret.y0 as f32);
|
||||
let paint = painter.paint(&PaintId::WHITE);
|
||||
@@ -132,28 +114,23 @@ impl TextView {
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.buf.text().to_string()
|
||||
self.text.text().to_string()
|
||||
}
|
||||
|
||||
pub fn update_attrs<R>(&mut self, update: impl FnOnce(&mut TextAttrs) -> R) -> R {
|
||||
self.text.update_attrs(update)
|
||||
}
|
||||
}
|
||||
|
||||
impl Text {
|
||||
pub fn new(content: impl Into<String>) -> Self {
|
||||
let content: String = content.into();
|
||||
Self {
|
||||
view: TextView::new(TextBuffer::new(&content), TextAttrs::default(), None),
|
||||
content: content.into(),
|
||||
}
|
||||
}
|
||||
fn update_buf(&mut self) {
|
||||
if self.content.changed {
|
||||
self.content.changed = false;
|
||||
self.view.buf.set_text(self.content.as_str());
|
||||
pub fn set_text(&mut self, content: impl Into<String>) {
|
||||
if self.view.text.set_text(content) {
|
||||
self.view.selection.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_text(&self) -> Option<String> {
|
||||
self.view.selection.selected_text(self.view.buf.text())
|
||||
self.view.selection.selected_text(&self.view.text.text())
|
||||
}
|
||||
|
||||
pub fn selection_range(&self) -> Option<std::ops::Range<usize>> {
|
||||
@@ -161,18 +138,22 @@ impl Text {
|
||||
}
|
||||
|
||||
pub fn set_with_spans(&mut self, content: impl Into<String>, spans: Vec<SpanStyle>) {
|
||||
let content = content.into();
|
||||
*self.content = content.clone();
|
||||
self.content.changed = false;
|
||||
self.view.buf.set_text(content);
|
||||
self.view.buf.set_spans(spans);
|
||||
self.view.text.set_text(content);
|
||||
self.view.text.set_spans(spans);
|
||||
self.view.selection.deselect();
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.view.content()
|
||||
}
|
||||
|
||||
pub fn update_attrs<R>(&mut self, update: impl FnOnce(&mut TextAttrs) -> R) -> R {
|
||||
self.view.update_attrs(update)
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Text {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
self.update_buf();
|
||||
let size = if self.view.selection.range.is_some() {
|
||||
self.view.draw_selectable(painter, false)
|
||||
} else {
|
||||
@@ -188,34 +169,6 @@ impl Widget for Text {
|
||||
|
||||
pub(super) const CARET_WIDTH: f32 = 1.0;
|
||||
|
||||
impl Deref for Text {
|
||||
type Target = TextAttrs;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Text {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.view
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TextView {
|
||||
type Target = TextAttrs;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.attrs
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TextView {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.attrs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::layout_tests::TestRsc;
|
||||
@@ -237,20 +190,20 @@ mod tests {
|
||||
let (mut rsc, _render, text, _root) = rendered_text("hello there");
|
||||
text.selection(&mut rsc).select_all();
|
||||
|
||||
let view = TextView::new(TextBuffer::new("hello there"), TextAttrs::default(), None);
|
||||
let mut edit = TextEdit::new(view, EditMode::MultiLine);
|
||||
let mut data = TextData::default();
|
||||
TextEditCtx {
|
||||
text: &mut edit,
|
||||
data: &mut data,
|
||||
}
|
||||
.select_all();
|
||||
let edit = wtext("hello there")
|
||||
.editable(EditMode::MultiLine)
|
||||
.add_strong(&mut rsc);
|
||||
let edit_id = edit.weak();
|
||||
edit_id(&mut rsc).select_all();
|
||||
|
||||
assert_eq!(
|
||||
rsc.ui.widgets[text].selection_range(),
|
||||
edit.selection_range()
|
||||
rsc.ui.widgets[edit_id].selection_range()
|
||||
);
|
||||
assert_eq!(
|
||||
rsc.ui.widgets[text].selected_text(),
|
||||
rsc.ui.widgets[edit_id].selected_text()
|
||||
);
|
||||
assert_eq!(rsc.ui.widgets[text].selected_text(), edit.selected_text());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -262,7 +215,7 @@ mod tests {
|
||||
Some("before")
|
||||
);
|
||||
|
||||
*rsc.ui.widgets[text].content = "after".to_string();
|
||||
rsc.ui.widgets[text].set_text("after");
|
||||
render.update(&root, &mut rsc);
|
||||
|
||||
assert_eq!(rsc.ui.widgets[text].selected_text(), None);
|
||||
@@ -277,17 +230,15 @@ mod tests {
|
||||
render.update(&root, &mut rsc);
|
||||
let selected = render.active[&text.id()].primitives.len();
|
||||
|
||||
let view = TextView::new(TextBuffer::new("selected"), TextAttrs::default(), None);
|
||||
let edit = rsc
|
||||
.ui
|
||||
.widgets
|
||||
.add_strong(TextEdit::new(view, EditMode::MultiLine));
|
||||
let edit = wtext("selected")
|
||||
.editable(EditMode::MultiLine)
|
||||
.add_strong(&mut rsc);
|
||||
let edit_id = edit.weak();
|
||||
let edit_root = edit.any();
|
||||
let mut edit_render = UiRenderState::new();
|
||||
edit_render.resize((800.0, 600.0));
|
||||
edit_render.update(&edit_root, &mut rsc);
|
||||
edit_id.edit(&mut rsc).select_all();
|
||||
edit_id(&mut rsc).select_all();
|
||||
edit_render.update(&edit_root, &mut rsc);
|
||||
let editable = edit_render.active[&edit_id.id()].primitives.len();
|
||||
|
||||
@@ -314,16 +265,16 @@ mod tests {
|
||||
render.resize((800.0, 600.0));
|
||||
render.update(&root, &mut rsc);
|
||||
|
||||
let rasterised = rsc.ui.text.atlas.glyph_count();
|
||||
let rasterised = rsc.ui.text.borrow().atlas.glyph_count();
|
||||
assert!(rasterised > 0, "the first frame rasterised no glyphs");
|
||||
|
||||
rsc.ui.text.atlas.clear();
|
||||
assert_eq!(rsc.ui.text.atlas.glyph_count(), 0);
|
||||
rsc.ui.text.borrow_mut().atlas.clear();
|
||||
assert_eq!(rsc.ui.text.borrow().atlas.glyph_count(), 0);
|
||||
render.resize((800.0, 600.0));
|
||||
render.update(&root, &mut rsc);
|
||||
|
||||
assert_eq!(
|
||||
rsc.ui.text.atlas.glyph_count(),
|
||||
rsc.ui.text.borrow().atlas.glyph_count(),
|
||||
rasterised,
|
||||
"the second frame re-emitted its cached glyphs instead of \
|
||||
re-rendering them against the fresh atlas"
|
||||
|
||||
Reference in new issue
Block a user