text update for more code reuse + much better caching

This commit is contained in:
2025-09-29 13:45:48 -04:00
parent c98a43f94d
commit 628840d5cd
9 changed files with 319 additions and 273 deletions

View File

@@ -8,7 +8,6 @@ mod sized;
mod span;
mod stack;
mod text;
mod text_edit;
mod trait_fns;
pub use align::*;
@@ -21,5 +20,4 @@ pub use sized::*;
pub use span::*;
pub use stack::*;
pub use text::*;
pub use text_edit::*;
pub use trait_fns::*;

View File

@@ -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
View 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,
}
}

View File

@@ -1,5 +1,7 @@
use std::ops::{Deref, DerefMut};
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 winit::{
event::KeyEvent,
@@ -7,38 +9,24 @@ use winit::{
};
pub struct TextEdit {
pub attrs: TextAttrs,
/// inner alignment of text region (within where its drawn)
pub align: Align,
buf: TextBuffer,
cursor: Option<Cursor>,
size: Vec2,
pub(super) view: TextView,
pub(super) cursor: Option<Cursor>,
}
impl TextEdit {
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 {
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.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);
let tex = self.view.draw(&mut painter.size_ctx());
let region = text_region(&tex, self.align);
painter.texture_within(&tex.handle, region);
if let Some(cursor) = &self.cursor
&& let Some(offset) = cursor_pos(cursor, &self.buf)
@@ -50,24 +38,11 @@ impl Widget for TextEdit {
.offset(offset)
.within(&region),
);
} 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 {
let width = if self.attrs.wrap {
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))
UiVec2::abs(self.view.draw(ctx).size())
}
}
@@ -104,41 +79,6 @@ fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<(f32, f32)> {
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 text: &'a mut TextEdit,
pub font_system: &'a mut FontSystem,
@@ -190,7 +130,7 @@ impl<'a> TextEditCtx<'a> {
fn insert_inner(&mut self, text: &str) {
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();
line_text.insert_str(cursor.index, text);
line.set_text(line_text, line.ending(), line.attrs_list().clone());
@@ -202,10 +142,11 @@ impl<'a> TextEditCtx<'a> {
pub fn newline(&mut self) {
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);
cursor.line += 1;
self.text.buf.lines.insert(cursor.line, new);
lines.insert(cursor.line, new);
cursor.index = 0;
}
}
@@ -221,13 +162,14 @@ impl<'a> TextEditCtx<'a> {
pub fn delete(&mut self) {
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.line == self.text.buf.lines.len() - 1 {
if cursor.line == lines.len() - 1 {
return;
}
let add = self.text.buf.lines.remove(cursor.line + 1).into_text();
let line = &mut self.text.buf.lines[cursor.line];
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);
line.set_text(cur, line.ending(), line.attrs_list().clone());
@@ -311,29 +253,16 @@ impl TextInputResult {
}
}
impl FnOnce<(&mut Ui,)> for TextEditBuilder {
type Output = TextEdit;
impl Deref for TextEdit {
type Target = TextView;
extern "rust-call" fn call_once(self, args: (&mut Ui,)) -> Self::Output {
let mut text = TextEdit {
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
fn deref(&self) -> &Self::Target {
&self.view
}
}
pub fn text_edit(content: impl Into<String>) -> TextEditBuilder {
TextEditBuilder {
content: content.into(),
attrs: TextAttrs::default(),
align: Align::Center,
impl DerefMut for TextEdit {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.view
}
}

130
src/core/text/mod.rs Normal file
View 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
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
layout::{
Layers, Modules, TextAttrs, TextBuffer, TextData, TextOffset, TextureHandle, Textures,
Layers, Modules, TextAttrs, TextBuffer, TextData, TextTexture, TextureHandle, Textures,
UiRegion, UiVec2, Vec2, WidgetId, Widgets,
},
render::{Primitive, PrimitiveHandle},
@@ -29,7 +29,7 @@ pub struct PainterCtx<'a> {
pub screen_size: Vec2,
pub modules: &'a mut Modules,
pub px_dependent: &'a mut HashSet<Id>,
drawing: HashSet<Id>,
draw_started: HashSet<Id>,
}
pub struct WidgetInstance {
@@ -80,12 +80,14 @@ impl<'a> PainterCtx<'a> {
screen_size: data.output_size,
modules: &mut data.modules,
px_dependent: &mut data.px_dependent,
drawing: HashSet::default(),
draw_started: HashSet::default(),
}
}
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 {
return;
};
@@ -99,11 +101,13 @@ impl<'a> PainterCtx<'a> {
widgets: self.widgets,
region: UiRegion::full(),
screen_size: self.screen_size,
px_dependent: self.px_dependent,
id,
};
let desired = ctx.size_inner(id, active.region);
if size != desired {
self.redraw(rid);
if self.drawing.contains(&id) {
if self.draw_started.contains(&id) {
return;
}
}
@@ -124,7 +128,7 @@ impl<'a> PainterCtx<'a> {
}
pub fn draw(&mut self, id: Id) {
self.drawing.clear();
self.draw_started.clear();
self.layers.clear();
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
// so swapping is impossible rn I think?
// 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)");
}
let mut old_children = old_children.unwrap_or_default();
@@ -165,7 +169,7 @@ impl<'a> PainterCtx<'a> {
resize = active.resize;
}
self.drawing.insert(id);
self.draw_started.insert(id);
let mut painter = Painter {
region,
@@ -308,11 +312,7 @@ impl<'a, 'c> Painter<'a, 'c> {
}
/// returns (handle, offset from top left)
pub fn render_text(
&mut self,
buffer: &mut TextBuffer,
attrs: &TextAttrs,
) -> (TextureHandle, TextOffset) {
pub fn render_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
self.ctx.text.draw(buffer, attrs, self.ctx.textures)
}
@@ -331,6 +331,8 @@ impl<'a, 'c> Painter<'a, 'c> {
widgets: self.ctx.widgets,
checked: &mut self.sized_children,
screen_size: self.ctx.screen_size,
px_dependent: self.ctx.px_dependent,
id: self.id,
region: self.region,
}
}
@@ -357,9 +359,11 @@ pub struct SizeCtx<'a> {
pub text: &'a mut TextData,
pub textures: &'a mut Textures,
widgets: &'a Widgets,
px_dependent: &'a mut HashSet<Id>,
checked: &'a mut HashMap<Id, UiVec2>,
region: UiRegion,
screen_size: Vec2,
id: Id,
}
impl SizeCtx<'_> {
@@ -381,14 +385,11 @@ impl SizeCtx<'_> {
pub fn size_within<W>(&mut self, id: &WidgetId<W>, region: UiRegion) -> UiVec2 {
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)
}
pub fn draw_text(
&mut self,
buffer: &mut TextBuffer,
attrs: &TextAttrs,
) -> (TextureHandle, TextOffset) {
pub fn draw_text(&mut self, buffer: &mut TextBuffer, attrs: &TextAttrs) -> TextTexture {
self.text.draw(buffer, attrs, self.textures)
}
}

View File

@@ -2,7 +2,7 @@ use cosmic_text::{Attrs, AttrsList, Buffer, Family, FontSystem, Metrics, SwashCa
use image::{Rgba, RgbaImage};
use crate::{
layout::{TextureHandle, Textures, UiColor, Vec2},
layout::{Align, TextureHandle, Textures, UiColor, Vec2},
util::HashMap,
};
@@ -27,6 +27,8 @@ pub struct TextAttrs {
pub line_height: f32,
pub family: Family<'static>,
pub wrap: bool,
/// inner alignment of text region (within where its drawn)
pub align: Align,
}
impl TextAttrs {
@@ -56,6 +58,7 @@ impl Default for TextAttrs {
line_height: size * 1.2,
family: Family::SansSerif,
wrap: false,
align: Align::Center,
}
}
}
@@ -66,7 +69,11 @@ impl TextData {
buffer: &mut TextBuffer,
attrs: &TextAttrs,
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 min_x = 0;
let mut min_y = 0;
@@ -113,22 +120,23 @@ impl TextData {
let y = (y - min_y) as u32;
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),
bot_right: Vec2::new(max_width - max_x as f32, height - max_y as f32),
};
(textures.add(image), offset)
}
}
}
#[derive(Clone, Copy)]
pub struct TextOffset {
#[derive(Clone)]
pub struct TextTexture {
pub handle: TextureHandle,
pub top_left: Vec2,
pub bot_right: Vec2,
}
impl TextOffset {
pub fn size(&self, handle: &TextureHandle) -> Vec2 {
handle.size() - self.top_left + self.bot_right
impl TextTexture {
pub fn size(&self) -> Vec2 {
self.handle.size() - self.top_left + self.bot_right
}
}

View File

@@ -14,6 +14,7 @@ use std::{
};
pub struct Ui {
// TODO: make this at least pub(super)
pub(crate) data: PainterData,
root: Option<WidgetId>,
updates: Vec<Id>,

View File

@@ -130,7 +130,8 @@ impl Client {
let texts = Span::empty(Dir::DOWN).add_static(&mut ui);
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)
.size(30)
.id_on(CursorSense::click(), |id, client: &mut Client, ctx| {
@@ -139,7 +140,8 @@ impl Client {
})
.id_on(Submit, move |id, client: &mut Client, _| {
let content = client.ui.text(id).take();
let text = text_edit(content)
let text = text(content)
.editable()
.size(30)
.text_align(Align::Left)
.wrap(true)