Add retained paints and shared text selection
This commit is contained in:
1 parent
1e6d3b1edd
commit
a33fbca966
42 files changed
+2424
-470
No files matched your search
+146
-2
@@ -1,9 +1,11 @@
|
||||
mod build;
|
||||
mod edit;
|
||||
mod selection;
|
||||
|
||||
pub use build::*;
|
||||
pub use edit::*;
|
||||
use iris_core::util::MutDetect;
|
||||
pub use selection::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
@@ -19,6 +21,7 @@ pub struct TextView {
|
||||
tex: Option<RenderedText>,
|
||||
width: Option<f32>,
|
||||
pub hint: Option<StrongWidget>,
|
||||
selection: TextSelection,
|
||||
}
|
||||
|
||||
impl TextView {
|
||||
@@ -37,6 +40,7 @@ impl TextView {
|
||||
tex: None,
|
||||
width: None,
|
||||
hint,
|
||||
selection: TextSelection::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +98,39 @@ impl TextView {
|
||||
Size::abs(tex.size)
|
||||
}
|
||||
|
||||
pub(super) fn draw_selectable(&mut self, painter: &mut Painter, caret: bool) -> Size {
|
||||
let base = painter.layer;
|
||||
painter.child_layer();
|
||||
let used = self.draw(painter);
|
||||
painter.layer = base;
|
||||
let region = self.region();
|
||||
|
||||
let Some(selection) = self.selection.range else {
|
||||
return used;
|
||||
};
|
||||
let layout = self.buf.layout();
|
||||
for (rect, _) in selection.geometry(layout) {
|
||||
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);
|
||||
painter.primitive_within(
|
||||
RectPrimitive::color(paint),
|
||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||
);
|
||||
}
|
||||
if caret {
|
||||
let caret = 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);
|
||||
painter.primitive_within(
|
||||
RectPrimitive::color(paint),
|
||||
size.align(Align::TOP_LEFT).offset(top_left).within(®ion),
|
||||
);
|
||||
}
|
||||
used
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.buf.text().to_string()
|
||||
}
|
||||
@@ -111,14 +148,36 @@ impl Text {
|
||||
if self.content.changed {
|
||||
self.content.changed = false;
|
||||
self.view.buf.set_text(self.content.as_str());
|
||||
self.view.selection.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_text(&self) -> Option<String> {
|
||||
self.view.selection.selected_text(self.view.buf.text())
|
||||
}
|
||||
|
||||
pub fn selection_range(&self) -> Option<std::ops::Range<usize>> {
|
||||
self.view.selection.range()
|
||||
}
|
||||
|
||||
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.selection.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for Text {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
self.update_buf();
|
||||
let size = self.view.draw(painter);
|
||||
let size = if self.view.selection.range.is_some() {
|
||||
self.view.draw_selectable(painter, false)
|
||||
} else {
|
||||
self.view.draw(painter)
|
||||
};
|
||||
painter.set_size(size);
|
||||
}
|
||||
|
||||
@@ -127,6 +186,8 @@ impl Widget for Text {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const CARET_WIDTH: f32 = 1.0;
|
||||
|
||||
impl Deref for Text {
|
||||
type Target = TextAttrs;
|
||||
|
||||
@@ -160,6 +221,89 @@ mod tests {
|
||||
use crate::layout_tests::TestRsc;
|
||||
use crate::prelude::*;
|
||||
|
||||
fn rendered_text(content: &str) -> (TestRsc, UiRenderState, WeakWidget<Text>, StrongWidget) {
|
||||
let mut rsc = TestRsc {
|
||||
ui: UiData::default(),
|
||||
};
|
||||
let text = wtext(content).add_strong(&mut rsc);
|
||||
let id = text.weak();
|
||||
let root = text.any();
|
||||
let mut render = UiRenderState::new();
|
||||
render.resize((800.0, 600.0));
|
||||
render.update(&root, &mut rsc);
|
||||
(rsc, render, id, root)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_text_and_edit_text_use_the_same_selection_engine() {
|
||||
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();
|
||||
|
||||
assert_eq!(
|
||||
rsc.ui.widgets[text].selection_range(),
|
||||
edit.selection_range()
|
||||
);
|
||||
assert_eq!(rsc.ui.widgets[text].selected_text(), edit.selected_text());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changing_display_text_clears_its_now_stale_selection() {
|
||||
let (mut rsc, mut render, text, root) = rendered_text("before");
|
||||
text.selection(&mut rsc).select_all();
|
||||
assert_eq!(
|
||||
rsc.ui.widgets[text].selected_text().as_deref(),
|
||||
Some("before")
|
||||
);
|
||||
|
||||
*rsc.ui.widgets[text].content = "after".to_string();
|
||||
render.update(&root, &mut rsc);
|
||||
|
||||
assert_eq!(rsc.ui.widgets[text].selected_text(), None);
|
||||
assert_eq!(rsc.ui.widgets[text].selection_range(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_text_draws_the_shared_highlight_without_an_editing_caret() {
|
||||
let (mut rsc, mut render, text, root) = rendered_text("selected");
|
||||
let plain = render.active[&text.id()].primitives.len();
|
||||
text.selection(&mut rsc).select_all();
|
||||
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_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_render.update(&edit_root, &mut rsc);
|
||||
let editable = edit_render.active[&edit_id.id()].primitives.len();
|
||||
|
||||
assert!(
|
||||
selected > plain,
|
||||
"the selection added no highlight primitive"
|
||||
);
|
||||
assert_eq!(
|
||||
editable,
|
||||
selected + 1,
|
||||
"editable text should add exactly its caret to the shared highlight"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clearing_the_atlas_re_renders_cached_text_instead_of_reusing_it() {
|
||||
let mut rsc = TestRsc {
|
||||
@@ -167,7 +311,7 @@ mod tests {
|
||||
};
|
||||
let root = wtext("hello there")
|
||||
.size(18)
|
||||
.color(UiColor::WHITE)
|
||||
.color(PaintId::WHITE)
|
||||
.add_strong(&mut rsc)
|
||||
.any();
|
||||
let mut render = UiRenderState::new();
|
||||
|
||||
Reference in new issue
Block a user