text update for more code reuse + much better caching
This commit is contained in:
@@ -8,7 +8,6 @@ mod sized;
|
|||||||
mod span;
|
mod span;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod text;
|
mod text;
|
||||||
mod text_edit;
|
|
||||||
mod trait_fns;
|
mod trait_fns;
|
||||||
|
|
||||||
pub use align::*;
|
pub use align::*;
|
||||||
@@ -21,5 +20,4 @@ pub use sized::*;
|
|||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
pub use text_edit::*;
|
|
||||||
pub use trait_fns::*;
|
pub use trait_fns::*;
|
||||||
|
|||||||
140
src/core/text.rs
140
src/core/text.rs
@@ -1,140 +0,0 @@
|
|||||||
use cosmic_text::{Attrs, Family, FontSystem, Metrics, Shaping};
|
|
||||||
|
|
||||||
use crate::{prelude::*, util::MutDetect};
|
|
||||||
|
|
||||||
pub struct Text {
|
|
||||||
pub content: MutDetect<String>,
|
|
||||||
pub attrs: TextAttrs,
|
|
||||||
/// inner alignment of text region (within where its drawn)
|
|
||||||
pub align: Align,
|
|
||||||
pub(super) buf: TextBuffer,
|
|
||||||
size: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Text {
|
|
||||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
|
||||||
self.attrs.font_size = size.to_f32();
|
|
||||||
self.attrs.line_height = self.attrs.font_size * 1.1;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
|
||||||
self.attrs.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn family(mut self, family: Family<'static>) -> Self {
|
|
||||||
self.attrs.family = family;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn line_height(mut self, height: f32) -> Self {
|
|
||||||
self.attrs.line_height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn new(content: impl Into<String>) -> Self {
|
|
||||||
let attrs = TextAttrs::default();
|
|
||||||
Self {
|
|
||||||
content: content.into().into(),
|
|
||||||
buf: TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height)),
|
|
||||||
attrs,
|
|
||||||
align: Align::Center,
|
|
||||||
size: Vec2::ZERO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn region(&self) -> UiRegion {
|
|
||||||
UiRegion::from_size_align(self.size, self.align)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_buf(&mut self, font_system: &mut FontSystem, width: Option<f32>) {
|
|
||||||
self.attrs.apply(font_system, &mut self.buf, width);
|
|
||||||
if self.content.changed {
|
|
||||||
self.content.changed = false;
|
|
||||||
self.buf.set_text(
|
|
||||||
font_system,
|
|
||||||
&self.content,
|
|
||||||
&Attrs::new().family(self.attrs.family),
|
|
||||||
Shaping::Advanced,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Text {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
|
||||||
let width = if self.attrs.wrap {
|
|
||||||
Some(painter.px_size().x)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let font_system = &mut painter.text_data().font_system;
|
|
||||||
self.update_buf(font_system, width);
|
|
||||||
let (handle, offset) = painter.render_text(&mut self.buf, &self.attrs);
|
|
||||||
let dims = handle.size();
|
|
||||||
self.size = offset.size(&handle);
|
|
||||||
let mut region = self.region();
|
|
||||||
region.top_left.abs += offset.top_left;
|
|
||||||
region.bot_right.abs = region.top_left.abs + dims;
|
|
||||||
painter.texture_within(&handle, region);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
|
||||||
self.update_buf(&mut ctx.text.font_system, None);
|
|
||||||
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
|
||||||
UiVec2::abs(offset.size(&handle))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(content: impl Into<String>) -> TextBuilder {
|
|
||||||
TextBuilder {
|
|
||||||
content: content.into(),
|
|
||||||
align: Align::Center,
|
|
||||||
attrs: TextAttrs::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextBuilder {
|
|
||||||
pub content: String,
|
|
||||||
pub attrs: TextAttrs,
|
|
||||||
/// inner alignment of text region (within where its drawn)
|
|
||||||
pub align: Align,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextBuilder {
|
|
||||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
|
||||||
self.attrs.font_size = size.to_f32();
|
|
||||||
self.attrs.line_height = self.attrs.font_size * 1.1;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
|
||||||
self.attrs.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn family(mut self, family: Family<'static>) -> Self {
|
|
||||||
self.attrs.family = family;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn line_height(mut self, height: f32) -> Self {
|
|
||||||
self.attrs.line_height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FnOnce<(&mut Ui,)> for TextBuilder {
|
|
||||||
type Output = Text;
|
|
||||||
|
|
||||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
|
||||||
let mut buf =
|
|
||||||
TextBuffer::new_empty(Metrics::new(self.attrs.font_size, self.attrs.line_height));
|
|
||||||
let font_system = &mut args.0.data.text.font_system;
|
|
||||||
buf.set_text(font_system, &self.content, &Attrs::new(), Shaping::Advanced);
|
|
||||||
let mut text = Text {
|
|
||||||
content: self.content.into(),
|
|
||||||
buf,
|
|
||||||
attrs: self.attrs,
|
|
||||||
align: self.align,
|
|
||||||
size: Vec2::ZERO,
|
|
||||||
};
|
|
||||||
text.content.changed = false;
|
|
||||||
self.attrs.apply(font_system, &mut text.buf, None);
|
|
||||||
text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
117
src/core/text/build.rs
Normal file
117
src/core/text/build.rs
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
use std::marker::{PhantomData, Sized};
|
||||||
|
|
||||||
|
use crate::prelude::*;
|
||||||
|
use cosmic_text::{Attrs, Family, Metrics, Shaping};
|
||||||
|
|
||||||
|
pub trait TextBuilderOutput: Sized {
|
||||||
|
type Output;
|
||||||
|
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextBuilder<O = TextOutput> {
|
||||||
|
pub content: String,
|
||||||
|
pub attrs: TextAttrs,
|
||||||
|
_pd: PhantomData<O>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<O> TextBuilder<O> {
|
||||||
|
pub fn size(mut self, size: impl UiNum) -> Self {
|
||||||
|
self.attrs.font_size = size.to_f32();
|
||||||
|
self.attrs.line_height = self.attrs.font_size * 1.1;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn color(mut self, color: UiColor) -> Self {
|
||||||
|
self.attrs.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn family(mut self, family: Family<'static>) -> Self {
|
||||||
|
self.attrs.family = family;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn line_height(mut self, height: f32) -> Self {
|
||||||
|
self.attrs.line_height = height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn text_align(mut self, align: Align) -> Self {
|
||||||
|
self.attrs.align = align;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn wrap(mut self, wrap: bool) -> Self {
|
||||||
|
self.attrs.wrap = wrap;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn editable(self) -> TextBuilder<TextEditOutput> {
|
||||||
|
TextBuilder {
|
||||||
|
content: self.content,
|
||||||
|
attrs: self.attrs,
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextOutput;
|
||||||
|
impl TextBuilderOutput for TextOutput {
|
||||||
|
type Output = Text;
|
||||||
|
|
||||||
|
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
||||||
|
let mut buf = TextBuffer::new_empty(Metrics::new(
|
||||||
|
builder.attrs.font_size,
|
||||||
|
builder.attrs.line_height,
|
||||||
|
));
|
||||||
|
let font_system = &mut ui.data.text.font_system;
|
||||||
|
buf.set_text(
|
||||||
|
font_system,
|
||||||
|
&builder.content,
|
||||||
|
&Attrs::new(),
|
||||||
|
Shaping::Advanced,
|
||||||
|
);
|
||||||
|
let mut text = Text {
|
||||||
|
content: builder.content.into(),
|
||||||
|
view: TextView::new(buf, builder.attrs),
|
||||||
|
};
|
||||||
|
text.content.changed = false;
|
||||||
|
builder.attrs.apply(font_system, &mut text.view.buf, None);
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextEditOutput;
|
||||||
|
impl TextBuilderOutput for TextEditOutput {
|
||||||
|
type Output = TextEdit;
|
||||||
|
|
||||||
|
fn run(ui: &mut Ui, builder: TextBuilder<Self>) -> Self::Output {
|
||||||
|
let buf = TextBuffer::new_empty(Metrics::new(
|
||||||
|
builder.attrs.font_size,
|
||||||
|
builder.attrs.line_height,
|
||||||
|
));
|
||||||
|
let mut text = TextEdit {
|
||||||
|
view: TextView::new(buf, builder.attrs),
|
||||||
|
cursor: None,
|
||||||
|
};
|
||||||
|
let font_system = &mut ui.data.text.font_system;
|
||||||
|
text.buf.set_text(
|
||||||
|
font_system,
|
||||||
|
&builder.content,
|
||||||
|
&Attrs::new(),
|
||||||
|
Shaping::Advanced,
|
||||||
|
);
|
||||||
|
builder.attrs.apply(font_system, &mut text.buf, None);
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<O: TextBuilderOutput> FnOnce<(&mut Ui,)> for TextBuilder<O> {
|
||||||
|
type Output = O::Output;
|
||||||
|
|
||||||
|
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
||||||
|
O::run(args.0, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(content: impl Into<String>) -> TextBuilder {
|
||||||
|
TextBuilder {
|
||||||
|
content: content.into(),
|
||||||
|
attrs: TextAttrs::default(),
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use cosmic_text::{Affinity, Attrs, Cursor, Family, FontSystem, Metrics, Motion, Shaping};
|
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion, Shaping};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::KeyEvent,
|
event::KeyEvent,
|
||||||
@@ -7,38 +9,24 @@ use winit::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct TextEdit {
|
pub struct TextEdit {
|
||||||
pub attrs: TextAttrs,
|
pub(super) view: TextView,
|
||||||
/// inner alignment of text region (within where its drawn)
|
pub(super) cursor: Option<Cursor>,
|
||||||
pub align: Align,
|
|
||||||
buf: TextBuffer,
|
|
||||||
cursor: Option<Cursor>,
|
|
||||||
size: Vec2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextEdit {
|
impl TextEdit {
|
||||||
pub fn region(&self) -> UiRegion {
|
pub fn region(&self) -> UiRegion {
|
||||||
UiRegion::from_size_align(self.size, self.align)
|
UiRegion::from_size_align(
|
||||||
|
self.tex().map(|t| t.size()).unwrap_or(Vec2::ZERO),
|
||||||
|
self.align,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for TextEdit {
|
impl Widget for TextEdit {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
let width = if self.attrs.wrap {
|
let tex = self.view.draw(&mut painter.size_ctx());
|
||||||
Some(painter.px_size().x)
|
let region = text_region(&tex, self.align);
|
||||||
} else {
|
painter.texture_within(&tex.handle, region);
|
||||||
None
|
|
||||||
};
|
|
||||||
let font_system = &mut painter.text_data().font_system;
|
|
||||||
self.attrs.apply(font_system, &mut self.buf, width);
|
|
||||||
self.buf.shape_until_scroll(font_system, false);
|
|
||||||
let (handle, tex_offset) = painter.render_text(&mut self.buf, &self.attrs);
|
|
||||||
let dims = handle.size();
|
|
||||||
self.size = tex_offset.size(&handle);
|
|
||||||
let region = self.region();
|
|
||||||
let mut tex_region = region;
|
|
||||||
tex_region.top_left.abs += tex_offset.top_left;
|
|
||||||
tex_region.bot_right.abs = tex_region.top_left.abs + dims;
|
|
||||||
painter.texture_within(&handle, tex_region);
|
|
||||||
|
|
||||||
if let Some(cursor) = &self.cursor
|
if let Some(cursor) = &self.cursor
|
||||||
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
&& let Some(offset) = cursor_pos(cursor, &self.buf)
|
||||||
@@ -50,24 +38,11 @@ impl Widget for TextEdit {
|
|||||||
.offset(offset)
|
.offset(offset)
|
||||||
.within(®ion),
|
.within(®ion),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// keep number of primitives constant so shifting isn't needed
|
|
||||||
painter.primitive(RectPrimitive::color(Color::NONE));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
||||||
let width = if self.attrs.wrap {
|
UiVec2::abs(self.view.draw(ctx).size())
|
||||||
Some(ctx.px_size().x)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
self.attrs
|
|
||||||
.apply(&mut ctx.text.font_system, &mut self.buf, width);
|
|
||||||
self.buf
|
|
||||||
.shape_until_scroll(&mut ctx.text.font_system, false);
|
|
||||||
let (handle, offset) = ctx.draw_text(&mut self.buf, &self.attrs);
|
|
||||||
UiVec2::abs(offset.size(&handle))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,41 +79,6 @@ fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
|
|||||||
prev
|
prev
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextEditBuilder {
|
|
||||||
pub content: String,
|
|
||||||
pub attrs: TextAttrs,
|
|
||||||
/// inner alignment of text region (within where its drawn)
|
|
||||||
pub align: Align,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextEditBuilder {
|
|
||||||
pub fn size(mut self, size: impl UiNum) -> Self {
|
|
||||||
self.attrs.font_size = size.to_f32();
|
|
||||||
self.attrs.line_height = self.attrs.font_size * 1.1;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn color(mut self, color: UiColor) -> Self {
|
|
||||||
self.attrs.color = color;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn family(mut self, family: Family<'static>) -> Self {
|
|
||||||
self.attrs.family = family;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn line_height(mut self, height: f32) -> Self {
|
|
||||||
self.attrs.line_height = height;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn text_align(mut self, align: Align) -> Self {
|
|
||||||
self.align = align;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn wrap(mut self, wrap: bool) -> Self {
|
|
||||||
self.attrs.wrap = wrap;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextEditCtx<'a> {
|
pub struct TextEditCtx<'a> {
|
||||||
pub text: &'a mut TextEdit,
|
pub text: &'a mut TextEdit,
|
||||||
pub font_system: &'a mut FontSystem,
|
pub font_system: &'a mut FontSystem,
|
||||||
@@ -190,7 +130,7 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
|
|
||||||
fn insert_inner(&mut self, text: &str) {
|
fn insert_inner(&mut self, text: &str) {
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
if let Some(cursor) = &mut self.text.cursor {
|
||||||
let line = &mut self.text.buf.lines[cursor.line];
|
let line = &mut self.text.view.buf.lines[cursor.line];
|
||||||
let mut line_text = line.text().to_string();
|
let mut line_text = line.text().to_string();
|
||||||
line_text.insert_str(cursor.index, text);
|
line_text.insert_str(cursor.index, text);
|
||||||
line.set_text(line_text, line.ending(), line.attrs_list().clone());
|
line.set_text(line_text, line.ending(), line.attrs_list().clone());
|
||||||
@@ -202,10 +142,11 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
|
|
||||||
pub fn newline(&mut self) {
|
pub fn newline(&mut self) {
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
if let Some(cursor) = &mut self.text.cursor {
|
||||||
let line = &mut self.text.buf.lines[cursor.line];
|
let lines = &mut self.text.view.buf.lines;
|
||||||
|
let line = &mut lines[cursor.line];
|
||||||
let new = line.split_off(cursor.index);
|
let new = line.split_off(cursor.index);
|
||||||
cursor.line += 1;
|
cursor.line += 1;
|
||||||
self.text.buf.lines.insert(cursor.line, new);
|
lines.insert(cursor.line, new);
|
||||||
cursor.index = 0;
|
cursor.index = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,13 +162,14 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
|
|
||||||
pub fn delete(&mut self) {
|
pub fn delete(&mut self) {
|
||||||
if let Some(cursor) = &mut self.text.cursor {
|
if let Some(cursor) = &mut self.text.cursor {
|
||||||
let line = &mut self.text.buf.lines[cursor.line];
|
let lines = &mut self.text.view.buf.lines;
|
||||||
|
let line = &mut lines[cursor.line];
|
||||||
if cursor.index == line.text().len() {
|
if cursor.index == line.text().len() {
|
||||||
if cursor.line == self.text.buf.lines.len() - 1 {
|
if cursor.line == lines.len() - 1 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let add = self.text.buf.lines.remove(cursor.line + 1).into_text();
|
let add = lines.remove(cursor.line + 1).into_text();
|
||||||
let line = &mut self.text.buf.lines[cursor.line];
|
let line = &mut lines[cursor.line];
|
||||||
let mut cur = line.text().to_string();
|
let mut cur = line.text().to_string();
|
||||||
cur.push_str(&add);
|
cur.push_str(&add);
|
||||||
line.set_text(cur, line.ending(), line.attrs_list().clone());
|
line.set_text(cur, line.ending(), line.attrs_list().clone());
|
||||||
@@ -311,29 +253,16 @@ impl TextInputResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FnOnce<(&mut Ui,)> for TextEditBuilder {
|
impl Deref for TextEdit {
|
||||||
type Output = TextEdit;
|
type Target = TextView;
|
||||||
|
|
||||||
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
|
fn deref(&self) -> &Self::Target {
|
||||||
let mut text = TextEdit {
|
&self.view
|
||||||
buf: TextBuffer::new_empty(Metrics::new(self.attrs.font_size, self.attrs.line_height)),
|
|
||||||
attrs: self.attrs,
|
|
||||||
align: self.align,
|
|
||||||
cursor: None,
|
|
||||||
size: Vec2::ZERO,
|
|
||||||
};
|
|
||||||
let font_system = &mut args.0.data.text.font_system;
|
|
||||||
text.buf
|
|
||||||
.set_text(font_system, &self.content, &Attrs::new(), Shaping::Advanced);
|
|
||||||
self.attrs.apply(font_system, &mut text.buf, None);
|
|
||||||
text
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_edit(content: impl Into<String>) -> TextEditBuilder {
|
impl DerefMut for TextEdit {
|
||||||
TextEditBuilder {
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
content: content.into(),
|
&mut self.view
|
||||||
attrs: TextAttrs::default(),
|
|
||||||
align: Align::Center,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
130
src/core/text/mod.rs
Normal file
130
src/core/text/mod.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
mod build;
|
||||||
|
mod edit;
|
||||||
|
|
||||||
|
pub use build::*;
|
||||||
|
pub use edit::*;
|
||||||
|
|
||||||
|
use crate::{prelude::*, util::MutDetect};
|
||||||
|
use cosmic_text::{Attrs, Metrics, Shaping};
|
||||||
|
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>,
|
||||||
|
// cache
|
||||||
|
tex: Option<TextTexture>,
|
||||||
|
width: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextView {
|
||||||
|
pub fn new(buf: TextBuffer, attrs: TextAttrs) -> Self {
|
||||||
|
Self {
|
||||||
|
attrs: attrs.into(),
|
||||||
|
buf: buf.into(),
|
||||||
|
tex: None,
|
||||||
|
width: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn draw(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
||||||
|
let width = if self.attrs.wrap {
|
||||||
|
Some(ctx.px_size().x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if width == self.width
|
||||||
|
&& let Some(tex) = &self.tex
|
||||||
|
&& !self.attrs.changed
|
||||||
|
&& !self.buf.changed
|
||||||
|
{
|
||||||
|
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 tex = ctx.draw_text(&mut self.buf, &self.attrs);
|
||||||
|
self.tex = Some(tex.clone());
|
||||||
|
self.attrs.changed = false;
|
||||||
|
self.buf.changed = false;
|
||||||
|
tex
|
||||||
|
}
|
||||||
|
pub fn tex(&self) -> Option<&TextTexture> {
|
||||||
|
self.tex.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Text {
|
||||||
|
pub fn new(content: impl Into<String>) -> Self {
|
||||||
|
let attrs = TextAttrs::default();
|
||||||
|
let buf = TextBuffer::new_empty(Metrics::new(attrs.font_size, attrs.line_height));
|
||||||
|
Self {
|
||||||
|
content: content.into().into(),
|
||||||
|
view: TextView::new(buf, attrs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn update_buf(&mut self, ctx: &mut SizeCtx) -> TextTexture {
|
||||||
|
if self.content.changed {
|
||||||
|
self.content.changed = false;
|
||||||
|
self.view.buf.set_text(
|
||||||
|
&mut ctx.text.font_system,
|
||||||
|
&self.content,
|
||||||
|
&Attrs::new().family(self.view.attrs.family),
|
||||||
|
Shaping::Advanced,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.view.draw(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Text {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let tex = self.update_buf(&mut painter.size_ctx());
|
||||||
|
let region = text_region(&tex, self.align);
|
||||||
|
painter.texture_within(&tex.handle, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn desired_size(&mut self, ctx: &mut SizeCtx) -> UiVec2 {
|
||||||
|
UiVec2::abs(self.update_buf(ctx).size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text_region(tex: &TextTexture, align: Align) -> UiRegion {
|
||||||
|
let tex_dims = tex.handle.size();
|
||||||
|
let mut region = UiRegion::from_size_align(tex.size(), align);
|
||||||
|
region.top_left.abs += tex.top_left;
|
||||||
|
region.bot_right.abs = region.top_left.abs + tex_dims;
|
||||||
|
region
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
layout::{
|
layout::{
|
||||||
Layers, Modules, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, Textures,
|
Layers, Modules, TextAttrs, TextBuffer, TextData, TextTexture, TextureHandle, Textures,
|
||||||
UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
UiRegion, UiVec2, Vec2, WidgetId, Widgets,
|
||||||
},
|
},
|
||||||
render::{Primitive, PrimitiveHandle},
|
render::{Primitive, PrimitiveHandle},
|
||||||
@@ -29,7 +29,7 @@ pub struct PainterCtx<'a> {
|
|||||||
pub screen_size: Vec2,
|
pub screen_size: Vec2,
|
||||||
pub modules: &'a mut Modules,
|
pub modules: &'a mut Modules,
|
||||||
pub px_dependent: &'a mut HashSet<Id>,
|
pub px_dependent: &'a mut HashSet<Id>,
|
||||||
drawing: HashSet<Id>,
|
draw_started: HashSet<Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WidgetInstance {
|
pub struct WidgetInstance {
|
||||||
@@ -80,12 +80,14 @@ impl<'a> PainterCtx<'a> {
|
|||||||
screen_size: data.output_size,
|
screen_size: data.output_size,
|
||||||
modules: &mut data.modules,
|
modules: &mut data.modules,
|
||||||
px_dependent: &mut data.px_dependent,
|
px_dependent: &mut data.px_dependent,
|
||||||
drawing: HashSet::default(),
|
draw_started: HashSet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redraw(&mut self, id: Id) {
|
pub fn redraw(&mut self, id: Id) {
|
||||||
self.drawing.clear();
|
if self.draw_started.contains(&id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let Some(active) = self.active.get(&id) else {
|
let Some(active) = self.active.get(&id) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -99,11 +101,13 @@ impl<'a> PainterCtx<'a> {
|
|||||||
widgets: self.widgets,
|
widgets: self.widgets,
|
||||||
region: UiRegion::full(),
|
region: UiRegion::full(),
|
||||||
screen_size: self.screen_size,
|
screen_size: self.screen_size,
|
||||||
|
px_dependent: self.px_dependent,
|
||||||
|
id,
|
||||||
};
|
};
|
||||||
let desired = ctx.size_inner(id, active.region);
|
let desired = ctx.size_inner(id, active.region);
|
||||||
if size != desired {
|
if size != desired {
|
||||||
self.redraw(rid);
|
self.redraw(rid);
|
||||||
if self.drawing.contains(&id) {
|
if self.draw_started.contains(&id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +128,7 @@ impl<'a> PainterCtx<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&mut self, id: Id) {
|
pub fn draw(&mut self, id: Id) {
|
||||||
self.drawing.clear();
|
self.draw_started.clear();
|
||||||
self.layers.clear();
|
self.layers.clear();
|
||||||
self.draw_inner(0, id, UiRegion::full(), None, None);
|
self.draw_inner(0, id, UiRegion::full(), None, None);
|
||||||
}
|
}
|
||||||
@@ -143,7 +147,7 @@ impl<'a> PainterCtx<'a> {
|
|||||||
// but this has a very weird issue where you can't move widgets unless u remove first
|
// but this has a very weird issue where you can't move widgets unless u remove first
|
||||||
// so swapping is impossible rn I think?
|
// so swapping is impossible rn I think?
|
||||||
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
|
// there's definitely better solutions like a counter (>1 = panic) but don't care rn
|
||||||
if self.drawing.contains(&id) {
|
if self.draw_started.contains(&id) {
|
||||||
panic!("Cannot draw the same widget twice (1)");
|
panic!("Cannot draw the same widget twice (1)");
|
||||||
}
|
}
|
||||||
let mut old_children = old_children.unwrap_or_default();
|
let mut old_children = old_children.unwrap_or_default();
|
||||||
@@ -165,7 +169,7 @@ impl<'a> PainterCtx<'a> {
|
|||||||
resize = active.resize;
|
resize = active.resize;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.drawing.insert(id);
|
self.draw_started.insert(id);
|
||||||
|
|
||||||
let mut painter = Painter {
|
let mut painter = Painter {
|
||||||
region,
|
region,
|
||||||
@@ -308,11 +312,7 @@ impl<'a, 'c> Painter<'a, 'c> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// returns (handle, offset from top left)
|
/// returns (handle, offset from top left)
|
||||||
pub fn render_text(
|
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
) -> (TextureHandle, TextOffset) {
|
|
||||||
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +331,8 @@ impl<'a, 'c> Painter<'a, 'c> {
|
|||||||
widgets: self.ctx.widgets,
|
widgets: self.ctx.widgets,
|
||||||
checked: &mut self.sized_children,
|
checked: &mut self.sized_children,
|
||||||
screen_size: self.ctx.screen_size,
|
screen_size: self.ctx.screen_size,
|
||||||
|
px_dependent: self.ctx.px_dependent,
|
||||||
|
id: self.id,
|
||||||
region: self.region,
|
region: self.region,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,9 +359,11 @@ pub struct SizeCtx<'a> {
|
|||||||
pub text: &'a mut TextData,
|
pub text: &'a mut TextData,
|
||||||
pub textures: &'a mut Textures,
|
pub textures: &'a mut Textures,
|
||||||
widgets: &'a Widgets,
|
widgets: &'a Widgets,
|
||||||
|
px_dependent: &'a mut HashSet<Id>,
|
||||||
checked: &'a mut HashMap<Id, UiVec2>,
|
checked: &'a mut HashMap<Id, UiVec2>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
screen_size: Vec2,
|
screen_size: Vec2,
|
||||||
|
id: Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeCtx<'_> {
|
impl SizeCtx<'_> {
|
||||||
@@ -381,14 +385,11 @@ impl SizeCtx<'_> {
|
|||||||
pub fn size_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) -> UiVec2 {
|
pub fn size_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) -> UiVec2 {
|
||||||
self.size_inner(id.id, region.within(&self.region))
|
self.size_inner(id.id, region.within(&self.region))
|
||||||
}
|
}
|
||||||
pub fn px_size(&self) -> Vec2 {
|
pub fn px_size(&mut self) -> Vec2 {
|
||||||
|
self.px_dependent.insert(self.id);
|
||||||
self.region.in_size(self.screen_size)
|
self.region.in_size(self.screen_size)
|
||||||
}
|
}
|
||||||
pub fn draw_text(
|
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
|
||||||
&mut self,
|
|
||||||
buffer: &mut TextBuffer,
|
|
||||||
attrs: &TextAttrs,
|
|
||||||
) -> (TextureHandle, TextOffset) {
|
|
||||||
self.text.draw(buffer, attrs, self.textures)
|
self.text.draw(buffer, attrs, self.textures)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use cosmic_text::{Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCa
|
|||||||
use image::{Rgba, RgbaImage};
|
use image::{Rgba, RgbaImage};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
layout::{TextureHandle, Textures, UiColor, Vec2},
|
layout::{Align, TextureHandle, Textures, UiColor, Vec2},
|
||||||
util::HashMap,
|
util::HashMap,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ pub struct TextAttrs {
|
|||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
pub family: Family<'static>,
|
pub family: Family<'static>,
|
||||||
pub wrap: bool,
|
pub wrap: bool,
|
||||||
|
/// inner alignment of text region (within where its drawn)
|
||||||
|
pub align: Align,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextAttrs {
|
impl TextAttrs {
|
||||||
@@ -56,6 +58,7 @@ impl Default for TextAttrs {
|
|||||||
line_height: size * 1.2,
|
line_height: size * 1.2,
|
||||||
family: Family::SansSerif,
|
family: Family::SansSerif,
|
||||||
wrap: false,
|
wrap: false,
|
||||||
|
align: Align::Center,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +69,11 @@ impl TextData {
|
|||||||
buffer: &mut TextBuffer,
|
buffer: &mut TextBuffer,
|
||||||
attrs: &TextAttrs,
|
attrs: &TextAttrs,
|
||||||
textures: &mut Textures,
|
textures: &mut Textures,
|
||||||
) -> (TextureHandle, TextOffset) {
|
) -> TextTexture {
|
||||||
|
// TODO: either this or the layout stuff (or both) is super slow,
|
||||||
|
// should probably do texture packing and things if possible.
|
||||||
|
// very visible if you add just a couple of wrapping texts and resize window
|
||||||
|
// should also be timed to figure out exactly what points need to be sped up
|
||||||
let mut pixels = HashMap::default();
|
let mut pixels = HashMap::default();
|
||||||
let mut min_x = 0;
|
let mut min_x = 0;
|
||||||
let mut min_y = 0;
|
let mut min_y = 0;
|
||||||
@@ -113,22 +120,23 @@ impl TextData {
|
|||||||
let y = (y - min_y) as u32;
|
let y = (y - min_y) as u32;
|
||||||
image.put_pixel(x, y, color);
|
image.put_pixel(x, y, color);
|
||||||
}
|
}
|
||||||
let offset = TextOffset {
|
TextTexture {
|
||||||
|
handle: textures.add(image),
|
||||||
top_left: Vec2::new(min_x as f32, min_y as f32),
|
top_left: Vec2::new(min_x as f32, min_y as f32),
|
||||||
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
|
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
|
||||||
};
|
}
|
||||||
(textures.add(image), offset)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone)]
|
||||||
pub struct TextOffset {
|
pub struct TextTexture {
|
||||||
|
pub handle: TextureHandle,
|
||||||
pub top_left: Vec2,
|
pub top_left: Vec2,
|
||||||
pub bot_right: Vec2,
|
pub bot_right: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextOffset {
|
impl TextTexture {
|
||||||
pub fn size(&self, handle: &TextureHandle) -> Vec2 {
|
pub fn size(&self) -> Vec2 {
|
||||||
handle.size() - self.top_left + self.bot_right
|
self.handle.size() - self.top_left + self.bot_right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub struct Ui {
|
pub struct Ui {
|
||||||
|
// TODO: make this at least pub(super)
|
||||||
pub(crate) data: PainterData,
|
pub(crate) data: PainterData,
|
||||||
root: Option<WidgetId>,
|
root: Option<WidgetId>,
|
||||||
updates: Vec<Id>,
|
updates: Vec<Id>,
|
||||||
|
|||||||
@@ -130,7 +130,8 @@ impl Client {
|
|||||||
|
|
||||||
let texts = Span::empty(Dir::DOWN).add_static(&mut ui);
|
let texts = Span::empty(Dir::DOWN).add_static(&mut ui);
|
||||||
let msg_area = (Rect::new(Color::SKY), texts.scroll()).stack();
|
let msg_area = (Rect::new(Color::SKY), texts.scroll()).stack();
|
||||||
let add_text = text_edit("add")
|
let add_text = text("add")
|
||||||
|
.editable()
|
||||||
.text_align(Align::Left)
|
.text_align(Align::Left)
|
||||||
.size(30)
|
.size(30)
|
||||||
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
|
||||||
@@ -139,7 +140,8 @@ impl Client {
|
|||||||
})
|
})
|
||||||
.id_on(Submit, move |id, client: &mut Client, _| {
|
.id_on(Submit, move |id, client: &mut Client, _| {
|
||||||
let content = client.ui.text(id).take();
|
let content = client.ui.text(id).take();
|
||||||
let text = text_edit(content)
|
let text = text(content)
|
||||||
|
.editable()
|
||||||
.size(30)
|
.size(30)
|
||||||
.text_align(Align::Left)
|
.text_align(Align::Left)
|
||||||
.wrap(true)
|
.wrap(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user